Horis 2 лет назад
Родитель
Сommit
6d2ce8f80c

+ 3 - 2
app/src/main/AndroidManifest.xml

@@ -23,13 +23,13 @@
     <application
         android:name=".App"
         android:allowBackup="true"
+        android:enableOnBackInvokedCallback="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:networkSecurityConfig="@xml/network_security_config"
         android:requestLegacyExternalStorage="true"
         android:supportsRtl="true"
         android:theme="@style/AppTheme.Light"
-        android:enableOnBackInvokedCallback="true"
         tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute">
         <!-- 主入口 -->
         <activity
@@ -367,6 +367,7 @@
                 <data android:mimeType="application/epub+zip" />
                 <!-- pdf -->
                 <data android:mimeType="application/pdf" />
+                <data android:mimeType="application/octet-stream" />
             </intent-filter>
             <!-- Works when an app doesn't know the media type, e.g. Dropbox -->
             <intent-filter>
@@ -428,9 +429,9 @@
         <service
             android:name=".service.WebTileService"
             android:exported="true"
+            android:foregroundServiceType="dataSync"
             android:icon="@drawable/ic_web_service_noti"
             android:label="legado Web Service"
-            android:foregroundServiceType="dataSync"
             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
                 <action android:name="android.service.quicksettings.action.QS_TILE" />

+ 3 - 0
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)

+ 4 - 1
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")

+ 42 - 19
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<FileAssociationViewModel>()
 
+    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()
+                        }
                     }
                 }
             }

+ 1 - 1
app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt

@@ -15,7 +15,7 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo
     val openBookLiveData = MutableLiveData<String>()
     val notSupportedLiveData = MutableLiveData<Pair<Uri, String>>()
 
-    fun dispatchIndent(uri: Uri) {
+    fun dispatchIntent(uri: Uri) {
         execute {
             lateinit var fileName: String
             //如果是普通的url,需要根据返回的内容判断是什么

+ 3 - 0
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 {

+ 12 - 3
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 {
             }
         }
     }
-}
+}
+
+fun Uri.canRead(): Boolean {
+    return appCtx.checkSelfUriPermission(
+        this,
+        Intent.FLAG_GRANT_READ_URI_PERMISSION
+    ) == PackageManager.PERMISSION_GRANTED
+}