diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt index 5a7feb99cc..f26b9c1e5f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.data.download +import android.app.PendingIntent import android.content.Context +import android.content.Intent import android.graphics.BitmapFactory import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat @@ -141,7 +143,10 @@ internal class DownloadNotifier(private val context: Context) { val title = download.manga.title.chop(15) val quotedTitle = Pattern.quote(title) - val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), "") + val chapter = download.chapter.name.replaceFirst( + "$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), + "" + ) setContentTitle("$title - $chapter".chop(30)) setContentText( context.getString(R.string.downloading_progress) @@ -218,17 +223,36 @@ internal class DownloadNotifier(private val context: Context) { * @param error string containing error information. * @param chapter string containing chapter title. */ - fun onError(error: String? = null, chapter: String? = null) { + fun onError( + error: String? = null, + chapter: String? = null, + customIntent: Intent? = null + ) { // Create notification with(notification) { setContentTitle(chapter ?: context.getString(R.string.download_error)) setContentText(error ?: context.getString(R.string.could_not_download_unexpected_error)) - setStyle(NotificationCompat.BigTextStyle().bigText(error ?: context.getString(R.string.could_not_download_unexpected_error))) + setStyle( + NotificationCompat.BigTextStyle().bigText( + error ?: context.getString(R.string.could_not_download_unexpected_error) + ) + ) setSmallIcon(android.R.drawable.stat_sys_warning) setCategory(NotificationCompat.CATEGORY_ERROR) clearActions() setAutoCancel(true) - setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) + if (customIntent != null) { + setContentIntent( + PendingIntent.getActivity( + context, + 0, + customIntent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + } else { + setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) + } color = ContextCompat.getColor(context, R.color.colorAccent) setProgress(0, 0, false) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 18323fa328..4fa70dd590 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -1,7 +1,12 @@ package eu.kanade.tachiyomi.data.download import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Environment +import android.provider.Settings import android.webkit.MimeTypeMap +import androidx.core.net.toUri import com.hippo.unifile.UniFile import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.PublishRelay @@ -298,7 +303,21 @@ class Downloader( notifier.onError(context.getString(R.string.couldnt_download_low_space), download.chapter.name) return@defer Observable.just(download) } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && + !Environment.isExternalStorageManager() + ) { + val intent = Intent( + Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, + "package:${context.packageName}".toUri() + ) + notifier.onError( + context.getString(R.string.external_storage_download_notice), + download.chapter.name, + intent + ) + return@defer Observable.just(download) + } val chapterDirname = provider.getChapterDirName(download.chapter) val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index 69d728e13f..35562b7311 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.ui.manga -import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.AnimatorSet @@ -103,7 +102,7 @@ import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.activityBinding import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.getText -import eu.kanade.tachiyomi.util.view.requestPermissionsSafe +import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setStyle @@ -215,7 +214,7 @@ class MangaDetailsController : presenter.onCreate() binding.swipeRefresh.isRefreshing = presenter.isLoading binding.swipeRefresh.setOnRefreshListener { presenter.refreshAll() } - requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) + requestFilePermissionsSafe(301) } /** Check if device is tablet, and use a second recycler to hold the details header if so */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt index 6dae64a1e5..1077520b18 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.ui.recents -import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.app.Activity import android.content.res.ColorStateList import android.os.Bundle @@ -52,7 +51,7 @@ import eu.kanade.tachiyomi.util.view.activityBinding import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.util.view.isCollapsed import eu.kanade.tachiyomi.util.view.isExpanded -import eu.kanade.tachiyomi.util.view.requestPermissionsSafe +import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setStyle @@ -364,7 +363,7 @@ class RecentsController(bundle: Bundle? = null) : binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.expand() } setPadding(binding.downloadBottomSheet.dlBottomSheet.sheetBehavior?.isHideable == true) - requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) + requestFilePermissionsSafe(301) } fun updateTitleAndMenu() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index 0723c50ae5..6aabaf8cfa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.ui.setting -import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.app.Activity import android.app.Dialog import android.content.ActivityNotFoundException @@ -29,7 +28,7 @@ import eu.kanade.tachiyomi.data.preference.asImmediateFlow import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.system.getFilePicker import eu.kanade.tachiyomi.util.system.toast -import eu.kanade.tachiyomi.util.view.requestPermissionsSafe +import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys @@ -43,7 +42,7 @@ class SettingsBackupController : SettingsController() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 500) + requestFilePermissionsSafe(500) } override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt index 3cad5ebc18..19a7512a91 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.ui.source -import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.animation.ValueAnimator import android.app.Activity import android.content.res.ColorStateList @@ -48,7 +47,7 @@ import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.util.view.isCollapsed import eu.kanade.tachiyomi.util.view.isExpanded -import eu.kanade.tachiyomi.util.view.requestPermissionsSafe +import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.snack @@ -159,7 +158,7 @@ class BrowseController : updateTitleAndMenu() } - requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) + requestFilePermissionsSafe(301) binding.bottomSheet.root.onCreate(this) binding.shadow.alpha = diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt index 086bd00d3c..2bec988fa5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt @@ -1,9 +1,13 @@ package eu.kanade.tachiyomi.util.view +import android.Manifest import android.animation.ValueAnimator import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.os.Build +import android.os.Environment +import android.provider.Settings import android.view.Gravity import android.view.View import android.view.ViewGroup @@ -18,6 +22,7 @@ import androidx.core.net.toUri import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.afollestad.materialdialogs.MaterialDialog import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType @@ -480,6 +485,42 @@ fun Controller.requestPermissionsSafe(permissions: Array, requestCode: I } } +fun Controller.requestFilePermissionsSafe(requestCode: Int) { + val activity = activity ?: return + val permissions = mutableListOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) + permissions.forEach { permission -> + if (ContextCompat.checkSelfPermission( + activity, + permission + ) != PackageManager.PERMISSION_GRANTED + ) { + requestPermissions(arrayOf(permission), requestCode) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && + !Environment.isExternalStorageManager() + ) { + MaterialDialog(activity) + .title(R.string.all_files_permission_required) + .message(R.string.external_storage_permission_notice) + .cancelOnTouchOutside(false) + .positiveButton(android.R.string.ok) { + val intent = Intent( + Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, + "package:${activity.packageName}".toUri() + ) + try { + activity.startActivity(intent) + } catch (_: Exception) { + val intent2 = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) + activity.startActivity(intent2) + } + } + .negativeButton(android.R.string.cancel) + .show() + } +} + fun Controller.withFadeTransaction(): RouterTransaction { return RouterTransaction.with(this) .pushChangeHandler(OneWayFadeChangeHandler()) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2a7d721427..b9756e455c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,6 +3,10 @@ TachiyomiJ2K TachiJ2K + File permissions required + TachiyomiJ2K requires access to all files in Android 11 to download chapters, create automatic backups, and read local manga. \n\nOn the next screen, enable \"Allow access to manage all files.\" + TachiyomiJ2K requires access to all files to download chapters. Tap here, then enable \"Allow access to manage all files.\" +