improved error reporting when requesting directory permission

This commit is contained in:
Thibault Deckers 2021-11-08 14:35:45 +09:00
parent 72ecbf3cb5
commit f385f16209
9 changed files with 42 additions and 33 deletions

View file

@ -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)
}
}

View file

@ -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)
}
}
}

View file

@ -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(),

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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()
}
}