diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index 7827394d0..6ee05d8d1 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -41,6 +41,7 @@ class MainActivity : FlutterActivity() { MethodChannel(messenger, ImageFileHandler.CHANNEL).setMethodCallHandler(ImageFileHandler(this)) MethodChannel(messenger, MetadataHandler.CHANNEL).setMethodCallHandler(MetadataHandler(this)) MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) + MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(this)) StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) } StreamsChannel(messenger, ImageOpStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageOpStreamHandler(this, args) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt index 7f942d055..98f03e3fe 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt @@ -29,7 +29,6 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { when (call.method) { "getAppIcon" -> GlobalScope.launch { getAppIcon(call, Coresult(result)) } "getAppNames" -> GlobalScope.launch { getAppNames(Coresult(result)) } - "getEnv" -> result.success(System.getenv()) "edit" -> { val title = call.argument("title") val uri = call.argument("uri")?.let { Uri.parse(it) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt new file mode 100644 index 000000000..b470f4875 --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DebugHandler.kt @@ -0,0 +1,222 @@ +package deckers.thibault.aves.channel.calls + +import android.content.ContentResolver +import android.content.ContentUris +import android.content.Context +import android.database.Cursor +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import android.util.Log +import androidx.exifinterface.media.ExifInterface +import com.drew.imaging.ImageMetadataReader +import com.drew.metadata.file.FileTypeDirectory +import deckers.thibault.aves.metadata.ExifInterfaceHelper +import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper +import deckers.thibault.aves.utils.LogUtils +import deckers.thibault.aves.utils.MimeTypes.isImage +import deckers.thibault.aves.utils.MimeTypes.isSupportedByExifInterface +import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor +import deckers.thibault.aves.utils.MimeTypes.isVideo +import deckers.thibault.aves.utils.StorageUtils +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.io.IOException +import java.util.* + +class DebugHandler(private val context: Context) : MethodCallHandler { + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "getEnv" -> result.success(System.getenv()) + "getBitmapFactoryInfo" -> GlobalScope.launch { getBitmapFactoryInfo(call, Coresult(result)) } + "getContentResolverMetadata" -> GlobalScope.launch { getContentResolverMetadata(call, Coresult(result)) } + "getExifInterfaceMetadata" -> GlobalScope.launch { getExifInterfaceMetadata(call, Coresult(result)) } + "getMediaMetadataRetrieverMetadata" -> GlobalScope.launch { getMediaMetadataRetrieverMetadata(call, Coresult(result)) } + "getMetadataExtractorSummary" -> GlobalScope.launch { getMetadataExtractorSummary(call, Coresult(result)) } + else -> result.notImplemented() + } + } + + private fun getBitmapFactoryInfo(call: MethodCall, result: MethodChannel.Result) { + val uri = call.argument("uri")?.let { Uri.parse(it) } + if (uri == null) { + result.error("getBitmapDecoderInfo-args", "failed because of missing arguments", null) + return + } + + val metadataMap = HashMap() + try { + StorageUtils.openInputStream(context, uri)?.use { input -> + val options = BitmapFactory.Options().apply { + inJustDecodeBounds = true + } + BitmapFactory.decodeStream(input, null, options) + options.outMimeType?.let { metadataMap["MimeType"] = it } + options.outWidth.takeIf { it >= 0 }?.let { metadataMap["Width"] = it.toString() } + options.outHeight.takeIf { it >= 0 }?.let { metadataMap["Height"] = it.toString() } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + options.outColorSpace?.let { metadataMap["ColorSpace"] = it.toString() } + options.outConfig?.let { metadataMap["Config"] = it.toString() } + } + } + } catch (e: IOException) { + // ignore + } + result.success(metadataMap) + } + + private fun getContentResolverMetadata(call: MethodCall, result: MethodChannel.Result) { + val mimeType = call.argument("mimeType") + val uri = call.argument("uri")?.let { Uri.parse(it) } + if (mimeType == null || uri == null) { + result.error("getContentResolverMetadata-args", "failed because of missing arguments", null) + return + } + + var contentUri: Uri = uri + if (uri.scheme == ContentResolver.SCHEME_CONTENT && MediaStore.AUTHORITY.equals(uri.host, ignoreCase = true)) { + try { + val id = ContentUris.parseId(uri) + contentUri = when { + isImage(mimeType) -> ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id) + isVideo(mimeType) -> ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id) + else -> uri + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + contentUri = MediaStore.setRequireOriginal(contentUri) + } + } catch (e: NumberFormatException) { + // ignore + } + } + + val cursor = context.contentResolver.query(contentUri, null, null, null, null) + if (cursor != null && cursor.moveToFirst()) { + val metadataMap = HashMap() + val columnCount = cursor.columnCount + val columnNames = cursor.columnNames + for (i in 0 until columnCount) { + val key = columnNames[i] + try { + metadataMap[key] = when (cursor.getType(i)) { + Cursor.FIELD_TYPE_NULL -> null + Cursor.FIELD_TYPE_INTEGER -> cursor.getLong(i) + Cursor.FIELD_TYPE_FLOAT -> cursor.getFloat(i) + Cursor.FIELD_TYPE_STRING -> cursor.getString(i) + Cursor.FIELD_TYPE_BLOB -> cursor.getBlob(i) + else -> null + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get value for key=$key", e) + } + } + cursor.close() + result.success(metadataMap) + } else { + result.error("getContentResolverMetadata-null", "failed to get cursor for contentUri=$contentUri", null) + } + } + + private fun getExifInterfaceMetadata(call: MethodCall, result: MethodChannel.Result) { + val mimeType = call.argument("mimeType") + val uri = call.argument("uri")?.let { Uri.parse(it) } + val sizeBytes = call.argument("sizeBytes")?.toLong() + if (mimeType == null || uri == null) { + result.error("getExifInterfaceMetadata-args", "failed because of missing arguments", null) + return + } + + val metadataMap = HashMap() + if (isSupportedByExifInterface(mimeType, sizeBytes, strict = false)) { + try { + StorageUtils.openInputStream(context, uri)?.use { input -> + val exif = ExifInterface(input) + for (tag in ExifInterfaceHelper.allTags.keys.filter { exif.hasAttribute(it) }) { + metadataMap[tag] = exif.getAttribute(tag) + } + } + } catch (e: Exception) { + // ExifInterface initialization can fail with a RuntimeException + // caused by an internal MediaMetadataRetriever failure + result.error("getExifInterfaceMetadata-failure", "failed to get exif for uri=$uri", e.message) + return + } + } + result.success(metadataMap) + } + + private fun getMediaMetadataRetrieverMetadata(call: MethodCall, result: MethodChannel.Result) { + val uri = call.argument("uri")?.let { Uri.parse(it) } + if (uri == null) { + result.error("getMediaMetadataRetrieverMetadata-args", "failed because of missing arguments", null) + return + } + + val metadataMap = HashMap() + val retriever = StorageUtils.openMetadataRetriever(context, uri) + if (retriever != null) { + try { + for ((code, name) in MediaMetadataRetrieverHelper.allKeys) { + retriever.extractMetadata(code)?.let { metadataMap[name] = it } + } + } catch (e: Exception) { + // ignore + } finally { + // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs + retriever.release() + } + } + result.success(metadataMap) + } + + private fun getMetadataExtractorSummary(call: MethodCall, result: MethodChannel.Result) { + val mimeType = call.argument("mimeType") + val uri = call.argument("uri")?.let { Uri.parse(it) } + val sizeBytes = call.argument("sizeBytes")?.toLong() + if (mimeType == null || uri == null) { + result.error("getMetadataExtractorSummary-args", "failed because of missing arguments", null) + return + } + + val metadataMap = HashMap() + if (isSupportedByMetadataExtractor(mimeType, sizeBytes)) { + try { + StorageUtils.openInputStream(context, uri)?.use { input -> + val metadata = ImageMetadataReader.readMetadata(input, sizeBytes ?: -1) + metadataMap["mimeType"] = metadata.getDirectoriesOfType(FileTypeDirectory::class.java).joinToString { dir -> + if (dir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)) { + dir.getString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE) + } else "" + } + metadataMap["typeName"] = metadata.getDirectoriesOfType(FileTypeDirectory::class.java).joinToString { dir -> + if (dir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_TYPE_NAME)) { + dir.getString(FileTypeDirectory.TAG_DETECTED_FILE_TYPE_NAME) + } else "" + } + for (dir in metadata.directories) { + val dirName = dir.name ?: "" + var index = 0 + while (metadataMap.containsKey("$dirName ($index)")) index++ + var value = "${dir.tagCount} tags" + dir.parent?.let { value += ", parent: ${it.name}" } + metadataMap["$dirName ($index)"] = value + } + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e) + } catch (e: NoClassDefFoundError) { + Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e) + } + } + result.success(metadataMap) + } + + companion object { + private val LOG_TAG = LogUtils.createTag(DebugHandler::class.java) + const val CHANNEL = "deckers.thibault/aves/debug" + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt index 46056b88c..91e08c0cc 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt @@ -1,14 +1,8 @@ package deckers.thibault.aves.channel.calls -import android.content.ContentResolver -import android.content.ContentUris import android.content.Context -import android.database.Cursor -import android.graphics.BitmapFactory import android.media.MediaMetadataRetriever import android.net.Uri -import android.os.Build -import android.provider.MediaStore import android.util.Log import androidx.exifinterface.media.ExifInterface import com.adobe.internal.xmp.XMPException @@ -25,7 +19,6 @@ import com.drew.metadata.file.FileTypeDirectory import com.drew.metadata.gif.GifAnimationDirectory import com.drew.metadata.webp.WebpDirectory import com.drew.metadata.xmp.XmpDirectory -import deckers.thibault.aves.metadata.ExifInterfaceHelper import deckers.thibault.aves.metadata.ExifInterfaceHelper.describeAll import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDouble @@ -51,7 +44,6 @@ import deckers.thibault.aves.utils.BitmapUtils import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MimeTypes -import deckers.thibault.aves.utils.MimeTypes.isImage import deckers.thibault.aves.utils.MimeTypes.isMultimedia import deckers.thibault.aves.utils.MimeTypes.isSupportedByExifInterface import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor @@ -63,7 +55,6 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import java.io.IOException import java.util.* import kotlin.math.roundToLong @@ -73,11 +64,6 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { "getAllMetadata" -> GlobalScope.launch { getAllMetadata(call, Coresult(result)) } "getCatalogMetadata" -> GlobalScope.launch { getCatalogMetadata(call, Coresult(result)) } "getOverlayMetadata" -> GlobalScope.launch { getOverlayMetadata(call, Coresult(result)) } - "getContentResolverMetadata" -> GlobalScope.launch { getContentResolverMetadata(call, Coresult(result)) } - "getExifInterfaceMetadata" -> GlobalScope.launch { getExifInterfaceMetadata(call, Coresult(result)) } - "getMediaMetadataRetrieverMetadata" -> GlobalScope.launch { getMediaMetadataRetrieverMetadata(call, Coresult(result)) } - "getBitmapFactoryInfo" -> GlobalScope.launch { getBitmapFactoryInfo(call, Coresult(result)) } - "getMetadataExtractorSummary" -> GlobalScope.launch { getMetadataExtractorSummary(call, Coresult(result)) } "getEmbeddedPictures" -> GlobalScope.launch { getEmbeddedPictures(call, Coresult(result)) } "getExifThumbnails" -> GlobalScope.launch { getExifThumbnails(call, Coresult(result)) } "getXmpThumbnails" -> GlobalScope.launch { getXmpThumbnails(call, Coresult(result)) } @@ -446,180 +432,6 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { result.success(metadataMap) } - private fun getContentResolverMetadata(call: MethodCall, result: MethodChannel.Result) { - val mimeType = call.argument("mimeType") - val uri = call.argument("uri")?.let { Uri.parse(it) } - if (mimeType == null || uri == null) { - result.error("getContentResolverMetadata-args", "failed because of missing arguments", null) - return - } - - var contentUri: Uri = uri - if (uri.scheme == ContentResolver.SCHEME_CONTENT && MediaStore.AUTHORITY.equals(uri.host, ignoreCase = true)) { - try { - val id = ContentUris.parseId(uri) - contentUri = when { - isImage(mimeType) -> ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id) - isVideo(mimeType) -> ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id) - else -> uri - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - contentUri = MediaStore.setRequireOriginal(contentUri) - } - } catch (e: NumberFormatException) { - // ignore - } - } - - val cursor = context.contentResolver.query(contentUri, null, null, null, null) - if (cursor != null && cursor.moveToFirst()) { - val metadataMap = HashMap() - val columnCount = cursor.columnCount - val columnNames = cursor.columnNames - for (i in 0 until columnCount) { - val key = columnNames[i] - try { - metadataMap[key] = when (cursor.getType(i)) { - Cursor.FIELD_TYPE_NULL -> null - Cursor.FIELD_TYPE_INTEGER -> cursor.getLong(i) - Cursor.FIELD_TYPE_FLOAT -> cursor.getFloat(i) - Cursor.FIELD_TYPE_STRING -> cursor.getString(i) - Cursor.FIELD_TYPE_BLOB -> cursor.getBlob(i) - else -> null - } - } catch (e: Exception) { - Log.w(LOG_TAG, "failed to get value for key=$key", e) - } - } - cursor.close() - result.success(metadataMap) - } else { - result.error("getContentResolverMetadata-null", "failed to get cursor for contentUri=$contentUri", null) - } - } - - private fun getExifInterfaceMetadata(call: MethodCall, result: MethodChannel.Result) { - val mimeType = call.argument("mimeType") - val uri = call.argument("uri")?.let { Uri.parse(it) } - val sizeBytes = call.argument("sizeBytes")?.toLong() - if (mimeType == null || uri == null) { - result.error("getExifInterfaceMetadata-args", "failed because of missing arguments", null) - return - } - - val metadataMap = HashMap() - if (isSupportedByExifInterface(mimeType, sizeBytes, strict = false)) { - try { - StorageUtils.openInputStream(context, uri)?.use { input -> - val exif = ExifInterface(input) - for (tag in ExifInterfaceHelper.allTags.keys.filter { exif.hasAttribute(it) }) { - metadataMap[tag] = exif.getAttribute(tag) - } - } - } catch (e: Exception) { - // ExifInterface initialization can fail with a RuntimeException - // caused by an internal MediaMetadataRetriever failure - result.error("getExifInterfaceMetadata-failure", "failed to get exif for uri=$uri", e.message) - return - } - } - result.success(metadataMap) - } - - private fun getMediaMetadataRetrieverMetadata(call: MethodCall, result: MethodChannel.Result) { - val uri = call.argument("uri")?.let { Uri.parse(it) } - if (uri == null) { - result.error("getMediaMetadataRetrieverMetadata-args", "failed because of missing arguments", null) - return - } - - val metadataMap = HashMap() - val retriever = StorageUtils.openMetadataRetriever(context, uri) - if (retriever != null) { - try { - for ((code, name) in MediaMetadataRetrieverHelper.allKeys) { - retriever.extractMetadata(code)?.let { metadataMap[name] = it } - } - } catch (e: Exception) { - // ignore - } finally { - // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs - retriever.release() - } - } - result.success(metadataMap) - } - - private fun getBitmapFactoryInfo(call: MethodCall, result: MethodChannel.Result) { - val uri = call.argument("uri")?.let { Uri.parse(it) } - if (uri == null) { - result.error("getBitmapDecoderInfo-args", "failed because of missing arguments", null) - return - } - - val metadataMap = HashMap() - try { - StorageUtils.openInputStream(context, uri)?.use { input -> - val options = BitmapFactory.Options().apply { - inJustDecodeBounds = true - } - BitmapFactory.decodeStream(input, null, options) - options.outMimeType?.let { metadataMap["MimeType"] = it } - options.outWidth.takeIf { it >= 0 }?.let { metadataMap["Width"] = it.toString() } - options.outHeight.takeIf { it >= 0 }?.let { metadataMap["Height"] = it.toString() } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - options.outColorSpace?.let { metadataMap["ColorSpace"] = it.toString() } - options.outConfig?.let { metadataMap["Config"] = it.toString() } - } - } - } catch (e: IOException) { - // ignore - } - result.success(metadataMap) - } - - private fun getMetadataExtractorSummary(call: MethodCall, result: MethodChannel.Result) { - val mimeType = call.argument("mimeType") - val uri = call.argument("uri")?.let { Uri.parse(it) } - val sizeBytes = call.argument("sizeBytes")?.toLong() - if (mimeType == null || uri == null) { - result.error("getMetadataExtractorSummary-args", "failed because of missing arguments", null) - return - } - - val metadataMap = HashMap() - if (isSupportedByMetadataExtractor(mimeType, sizeBytes)) { - try { - StorageUtils.openInputStream(context, uri)?.use { input -> - val metadata = ImageMetadataReader.readMetadata(input, sizeBytes ?: -1) - metadataMap["mimeType"] = metadata.getDirectoriesOfType(FileTypeDirectory::class.java).joinToString { dir -> - if (dir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)) { - dir.getString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE) - } else "" - } - metadataMap["typeName"] = metadata.getDirectoriesOfType(FileTypeDirectory::class.java).joinToString { dir -> - if (dir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_TYPE_NAME)) { - dir.getString(FileTypeDirectory.TAG_DETECTED_FILE_TYPE_NAME) - } else "" - } - for (dir in metadata.directories) { - val dirName = dir.name ?: "" - var index = 0 - while (metadataMap.containsKey("$dirName ($index)")) index++ - var value = "${dir.tagCount} tags" - dir.parent?.let { value += ", parent: ${it.name}" } - metadataMap["$dirName ($index)"] = value - } - } - } catch (e: Exception) { - Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e) - } catch (e: NoClassDefFoundError) { - Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e) - } - } - result.success(metadataMap) - } - private fun getEmbeddedPictures(call: MethodCall, result: MethodChannel.Result) { val uri = call.argument("uri")?.let { Uri.parse(it) } if (uri == null) { diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart index 7549d69af..bed486206 100644 --- a/lib/services/android_app_service.dart +++ b/lib/services/android_app_service.dart @@ -31,16 +31,6 @@ class AndroidAppService { return null; } - static Future getEnv() async { - try { - final result = await platform.invokeMethod('getEnv'); - return result as Map; - } on PlatformException catch (e) { - debugPrint('getEnv failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); - } - return {}; - } - static Future edit(String uri, String mimeType) async { try { return await platform.invokeMethod('edit', { diff --git a/lib/services/android_debug_service.dart b/lib/services/android_debug_service.dart new file mode 100644 index 000000000..301eccc32 --- /dev/null +++ b/lib/services/android_debug_service.dart @@ -0,0 +1,91 @@ +import 'dart:typed_data'; + +import 'package:aves/model/image_entry.dart'; +import 'package:aves/model/image_metadata.dart'; +import 'package:aves/services/service_policy.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +class AndroidDebugService { + static const platform = MethodChannel('deckers.thibault/aves/debug'); + + static Future getEnv() async { + try { + final result = await platform.invokeMethod('getEnv'); + return result as Map; + } on PlatformException catch (e) { + debugPrint('getEnv failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); + } + return {}; + } + + static Future getBitmapFactoryInfo(ImageEntry entry) async { + try { + // return map with all data available when decoding image bounds with `BitmapFactory` + final result = await platform.invokeMethod('getBitmapFactoryInfo', { + 'uri': entry.uri, + }) as Map; + return result; + } on PlatformException catch (e) { + debugPrint('getBitmapFactoryInfo failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return {}; + } + + static Future getContentResolverMetadata(ImageEntry entry) async { + try { + // return map with all data available from the content resolver + final result = await platform.invokeMethod('getContentResolverMetadata', { + 'mimeType': entry.mimeType, + 'uri': entry.uri, + }) as Map; + return result; + } on PlatformException catch (e) { + debugPrint('getContentResolverMetadata failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return {}; + } + + static Future getExifInterfaceMetadata(ImageEntry entry) async { + try { + // return map with all data available from the `ExifInterface` library + final result = await platform.invokeMethod('getExifInterfaceMetadata', { + 'mimeType': entry.mimeType, + 'uri': entry.uri, + 'sizeBytes': entry.sizeBytes, + }) as Map; + return result; + } on PlatformException catch (e) { + debugPrint('getExifInterfaceMetadata failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return {}; + } + + static Future getMediaMetadataRetrieverMetadata(ImageEntry entry) async { + try { + // return map with all data available from `MediaMetadataRetriever` + final result = await platform.invokeMethod('getMediaMetadataRetrieverMetadata', { + 'uri': entry.uri, + }) as Map; + return result; + } on PlatformException catch (e) { + debugPrint('getMediaMetadataRetrieverMetadata failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return {}; + } + + static Future getMetadataExtractorSummary(ImageEntry entry) async { + try { + // return map with the mime type and tag count for each directory found by `metadata-extractor` + final result = await platform.invokeMethod('getMetadataExtractorSummary', { + 'mimeType': entry.mimeType, + 'uri': entry.uri, + 'sizeBytes': entry.sizeBytes, + }) as Map; + return result; + } on PlatformException catch (e) { + debugPrint('getMetadataExtractorSummary failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return {}; + } +} diff --git a/lib/services/metadata_service.dart b/lib/services/metadata_service.dart index f3e7bfb54..1a0cbcee3 100644 --- a/lib/services/metadata_service.dart +++ b/lib/services/metadata_service.dart @@ -80,76 +80,6 @@ class MetadataService { return null; } - static Future getBitmapFactoryInfo(ImageEntry entry) async { - try { - // return map with all data available when decoding image bounds with `BitmapFactory` - final result = await platform.invokeMethod('getBitmapFactoryInfo', { - 'uri': entry.uri, - }) as Map; - return result; - } on PlatformException catch (e) { - debugPrint('getBitmapFactoryInfo failed with code=${e.code}, exception=${e.message}, details=${e.details}'); - } - return {}; - } - - static Future getContentResolverMetadata(ImageEntry entry) async { - try { - // return map with all data available from the content resolver - final result = await platform.invokeMethod('getContentResolverMetadata', { - 'mimeType': entry.mimeType, - 'uri': entry.uri, - }) as Map; - return result; - } on PlatformException catch (e) { - debugPrint('getContentResolverMetadata failed with code=${e.code}, exception=${e.message}, details=${e.details}'); - } - return {}; - } - - static Future getExifInterfaceMetadata(ImageEntry entry) async { - try { - // return map with all data available from the `ExifInterface` library - final result = await platform.invokeMethod('getExifInterfaceMetadata', { - 'mimeType': entry.mimeType, - 'uri': entry.uri, - 'sizeBytes': entry.sizeBytes, - }) as Map; - return result; - } on PlatformException catch (e) { - debugPrint('getExifInterfaceMetadata failed with code=${e.code}, exception=${e.message}, details=${e.details}'); - } - return {}; - } - - static Future getMediaMetadataRetrieverMetadata(ImageEntry entry) async { - try { - // return map with all data available from `MediaMetadataRetriever` - final result = await platform.invokeMethod('getMediaMetadataRetrieverMetadata', { - 'uri': entry.uri, - }) as Map; - return result; - } on PlatformException catch (e) { - debugPrint('getMediaMetadataRetrieverMetadata failed with code=${e.code}, exception=${e.message}, details=${e.details}'); - } - return {}; - } - - static Future getMetadataExtractorSummary(ImageEntry entry) async { - try { - // return map with the mime type and tag count for each directory found by `metadata-extractor` - final result = await platform.invokeMethod('getMetadataExtractorSummary', { - 'mimeType': entry.mimeType, - 'uri': entry.uri, - 'sizeBytes': entry.sizeBytes, - }) as Map; - return result; - } on PlatformException catch (e) { - debugPrint('getMetadataExtractorSummary failed with code=${e.code}, exception=${e.message}, details=${e.details}'); - } - return {}; - } - static Future> getEmbeddedPictures(String uri) async { try { final result = await platform.invokeMethod('getEmbeddedPictures', { diff --git a/lib/widgets/debug/android_env.dart b/lib/widgets/debug/android_env.dart index e590c9cfe..b831572b0 100644 --- a/lib/widgets/debug/android_env.dart +++ b/lib/widgets/debug/android_env.dart @@ -1,6 +1,6 @@ import 'dart:collection'; -import 'package:aves/services/android_app_service.dart'; +import 'package:aves/services/android_debug_service.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/fullscreen/info/common.dart'; import 'package:flutter/material.dart'; @@ -16,7 +16,7 @@ class _DebugAndroidEnvironmentSectionState extends State { } void _loadMetadata() { - _bitmapFactoryLoader = MetadataService.getBitmapFactoryInfo(entry); - _contentResolverMetadataLoader = MetadataService.getContentResolverMetadata(entry); - _exifInterfaceMetadataLoader = MetadataService.getExifInterfaceMetadata(entry); - _mediaMetadataLoader = MetadataService.getMediaMetadataRetrieverMetadata(entry); - _metadataExtractorLoader = MetadataService.getMetadataExtractorSummary(entry); + _bitmapFactoryLoader = AndroidDebugService.getBitmapFactoryInfo(entry); + _contentResolverMetadataLoader = AndroidDebugService.getContentResolverMetadata(entry); + _exifInterfaceMetadataLoader = AndroidDebugService.getExifInterfaceMetadata(entry); + _mediaMetadataLoader = AndroidDebugService.getMediaMetadataRetrieverMetadata(entry); + _metadataExtractorLoader = AndroidDebugService.getMetadataExtractorSummary(entry); setState(() {}); }