diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8f836071a..5163b16c2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,13 +23,13 @@ + @@ -428,9 +429,9 @@ diff --git a/app/src/main/java/io/legado/app/exception/InvalidBooksDirException.kt b/app/src/main/java/io/legado/app/exception/InvalidBooksDirException.kt new file mode 100644 index 000000000..49afbb8b3 --- /dev/null +++ b/app/src/main/java/io/legado/app/exception/InvalidBooksDirException.kt @@ -0,0 +1,3 @@ +package io.legado.app.exception + +class InvalidBooksDirException(msg: String) : NoStackTraceException(msg) diff --git a/app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt b/app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt index f010b30c8..a20eb2bee 100644 --- a/app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt +++ b/app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt @@ -43,7 +43,10 @@ class PermissionActivity : AppCompatActivity() { try { if (Permissions.isManageExternalStorage()) { val settingIntent = - Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) + Intent( + Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, + Uri.parse("package:$packageName") + ) settingActivityResult.launch(settingIntent) } else { throw NoStackTraceException("no MANAGE_ALL_FILES_ACCESS_PERMISSION") diff --git a/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt b/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt index 072d9b4b8..704ca2c5a 100644 --- a/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt +++ b/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt @@ -4,12 +4,14 @@ import android.net.Uri import android.os.Build import android.os.Bundle import androidx.activity.viewModels +import androidx.core.os.postDelayed import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.lifecycleScope import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.constant.AppLog import io.legado.app.databinding.ActivityTranslucenceBinding +import io.legado.app.exception.InvalidBooksDirException import io.legado.app.help.config.AppConfig import io.legado.app.lib.dialogs.alert import io.legado.app.lib.permission.Permissions @@ -17,6 +19,8 @@ import io.legado.app.lib.permission.PermissionsCompat import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.file.HandleFileContract import io.legado.app.utils.FileUtils +import io.legado.app.utils.buildMainHandler +import io.legado.app.utils.canRead import io.legado.app.utils.checkWrite import io.legado.app.utils.getFile import io.legado.app.utils.isContentScheme @@ -43,7 +47,7 @@ class FileAssociationActivity : } ?: let { val storageHelp = String(assets.open("storageHelp.md").readBytes()) toastOnUi(storageHelp) - viewModel.importBook(uri) + importBook(null, uri) } } } @@ -52,6 +56,10 @@ class FileAssociationActivity : override val viewModel by viewModels() + private val handler by lazy { + buildMainHandler() + } + override fun onActivityCreated(savedInstanceState: Bundle?) { binding.rotateLoading.visible() viewModel.importBookLiveData.observe(this) { uri -> @@ -114,21 +122,20 @@ class FileAssociationActivity : noButton { finish() } + onCancelled { + finish() + } } } intent.data?.let { data -> if (data.isContentScheme()) { - viewModel.dispatchIndent(data) + if (data.canRead()) { + viewModel.dispatchIntent(data) + } else { + dispatchWithPermission(data) + } } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { - PermissionsCompat.Builder() - .addPermissions(*Permissions.Group.STORAGE) - .rationale(R.string.tip_perm_request_storage) - .onGranted { - viewModel.dispatchIndent(data) - }.onDenied { - toastOnUi("请求存储权限失败。") - finish() - }.request() + dispatchWithPermission(data) } else { toastOnUi("由于安卓系统限制,请使用系统文件管理重新打开。") finish() @@ -136,6 +143,18 @@ class FileAssociationActivity : } ?: finish() } + private fun dispatchWithPermission(data: Uri) { + PermissionsCompat.Builder() + .addPermissions(*Permissions.Group.STORAGE) + .rationale(R.string.tip_perm_request_storage) + .onGranted { + viewModel.dispatchIntent(data) + }.onDenied { + toastOnUi("请求存储权限失败。") + finish() + }.request() + } + private fun importBook(uri: Uri) { if (uri.isContentScheme()) { val treeUriStr = AppConfig.defaultBookTreeUri @@ -148,19 +167,21 @@ class FileAssociationActivity : importBook(Uri.parse(treeUriStr), uri) } } else { - viewModel.importBook(uri) + importBook(null, uri) } } - private fun importBook(treeUri: Uri, uri: Uri) { + private fun importBook(treeUri: Uri?, uri: Uri) { lifecycleScope.launch { runCatching { withContext(IO) { - if (treeUri.isContentScheme()) { + if (treeUri == null) { + viewModel.importBook(uri) + } else if (treeUri.isContentScheme()) { val treeDoc = DocumentFile.fromTreeUri(this@FileAssociationActivity, treeUri) if (!treeDoc!!.checkWrite()) { - throw SecurityException("请重新设置书籍保存位置\nPermission Denial") + throw InvalidBooksDirException("请重新设置书籍保存位置\nPermission Denial") } readUri(uri) { fileDoc, inputStream -> val name = fileDoc.name @@ -168,7 +189,7 @@ class FileAssociationActivity : if (doc == null || fileDoc.lastModified > doc.lastModified()) { if (doc == null) { doc = treeDoc.createFile(FileUtils.getMimeType(name), name) - ?: throw SecurityException("请重新设置书籍保存位置\nPermission Denial") + ?: throw InvalidBooksDirException("请重新设置书籍保存位置\nPermission Denial") } contentResolver.openOutputStream(doc.uri)!!.use { oStream -> inputStream.copyTo(oStream) @@ -180,7 +201,7 @@ class FileAssociationActivity : } else { val treeFile = File(treeUri.path ?: treeUri.toString()) if (!treeFile.checkWrite()) { - throw SecurityException("请重新设置书籍保存位置\nPermission Denial") + throw InvalidBooksDirException("请重新设置书籍保存位置\nPermission Denial") } readUri(uri) { fileDoc, inputStream -> val name = fileDoc.name @@ -197,7 +218,7 @@ class FileAssociationActivity : } }.onFailure { when (it) { - is SecurityException -> localBookTreeSelect.launch { + is InvalidBooksDirException -> localBookTreeSelect.launch { title = getString(R.string.select_book_folder) mode = HandleFileContract.DIR_SYS } @@ -206,7 +227,9 @@ class FileAssociationActivity : val msg = "导入书籍失败\n${it.localizedMessage}" AppLog.put(msg, it) toastOnUi(msg) - finish() + handler.postDelayed(2000) { + finish() + } } } } diff --git a/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt b/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt index 38157327a..187d47ce6 100644 --- a/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt @@ -15,7 +15,7 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo val openBookLiveData = MutableLiveData() val notSupportedLiveData = MutableLiveData>() - fun dispatchIndent(uri: Uri) { + fun dispatchIntent(uri: Uri) { execute { lateinit var fileName: String //如果是普通的url,需要根据返回的内容判断是什么 diff --git a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt index f6828fe2e..1abec54c6 100644 --- a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt @@ -159,6 +159,9 @@ fun Context.getCompatDrawable(@DrawableRes id: Int): Drawable? = ContextCompat.g fun Context.getCompatColorStateList(@ColorRes id: Int): ColorStateList? = ContextCompat.getColorStateList(this, id) +fun Context.checkSelfUriPermission(uri: Uri, modeFlags: Int): Int = + checkUriPermission(uri, Process.myPid(), Process.myUid(), modeFlags) + fun Context.restart() { val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName) intent?.let { diff --git a/app/src/main/java/io/legado/app/utils/UriExtensions.kt b/app/src/main/java/io/legado/app/utils/UriExtensions.kt index 4f59f7f61..8d246d836 100644 --- a/app/src/main/java/io/legado/app/utils/UriExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/UriExtensions.kt @@ -1,6 +1,8 @@ package io.legado.app.utils import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.ParcelFileDescriptor import androidx.appcompat.app.AppCompatActivity @@ -63,7 +65,7 @@ fun AppCompatActivity.readUri( } } catch (e: Exception) { e.printOnDebug() - toastOnUi(e.localizedMessage ?: "read uri error") + toastOnUi("读取Uri出错\n${e.localizedMessage}") if (e is SecurityException) { throw e } @@ -104,7 +106,7 @@ fun Fragment.readUri(uri: Uri?, success: (fileDoc: FileDoc, inputStream: InputSt } } catch (e: Exception) { e.printOnDebug() - toastOnUi(e.localizedMessage ?: "read uri error") + toastOnUi("读取Uri出错\n${e.localizedMessage}") } } @@ -311,4 +313,11 @@ fun Uri.toRequestBody(contentType: MediaType? = null): RequestBody { } } } -} \ No newline at end of file +} + +fun Uri.canRead(): Boolean { + return appCtx.checkSelfUriPermission( + this, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) == PackageManager.PERMISSION_GRANTED +}