From a14e14f92ced770af8803d4b1e4985ff1b5d8f19 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Sat, 9 Mar 2024 22:13:51 -0800 Subject: [PATCH] Feat: Support missing "All files access" setting for e.g. Android TV Fixes: #875 --- app/src/main/AndroidManifest.xml | 2 +- .../files/filelist/FileListFragment.kt | 3 +- .../files/navigation/NavigationItems.kt | 3 +- .../android/files/storage/DocumentTree.kt | 6 ++-- .../files/util/EnvironmentExtensions.kt | 35 +++++++++++++++++++ 5 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/me/zhanghai/android/files/util/EnvironmentExtensions.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b6bf024ca..3397d71f2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,7 @@ + android:maxSdkVersion="32" /> diff --git a/app/src/main/java/me/zhanghai/android/files/filelist/FileListFragment.kt b/app/src/main/java/me/zhanghai/android/files/filelist/FileListFragment.kt index c42112e61..ffe8d7d58 100644 --- a/app/src/main/java/me/zhanghai/android/files/filelist/FileListFragment.kt +++ b/app/src/main/java/me/zhanghai/android/files/filelist/FileListFragment.kt @@ -117,6 +117,7 @@ import me.zhanghai.android.files.util.isOrientationLandscape import me.zhanghai.android.files.util.putArgs import me.zhanghai.android.files.util.showToast import me.zhanghai.android.files.util.startActivitySafe +import me.zhanghai.android.files.util.supportsExternalStorageManager import me.zhanghai.android.files.util.takeIfNotEmpty import me.zhanghai.android.files.util.valueCompat import me.zhanghai.android.files.util.viewModels @@ -1350,7 +1351,7 @@ class FileListFragment : Fragment(), BreadcrumbLayout.Listener, FileListAdapter. if (viewModel.isStorageAccessRequested) { return } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (Environment::class.supportsExternalStorageManager()) { if (!Environment.isExternalStorageManager()) { ShowRequestAllFilesAccessRationaleDialogFragment.show(this) viewModel.isStorageAccessRequested = true diff --git a/app/src/main/java/me/zhanghai/android/files/navigation/NavigationItems.kt b/app/src/main/java/me/zhanghai/android/files/navigation/NavigationItems.kt index 2296d3b71..f9851b69c 100644 --- a/app/src/main/java/me/zhanghai/android/files/navigation/NavigationItems.kt +++ b/app/src/main/java/me/zhanghai/android/files/navigation/NavigationItems.kt @@ -33,13 +33,14 @@ import me.zhanghai.android.files.storage.StorageVolumeListLiveData import me.zhanghai.android.files.util.createIntent import me.zhanghai.android.files.util.isMounted import me.zhanghai.android.files.util.putArgs +import me.zhanghai.android.files.util.supportsExternalStorageManager import me.zhanghai.android.files.util.valueCompat val navigationItems: List get() = mutableListOf().apply { addAll(storageItems) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (Environment::class.supportsExternalStorageManager()) { // Starting with R, we can get read/write access to non-primary storage volumes with // MANAGE_EXTERNAL_STORAGE. However before R, we only have read-only access to them // and need to use the Storage Access Framework instead, so hide them in this case diff --git a/app/src/main/java/me/zhanghai/android/files/storage/DocumentTree.kt b/app/src/main/java/me/zhanghai/android/files/storage/DocumentTree.kt index 51b396dcc..941f3974a 100644 --- a/app/src/main/java/me/zhanghai/android/files/storage/DocumentTree.kt +++ b/app/src/main/java/me/zhanghai/android/files/storage/DocumentTree.kt @@ -9,6 +9,7 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.os.Build +import android.os.Environment import androidx.annotation.DrawableRes import java8.nio.file.Path import kotlinx.parcelize.Parcelize @@ -22,6 +23,7 @@ import me.zhanghai.android.files.file.storageVolume import me.zhanghai.android.files.provider.document.createDocumentTreeRootPath import me.zhanghai.android.files.util.createIntent import me.zhanghai.android.files.util.putArgs +import me.zhanghai.android.files.util.supportsExternalStorageManager import kotlin.random.Random @Parcelize @@ -42,8 +44,8 @@ data class DocumentTree( // android.os.storage.StorageVolume#equals [NewApi] @SuppressLint("NewApi") get() = - // We are using MANAGE_EXTERNAL_STORAGE to access all storage volumes since R. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R + // We are using MANAGE_EXTERNAL_STORAGE to access all storage volumes when supported. + if (!Environment::class.supportsExternalStorageManager() && uri.storageVolume.let { it != null && !it.isPrimaryCompat }) { R.drawable.sd_card_icon_white_24dp } else { diff --git a/app/src/main/java/me/zhanghai/android/files/util/EnvironmentExtensions.kt b/app/src/main/java/me/zhanghai/android/files/util/EnvironmentExtensions.kt new file mode 100644 index 000000000..668f59b11 --- /dev/null +++ b/app/src/main/java/me/zhanghai/android/files/util/EnvironmentExtensions.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Hai Zhang + * All Rights Reserved. + */ + +package me.zhanghai.android.files.util + +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.os.Environment +import android.provider.Settings +import androidx.annotation.ChecksSdkIntAtLeast +import androidx.annotation.RequiresApi +import me.zhanghai.android.files.app.packageManager +import kotlin.reflect.KClass + +// TvSettings didn't have "All files access" page until Android 13. +@ChecksSdkIntAtLeast(Build.VERSION_CODES.R) +fun KClass.supportsExternalStorageManager(): Boolean = + when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> true + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> + isManageAppAllFilesAccessPermissionIntentResolved + else -> false + } + +@delegate:RequiresApi(Build.VERSION_CODES.R) +private val isManageAppAllFilesAccessPermissionIntentResolved: Boolean + by lazy(LazyThreadSafetyMode.NONE) { + packageManager.resolveActivity( + Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION), + PackageManager.MATCH_DEFAULT_ONLY or PackageManager.MATCH_SYSTEM_ONLY + ) != null + }