This commit is contained in:
Horis 2023-12-16 16:06:36 +08:00
parent 7a70e10d08
commit cc71a6c22b
7 changed files with 68 additions and 26 deletions

View File

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

View File

@ -0,0 +1,3 @@
package io.legado.app.exception
class InvalidBooksDirException(msg: String) : NoStackTraceException(msg)

View File

@ -43,7 +43,10 @@ class PermissionActivity : AppCompatActivity() {
try { try {
if (Permissions.isManageExternalStorage()) { if (Permissions.isManageExternalStorage()) {
val settingIntent = 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) settingActivityResult.launch(settingIntent)
} else { } else {
throw NoStackTraceException("no MANAGE_ALL_FILES_ACCESS_PERMISSION") throw NoStackTraceException("no MANAGE_ALL_FILES_ACCESS_PERMISSION")

View File

@ -4,12 +4,14 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.os.postDelayed
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.databinding.ActivityTranslucenceBinding import io.legado.app.databinding.ActivityTranslucenceBinding
import io.legado.app.exception.InvalidBooksDirException
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.permission.Permissions 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.book.read.ReadBookActivity
import io.legado.app.ui.file.HandleFileContract import io.legado.app.ui.file.HandleFileContract
import io.legado.app.utils.FileUtils 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.checkWrite
import io.legado.app.utils.getFile import io.legado.app.utils.getFile
import io.legado.app.utils.isContentScheme import io.legado.app.utils.isContentScheme
@ -43,7 +47,7 @@ class FileAssociationActivity :
} ?: let { } ?: let {
val storageHelp = String(assets.open("storageHelp.md").readBytes()) val storageHelp = String(assets.open("storageHelp.md").readBytes())
toastOnUi(storageHelp) toastOnUi(storageHelp)
viewModel.importBook(uri) importBook(null, uri)
} }
} }
} }
@ -52,6 +56,10 @@ class FileAssociationActivity :
override val viewModel by viewModels<FileAssociationViewModel>() override val viewModel by viewModels<FileAssociationViewModel>()
private val handler by lazy {
buildMainHandler()
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
binding.rotateLoading.visible() binding.rotateLoading.visible()
viewModel.importBookLiveData.observe(this) { uri -> viewModel.importBookLiveData.observe(this) { uri ->
@ -114,21 +122,20 @@ class FileAssociationActivity :
noButton { noButton {
finish() finish()
} }
onCancelled {
finish()
}
} }
} }
intent.data?.let { data -> intent.data?.let { data ->
if (data.isContentScheme()) { 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) { } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
PermissionsCompat.Builder() dispatchWithPermission(data)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
viewModel.dispatchIndent(data)
}.onDenied {
toastOnUi("请求存储权限失败。")
finish()
}.request()
} else { } else {
toastOnUi("由于安卓系统限制,请使用系统文件管理重新打开。") toastOnUi("由于安卓系统限制,请使用系统文件管理重新打开。")
finish() finish()
@ -136,6 +143,18 @@ class FileAssociationActivity :
} ?: finish() } ?: 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) { private fun importBook(uri: Uri) {
if (uri.isContentScheme()) { if (uri.isContentScheme()) {
val treeUriStr = AppConfig.defaultBookTreeUri val treeUriStr = AppConfig.defaultBookTreeUri
@ -148,19 +167,21 @@ class FileAssociationActivity :
importBook(Uri.parse(treeUriStr), uri) importBook(Uri.parse(treeUriStr), uri)
} }
} else { } else {
viewModel.importBook(uri) importBook(null, uri)
} }
} }
private fun importBook(treeUri: Uri, uri: Uri) { private fun importBook(treeUri: Uri?, uri: Uri) {
lifecycleScope.launch { lifecycleScope.launch {
runCatching { runCatching {
withContext(IO) { withContext(IO) {
if (treeUri.isContentScheme()) { if (treeUri == null) {
viewModel.importBook(uri)
} else if (treeUri.isContentScheme()) {
val treeDoc = val treeDoc =
DocumentFile.fromTreeUri(this@FileAssociationActivity, treeUri) DocumentFile.fromTreeUri(this@FileAssociationActivity, treeUri)
if (!treeDoc!!.checkWrite()) { if (!treeDoc!!.checkWrite()) {
throw SecurityException("请重新设置书籍保存位置\nPermission Denial") throw InvalidBooksDirException("请重新设置书籍保存位置\nPermission Denial")
} }
readUri(uri) { fileDoc, inputStream -> readUri(uri) { fileDoc, inputStream ->
val name = fileDoc.name val name = fileDoc.name
@ -168,7 +189,7 @@ class FileAssociationActivity :
if (doc == null || fileDoc.lastModified > doc.lastModified()) { if (doc == null || fileDoc.lastModified > doc.lastModified()) {
if (doc == null) { if (doc == null) {
doc = treeDoc.createFile(FileUtils.getMimeType(name), name) doc = treeDoc.createFile(FileUtils.getMimeType(name), name)
?: throw SecurityException("请重新设置书籍保存位置\nPermission Denial") ?: throw InvalidBooksDirException("请重新设置书籍保存位置\nPermission Denial")
} }
contentResolver.openOutputStream(doc.uri)!!.use { oStream -> contentResolver.openOutputStream(doc.uri)!!.use { oStream ->
inputStream.copyTo(oStream) inputStream.copyTo(oStream)
@ -180,7 +201,7 @@ class FileAssociationActivity :
} else { } else {
val treeFile = File(treeUri.path ?: treeUri.toString()) val treeFile = File(treeUri.path ?: treeUri.toString())
if (!treeFile.checkWrite()) { if (!treeFile.checkWrite()) {
throw SecurityException("请重新设置书籍保存位置\nPermission Denial") throw InvalidBooksDirException("请重新设置书籍保存位置\nPermission Denial")
} }
readUri(uri) { fileDoc, inputStream -> readUri(uri) { fileDoc, inputStream ->
val name = fileDoc.name val name = fileDoc.name
@ -197,7 +218,7 @@ class FileAssociationActivity :
} }
}.onFailure { }.onFailure {
when (it) { when (it) {
is SecurityException -> localBookTreeSelect.launch { is InvalidBooksDirException -> localBookTreeSelect.launch {
title = getString(R.string.select_book_folder) title = getString(R.string.select_book_folder)
mode = HandleFileContract.DIR_SYS mode = HandleFileContract.DIR_SYS
} }
@ -206,7 +227,9 @@ class FileAssociationActivity :
val msg = "导入书籍失败\n${it.localizedMessage}" val msg = "导入书籍失败\n${it.localizedMessage}"
AppLog.put(msg, it) AppLog.put(msg, it)
toastOnUi(msg) toastOnUi(msg)
finish() handler.postDelayed(2000) {
finish()
}
} }
} }
} }

View File

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

View File

@ -159,6 +159,9 @@ fun Context.getCompatDrawable(@DrawableRes id: Int): Drawable? = ContextCompat.g
fun Context.getCompatColorStateList(@ColorRes id: Int): ColorStateList? = fun Context.getCompatColorStateList(@ColorRes id: Int): ColorStateList? =
ContextCompat.getColorStateList(this, id) ContextCompat.getColorStateList(this, id)
fun Context.checkSelfUriPermission(uri: Uri, modeFlags: Int): Int =
checkUriPermission(uri, Process.myPid(), Process.myUid(), modeFlags)
fun Context.restart() { fun Context.restart() {
val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName) val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName)
intent?.let { intent?.let {

View File

@ -1,6 +1,8 @@
package io.legado.app.utils package io.legado.app.utils
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -63,7 +65,7 @@ fun AppCompatActivity.readUri(
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printOnDebug() e.printOnDebug()
toastOnUi(e.localizedMessage ?: "read uri error") toastOnUi("读取Uri出错\n${e.localizedMessage}")
if (e is SecurityException) { if (e is SecurityException) {
throw e throw e
} }
@ -104,7 +106,7 @@ fun Fragment.readUri(uri: Uri?, success: (fileDoc: FileDoc, inputStream: InputSt
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printOnDebug() e.printOnDebug()
toastOnUi(e.localizedMessage ?: "read uri error") toastOnUi("读取Uri出错\n${e.localizedMessage}")
} }
} }
@ -312,3 +314,10 @@ fun Uri.toRequestBody(contentType: MediaType? = null): RequestBody {
} }
} }
} }
fun Uri.canRead(): Boolean {
return appCtx.checkSelfUriPermission(
this,
Intent.FLAG_GRANT_READ_URI_PERMISSION
) == PackageManager.PERMISSION_GRANTED
}