safer platform calls

This commit is contained in:
Thibault Deckers 2021-01-28 16:31:37 +09:00
parent c7b6e17a7f
commit 2d893d4415
6 changed files with 76 additions and 47 deletions

View file

@ -12,6 +12,7 @@ import androidx.core.content.FileProvider
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.request.RequestOptions
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.LogUtils
import io.flutter.plugin.common.MethodCall
@ -28,8 +29,8 @@ import kotlin.math.roundToInt
class AppAdapterHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getAppIcon" -> GlobalScope.launch(Dispatchers.IO) { getAppIcon(call, Coresult(result)) }
"getAppNames" -> GlobalScope.launch(Dispatchers.IO) { getAppNames(Coresult(result)) }
"getAppNames" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getAppNames) }
"getAppIcon" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getAppIcon) }
"edit" -> {
val title = call.argument<String>("title")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
@ -61,7 +62,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
}
}
private fun getAppNames(result: MethodChannel.Result) {
private fun getAppNames(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
val nameMap = HashMap<String, String>()
val intent = Intent(Intent.ACTION_MAIN, null)
.addCategory(Intent.CATEGORY_LAUNCHER)

View file

@ -1,9 +1,11 @@
package deckers.thibault.aves.channel.calls
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.reflect.KSuspendFunction2
// ensure `result` methods are called on the main looper thread
class Coresult internal constructor(private val methodResult: MethodChannel.Result) : MethodChannel.Result {
@ -20,4 +22,24 @@ class Coresult internal constructor(private val methodResult: MethodChannel.Resu
override fun notImplemented() {
mainScope.launch { methodResult.notImplemented() }
}
companion object {
fun safe(call: MethodCall, result: MethodChannel.Result, function: (call: MethodCall, result: MethodChannel.Result) -> Unit) {
val res = Coresult(result)
try {
function(call, res)
} catch (e: Exception) {
res.error("safe-exception", e.message, e.stackTraceToString())
}
}
suspend fun safesus(call: MethodCall, result: MethodChannel.Result, function: KSuspendFunction2<MethodCall, MethodChannel.Result, Unit>) {
val res = Coresult(result)
try {
function(call, res)
} catch (e: Exception) {
res.error("safe-exception", e.message, e.stackTraceToString())
}
}
}
}

View file

@ -12,6 +12,7 @@ import android.util.Log
import androidx.exifinterface.media.ExifInterface
import com.drew.imaging.ImageMetadataReader
import com.drew.metadata.file.FileTypeDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.ExifInterfaceHelper
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper
import deckers.thibault.aves.metadata.Metadata
@ -37,12 +38,12 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
when (call.method) {
"getContextDirs" -> result.success(getContextDirs())
"getEnv" -> result.success(System.getenv())
"getBitmapFactoryInfo" -> GlobalScope.launch(Dispatchers.IO) { getBitmapFactoryInfo(call, Coresult(result)) }
"getContentResolverMetadata" -> GlobalScope.launch(Dispatchers.IO) { getContentResolverMetadata(call, Coresult(result)) }
"getExifInterfaceMetadata" -> GlobalScope.launch(Dispatchers.IO) { getExifInterfaceMetadata(call, Coresult(result)) }
"getMediaMetadataRetrieverMetadata" -> GlobalScope.launch(Dispatchers.IO) { getMediaMetadataRetrieverMetadata(call, Coresult(result)) }
"getMetadataExtractorSummary" -> GlobalScope.launch(Dispatchers.IO) { getMetadataExtractorSummary(call, Coresult(result)) }
"getTiffStructure" -> GlobalScope.launch(Dispatchers.IO) { getTiffStructure(call, Coresult(result)) }
"getBitmapFactoryInfo" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getBitmapFactoryInfo) }
"getContentResolverMetadata" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getContentResolverMetadata) }
"getExifInterfaceMetadata" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getExifInterfaceMetadata) }
"getMediaMetadataRetrieverMetadata" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getMediaMetadataRetrieverMetadata) }
"getMetadataExtractorSummary" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getMetadataExtractorSummary) }
"getTiffStructure" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getTiffStructure) }
else -> result.notImplemented()
}
}

View file

