improved error reporting when requesting directory permission
This commit is contained in:
parent
72ecbf3cb5
commit
f385f16209
9 changed files with 42 additions and 33 deletions
|
@ -17,7 +17,6 @@ import deckers.thibault.aves.channel.calls.MediaStoreHandler
|
|||
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
|
||||
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
|
||||
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
|
||||
import deckers.thibault.aves.utils.ContextUtils.runOnUiThread
|
||||
import deckers.thibault.aves.utils.FlutterUtils
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
|
@ -155,12 +154,11 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() {
|
|||
|
||||
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
val context = this@AnalysisService
|
||||
val data = msg.data
|
||||
when (data.getString(KEY_COMMAND)) {
|
||||
COMMAND_START -> {
|
||||
runBlocking {
|
||||
context.runOnUiThread {
|
||||
FlutterUtils.runOnUiThread {
|
||||
val contentIds = data.get(KEY_CONTENT_IDS)?.takeIf { it is IntArray }?.let { (it as IntArray).toList() }
|
||||
backgroundChannel?.invokeMethod(
|
||||
"start", hashMapOf(
|
||||
|
@ -174,7 +172,7 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() {
|
|||
COMMAND_STOP -> {
|
||||
// unconditionally stop the service
|
||||
runBlocking {
|
||||
context.runOnUiThread {
|
||||
FlutterUtils.runOnUiThread {
|
||||
backgroundChannel?.invokeMethod("stop", null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -311,7 +311,10 @@ class MainActivity : FlutterActivity() {
|
|||
|
||||
var errorStreamHandler: ErrorStreamHandler? = null
|
||||
|
||||
fun notifyError(error: String) = errorStreamHandler?.notifyError(error)
|
||||
suspend fun notifyError(error: String) {
|
||||
Log.e(LOG_TAG, "notifyError error=$error")
|
||||
errorStreamHandler?.notifyError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import android.text.format.DateFormat
|
|||
import android.util.Log
|
||||
import deckers.thibault.aves.model.FieldMap
|
||||
import deckers.thibault.aves.utils.ContextUtils.resourceUri
|
||||
import deckers.thibault.aves.utils.ContextUtils.runOnUiThread
|
||||
import deckers.thibault.aves.utils.FlutterUtils
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
|
@ -80,7 +79,7 @@ class SearchSuggestionsProvider : MethodChannel.MethodCallHandler, ContentProvid
|
|||
|
||||
return suspendCoroutine { cont ->
|
||||
GlobalScope.launch {
|
||||
context.runOnUiThread {
|
||||
FlutterUtils.runOnUiThread {
|
||||
backgroundChannel.invokeMethod("getSuggestions", hashMapOf(
|
||||
"query" to query,
|
||||
"locale" to Locale.getDefault().toString(),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package deckers.thibault.aves.channel.streams
|
||||
|
||||
import deckers.thibault.aves.utils.FlutterUtils
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.EventChannel.EventSink
|
||||
|
||||
|
@ -15,9 +16,11 @@ class ErrorStreamHandler : EventChannel.StreamHandler {
|
|||
|
||||
override fun onCancel(arguments: Any?) {}
|
||||
|
||||
fun notifyError(error: String) {
|
||||
suspend fun notifyError(error: String) {
|
||||
FlutterUtils.runOnUiThread {
|
||||
eventSink?.success(error)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL = "deckers.thibault/aves/error"
|
||||
|
|
|
@ -49,7 +49,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
|
|||
}
|
||||
}
|
||||
|
||||
private fun requestDirectoryAccess() {
|
||||
private suspend fun requestDirectoryAccess() {
|
||||
val path = args["path"] as String?
|
||||
if (path == null) {
|
||||
error("requestDirectoryAccess-args", "failed because of missing arguments", null)
|
||||
|
|
|
@ -323,6 +323,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
// with a path, and retrieve its content URI, but:
|
||||
// - the Media Store isolates content by storage volume (e.g. `MediaStore.Images.Media.getContentUri(volumeName)`)
|
||||
// - the volume name should be lower case, not exactly as the `StorageVolume` UUID
|
||||
// cf new method in API 30 `StorageVolume.getMediaStoreVolumeName()`
|
||||
// - inserting on a removable volume works on API 29, but not on API 25 nor 26 (on which API/devices does it work?)
|
||||
// - there is no documentation regarding support for usage with removable storage
|
||||
// - the Media Store only allows inserting in specific primary directories ("DCIM", "Pictures") when using scoped storage
|
||||
|
|
|
@ -5,10 +5,6 @@ import android.app.Service
|
|||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object ContextUtils {
|
||||
fun Context.resourceUri(resourceId: Int): Uri = with(resources) {
|
||||
|
@ -20,19 +16,6 @@ object ContextUtils {
|
|||
.build()
|
||||
}
|
||||
|
||||
suspend fun Context.runOnUiThread(r: Runnable) {
|
||||
if (Looper.myLooper() != mainLooper) {
|
||||
suspendCoroutine<Boolean> { cont ->
|
||||
Handler(mainLooper).post {
|
||||
r.run()
|
||||
cont.resume(true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isMyServiceRunning(serviceClass: Class<out Service>): Boolean {
|
||||
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager?
|
||||
am ?: return false
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package deckers.thibault.aves.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import deckers.thibault.aves.utils.ContextUtils.runOnUiThread
|
||||
import io.flutter.FlutterInjector
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.dart.DartExecutor
|
||||
import io.flutter.embedding.engine.loader.FlutterLoader
|
||||
import io.flutter.view.FlutterCallbackInformation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object FlutterUtils {
|
||||
private val LOG_TAG = LogUtils.createTag<FlutterUtils>()
|
||||
|
@ -20,7 +23,7 @@ object FlutterUtils {
|
|||
}
|
||||
|
||||
lateinit var flutterLoader: FlutterLoader
|
||||
context.runOnUiThread {
|
||||
FlutterUtils.runOnUiThread {
|
||||
// initialization must happen on the main thread
|
||||
flutterLoader = FlutterInjector.instance().flutterLoader().apply {
|
||||
startInitialization(context)
|
||||
|
@ -39,11 +42,25 @@ object FlutterUtils {
|
|||
flutterLoader.findAppBundlePath(),
|
||||
callbackInfo
|
||||
)
|
||||
context.runOnUiThread {
|
||||
runOnUiThread {
|
||||
val engine = FlutterEngine(context).apply {
|
||||
dartExecutor.executeDartCallback(args)
|
||||
}
|
||||
engineSetter(engine)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun runOnUiThread(r: Runnable) {
|
||||
val mainLooper = Looper.getMainLooper()
|
||||
if (Looper.myLooper() != mainLooper) {
|
||||
suspendCoroutine<Boolean> { cont ->
|
||||
Handler(mainLooper).post {
|
||||
r.run()
|
||||
cont.resume(true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.run()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,13 +31,18 @@ object PermissionManager {
|
|||
)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
fun requestDirectoryAccess(activity: Activity, path: String, onGranted: (uri: Uri) -> Unit, onDenied: () -> Unit) {
|
||||
suspend fun requestDirectoryAccess(activity: Activity, path: String, onGranted: (uri: Uri) -> Unit, onDenied: () -> Unit) {
|
||||
Log.i(LOG_TAG, "request user to select and grant access permission to path=$path")
|
||||
|
||||
var intent: Intent? = null
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val sm = activity.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
|
||||
intent = sm?.getStorageVolume(File(path))?.createOpenDocumentTreeIntent()
|
||||
val storageVolume = sm?.getStorageVolume(File(path))
|
||||
if (storageVolume != null) {
|
||||
intent = storageVolume.createOpenDocumentTreeIntent()
|
||||
} else {
|
||||
MainActivity.notifyError("failed to get storage volume for path=$path on volumes=${sm?.storageVolumes?.joinToString(", ")}")
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to basic open document tree intent
|
||||
|
@ -49,7 +54,7 @@ object PermissionManager {
|
|||
MainActivity.pendingStorageAccessResultHandlers[MainActivity.DOCUMENT_TREE_ACCESS_REQUEST] = PendingStorageAccessResultHandler(path, onGranted, onDenied)
|
||||
activity.startActivityForResult(intent, MainActivity.DOCUMENT_TREE_ACCESS_REQUEST)
|
||||
} else {
|
||||
Log.e(LOG_TAG, "failed to resolve activity for intent=$intent")
|
||||
MainActivity.notifyError("failed to resolve activity for intent=$intent")
|
||||
onDenied()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue