fix android method channel

This commit is contained in:
2024-11-16 19:13:19 +08:00
parent 30a1c806cd
commit 0ee99a8760

View File

@@ -1,19 +1,27 @@
package com.github.wgh136.venera package com.github.wgh136.venera
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.view.KeyEvent
import android.Manifest
import android.os.Environment import android.os.Environment
import android.provider.DocumentsContract
import android.provider.Settings import android.provider.Settings
import android.view.KeyEvent
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import dev.flutter.packages.file_selector_android.FileUtils
import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.EventChannel
@@ -21,17 +29,43 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant import io.flutter.plugins.GeneratedPluginRegistrant
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.lang.Exception import java.util.concurrent.atomic.AtomicInteger
class MainActivity : FlutterFragmentActivity() { class MainActivity : FlutterFragmentActivity() {
var volumeListen = VolumeListen() var volumeListen = VolumeListen()
var listening = false var listening = false
private lateinit var result: MethodChannel.Result
private val storageRequestCode = 0x10 private val storageRequestCode = 0x10
private var storagePermissionRequest: ((Boolean) -> Unit)? = null private var storagePermissionRequest: ((Boolean) -> Unit)? = null
private val nextLocalRequestCode = AtomicInteger()
private fun <I, O> startContractForResult(
contract: ActivityResultContract<I, O>,
input: I,
callback: ActivityResultCallback<O>
) {
val key = "activity_rq_for_result#${nextLocalRequestCode.getAndIncrement()}"
val registry = activityResultRegistry
var launcher: ActivityResultLauncher<I>? = null
val observer = object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (Lifecycle.Event.ON_DESTROY == event) {
launcher?.unregister()
lifecycle.removeObserver(this)
}
}
}
lifecycle.addObserver(observer)
val newCallback = ActivityResultCallback<O> {
launcher?.unregister()
lifecycle.removeObserver(observer)
callback.onActivityResult(it)
}
launcher = registry.register(key, contract, newCallback)
launcher.launch(input)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine) GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel( MethodChannel(
@@ -53,24 +87,21 @@ class MainActivity : FlutterFragmentActivity() {
"getDirectoryPath" -> { "getDirectoryPath" -> {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
val launcher = startContractForResult(ActivityResultContracts.StartActivityForResult(), intent) { activityResult ->
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
if (activityResult.resultCode != Activity.RESULT_OK) { if (activityResult.resultCode != Activity.RESULT_OK) {
result.success(null) res.success(null)
return@startContractForResult
} }
val pickedDirectoryUri = activityResult.data?.data val pickedDirectoryUri = activityResult.data?.data
if (pickedDirectoryUri == null) if (pickedDirectoryUri == null)
result.success(null) res.success(null)
else else
Thread {
try { try {
result.success(onPickedDirectory(pickedDirectoryUri)) res.success(onPickedDirectory(pickedDirectoryUri))
} catch (e: Exception) { } catch (e: Exception) {
result.error("Failed to Copy Files", e.toString(), null) res.error("Failed to Copy Files", e.toString(), null)
} }
}.start()
} }
launcher.launch(intent)
} }
else -> res.notImplemented() else -> res.notImplemented()
@@ -104,8 +135,7 @@ class MainActivity : FlutterFragmentActivity() {
val selectFileChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "venera/select_file") val selectFileChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "venera/select_file")
selectFileChannel.setMethodCallHandler { _, res -> selectFileChannel.setMethodCallHandler { _, res ->
openFile() openFile(res)
result = res
} }
} }
@@ -138,13 +168,24 @@ class MainActivity : FlutterFragmentActivity() {
/// copy the directory to tmp directory, return copied directory /// copy the directory to tmp directory, return copied directory
private fun onPickedDirectory(uri: Uri): String { private fun onPickedDirectory(uri: Uri): String {
if (!hasStoragePermission()) {
// dart:io cannot access the directory without permission.
// so we need to copy the directory to cache directory
val contentResolver = contentResolver val contentResolver = contentResolver
var tmp = cacheDir var tmp = cacheDir
tmp = File(tmp, "getDirectoryPathTemp") tmp = File(tmp, "getDirectoryPathTemp")
tmp.mkdir() tmp.mkdir()
Thread {
copyDirectory(contentResolver, uri, tmp) copyDirectory(contentResolver, uri, tmp)
}.start()
return tmp.absolutePath return tmp.absolutePath
} else {
val docId = DocumentsContract.getTreeDocumentId(uri)
val split: Array<String?> = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
return if ((split.size >= 2) && (split[1] != null)) split[1]!!
else File.separator
}
} }
private fun copyDirectory(resolver: ContentResolver, srcUri: Uri, destDir: File) { private fun copyDirectory(resolver: ContentResolver, srcUri: Uri, destDir: File) {
@@ -165,6 +206,20 @@ class MainActivity : FlutterFragmentActivity() {
} }
} }
private fun hasStoragePermission(): Boolean {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
} else {
Environment.isExternalStorageManager()
}
}
private fun requestStoragePermission(result: (Boolean) -> Unit) { private fun requestStoragePermission(result: (Boolean) -> Unit) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
val readPermission = ContextCompat.checkSelfPermission( val readPermission = ContextCompat.checkSelfPermission(
@@ -196,10 +251,9 @@ class MainActivity : FlutterFragmentActivity() {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.addCategory("android.intent.category.DEFAULT") intent.addCategory("android.intent.category.DEFAULT")
intent.data = Uri.parse("package:$packageName") intent.data = Uri.parse("package:$packageName")
val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ -> startContractForResult(ActivityResultContracts.StartActivityForResult(), intent){ _ ->
result(Environment.isExternalStorageManager()) result(Environment.isExternalStorageManager())
} }
launcher.launch(intent)
} catch (e: Exception) { } catch (e: Exception) {
result(false) result(false)
} }
@@ -223,29 +277,40 @@ class MainActivity : FlutterFragmentActivity() {
} }
} }
private fun openFile() { private fun openFile(result: MethodChannel.Result) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*" intent.type = "*/*"
val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult -> startContractForResult(ActivityResultContracts.StartActivityForResult(), intent){ activityResult ->
if (activityResult.resultCode != Activity.RESULT_OK) { if (activityResult.resultCode != Activity.RESULT_OK) {
result.success(null) result.success(null)
return@startContractForResult
} }
val uri = activityResult.data?.data val uri = activityResult.data?.data
if (uri == null) { if (uri == null) {
result.success(null) result.success(null)
return@registerForActivityResult return@startContractForResult
} }
val contentResolver = contentResolver val contentResolver = contentResolver
val file = DocumentFile.fromSingleUri(this, uri) val file = DocumentFile.fromSingleUri(this, uri)
if (file == null) { if (file == null) {
result.success(null) result.success(null)
return@registerForActivityResult return@startContractForResult
} }
val fileName = file.name val fileName = file.name
if (fileName == null) { if (fileName == null) {
result.success(null) result.success(null)
return@registerForActivityResult return@startContractForResult
}
if(hasStoragePermission()) {
try {
val filePath = FileUtils.getPathFromUri(this, uri)
result.success(filePath)
return@startContractForResult
}
catch (e: Exception) {
// ignore
}
} }
// copy file to cache directory // copy file to cache directory
val cacheDir = cacheDir val cacheDir = cacheDir
@@ -253,7 +318,7 @@ class MainActivity : FlutterFragmentActivity() {
val inputStream = contentResolver.openInputStream(uri) val inputStream = contentResolver.openInputStream(uri)
if (inputStream == null) { if (inputStream == null) {
result.success(null) result.success(null)
return@registerForActivityResult return@startContractForResult
} }
val outputStream = FileOutputStream(newFile) val outputStream = FileOutputStream(newFile)
inputStream.copyTo(outputStream) inputStream.copyTo(outputStream)
@@ -262,7 +327,6 @@ class MainActivity : FlutterFragmentActivity() {
// send file path to flutter // send file path to flutter
result.success(newFile.absolutePath) result.success(newFile.absolutePath)
} }
launcher.launch(intent)
} }
} }