@ -5,6 +5,8 @@ import android.graphics.Rect
import android.net.Uri
import android.util.Size
import com.bumptech.glide.Glide
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safesus
import deckers.thibault.aves.channel.calls.fetchers.RegionFetcher
import deckers.thibault.aves.channel.calls.fetchers.ThumbnailFetcher
import deckers.thibault.aves.channel.calls.fetchers.TiffRegionFetcher
@ -29,17 +31,14 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getObsoleteEntries" -> GlobalScope.launch(Dispatchers.IO) { getObsoleteEntries(call, Coresult(result)) }
"getEntry" -> GlobalScope.launch(Dispatchers.IO) { getEntry(call, Coresult(result)) }
"getThumbnail" -> GlobalScope.launch(Dispatchers.IO) { getThumbnail(call, Coresult(result)) }
"getRegion" -> GlobalScope.launch(Dispatchers.IO) { getRegion(call, Coresult(result)) }
"clearSizedThumbnailDiskCache" -> {
GlobalScope.launch(Dispatchers.IO) { Glide.get(activity).clearDiskCache() }
result.success(null)
}
"rename" -> GlobalScope.launch(Dispatchers.IO) { rename(call, Coresult(result)) }
"rotate" -> GlobalScope.launch(Dispatchers.IO) { rotate(call, Coresult(result)) }
"flip" -> GlobalScope.launch(Dispatchers.IO) { flip(call, Coresult(result)) }
"getObsoleteEntries" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getObsoleteEntries) }
"getEntry" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::getEntry) }
"getThumbnail" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getThumbnail) }
"getRegion" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getRegion) }
"clearSizedThumbnailDiskCache" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::clearSizedThumbnailDiskCache) }
"rename" -> GlobalScope.launch(Dispatchers.IO) { safesus(call, result, ::rename) }
"rotate" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::rotate) }
"flip" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::flip) }
else -> result.notImplemented()
}
}
@ -143,6 +142,11 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
})
}
private fun clearSizedThumbnailDiskCache(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
Glide.get(activity).clearDiskCache()
result.success(null)
}
private suspend fun rename(call: MethodCall, result: MethodChannel.Result) {
val entryMap = call.argument<FieldMap>("entry")
val newName = call.argument<String>("newName")

View file

@ -20,6 +20,7 @@ import com.drew.metadata.iptc.IptcDirectory
import com.drew.metadata.mp4.media.Mp4UuidBoxDirectory
import com.drew.metadata.webp.WebpDirectory
import com.drew.metadata.xmp.XmpDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.*
import deckers.thibault.aves.metadata.ExifInterfaceHelper.describeAll
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
@ -69,14 +70,14 @@ import kotlin.math.roundToLong
class MetadataHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getAllMetadata" -> GlobalScope.launch(Dispatchers.IO) { getAllMetadata(call, Coresult(result)) }
"getCatalogMetadata" -> GlobalScope.launch(Dispatchers.IO) { getCatalogMetadata(call, Coresult(result)) }
"getOverlayMetadata" -> GlobalScope.launch(Dispatchers.IO) { getOverlayMetadata(call, Coresult(result)) }
"getMultiPageInfo" -> GlobalScope.launch(Dispatchers.IO) { getMultiPageInfo(call, Coresult(result)) }
"getPanoramaInfo" -> GlobalScope.launch(Dispatchers.IO) { getPanoramaInfo(call, Coresult(result)) }
"getEmbeddedPictures" -> GlobalScope.launch(Dispatchers.IO) { getEmbeddedPictures(call, Coresult(result)) }
"getExifThumbnails" -> GlobalScope.launch(Dispatchers.IO) { getExifThumbnails(call, Coresult(result)) }
"extractXmpDataProp" -> GlobalScope.launch(Dispatchers.IO) { extractXmpDataProp(call, Coresult(result)) }
"getAllMetadata" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getAllMetadata) }
"getCatalogMetadata" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getCatalogMetadata) }
"getOverlayMetadata" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getOverlayMetadata) }
"getMultiPageInfo" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getMultiPageInfo) }
"getPanoramaInfo" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getPanoramaInfo) }
"getEmbeddedPictures" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getEmbeddedPictures) }
"getExifThumbnails" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getExifThumbnails) }
"extractXmpDataProp" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::extractXmpDataProp) }
else -> result.notImplemented()
}
}

View file

@ -5,7 +5,7 @@ import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.storage.StorageManager
import androidx.annotation.RequiresApi
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.utils.PermissionManager
import deckers.thibault.aves.utils.StorageUtils.getVolumePaths
import io.flutter.plugin.common.MethodCall
@ -20,27 +20,18 @@ import java.util.*
class StorageHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getStorageVolumes" -> {
val volumes: List<Map<String, Any>> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
storageVolumes
} else {
// TODO TLAD find alternative for Android <N
emptyList()
}
result.success(volumes)
}
"getFreeSpace" -> getFreeSpace(call, result)
"getGrantedDirectories" -> result.success(ArrayList(PermissionManager.getGrantedDirs(context)))
"getInaccessibleDirectories" -> getInaccessibleDirectories(call, result)
"revokeDirectoryAccess" -> revokeDirectoryAccess(call, result)
"scanFile" -> GlobalScope.launch(Dispatchers.IO) { scanFile(call, Coresult(result)) }
"getStorageVolumes" -> safe(call, result, ::getStorageVolumes)
"getFreeSpace" -> safe(call, result, ::getFreeSpace)
"getGrantedDirectories" -> safe(call, result, ::getGrantedDirectories)
"getInaccessibleDirectories" -> safe(call, result, ::getInaccessibleDirectories)
"revokeDirectoryAccess" -> safe(call, result, ::revokeDirectoryAccess)
"scanFile" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::scanFile) }
else -> result.notImplemented()
}
}
private val storageVolumes: List<Map<String, Any>>
@RequiresApi(api = Build.VERSION_CODES.N)
get() {
private fun getStorageVolumes(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
val volumes: List<Map<String, Any>> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val volumes = ArrayList<Map<String, Any>>()
val sm = context.getSystemService(StorageManager::class.java)
if (sm != null) {
@ -61,8 +52,13 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
}
}
}
return volumes
volumes
} else {
// TODO TLAD find alternative for Android <N
emptyList()
}
result.success(volumes)
}
private fun getFreeSpace(call: MethodCall, result: MethodChannel.Result) {
val path = call.argument<String>("path")
@ -93,6 +89,10 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
}
}
private fun getGrantedDirectories(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
result.success(ArrayList(PermissionManager.getGrantedDirs(context)))
}
private fun getInaccessibleDirectories(call: MethodCall, result: MethodChannel.Result) {
val dirPaths = call.argument<List<String>>("dirPaths")
if (dirPaths == null) {