moved debug related functions
This commit is contained in:
parent
f205075ac4
commit
4f7287de02
9 changed files with 322 additions and 277 deletions
|
@ -41,6 +41,7 @@ class MainActivity : FlutterActivity() {
|
||||||
MethodChannel(messenger, ImageFileHandler.CHANNEL).setMethodCallHandler(ImageFileHandler(this))
|
MethodChannel(messenger, ImageFileHandler.CHANNEL).setMethodCallHandler(ImageFileHandler(this))
|
||||||
MethodChannel(messenger, MetadataHandler.CHANNEL).setMethodCallHandler(MetadataHandler(this))
|
MethodChannel(messenger, MetadataHandler.CHANNEL).setMethodCallHandler(MetadataHandler(this))
|
||||||
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(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, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) }
|
||||||
StreamsChannel(messenger, ImageOpStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageOpStreamHandler(this, args) }
|
StreamsChannel(messenger, ImageOpStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageOpStreamHandler(this, args) }
|
||||||
|
|
|
@ -29,7 +29,6 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getAppIcon" -> GlobalScope.launch { getAppIcon(call, Coresult(result)) }
|
"getAppIcon" -> GlobalScope.launch { getAppIcon(call, Coresult(result)) }
|
||||||
"getAppNames" -> GlobalScope.launch { getAppNames(Coresult(result)) }
|
"getAppNames" -> GlobalScope.launch { getAppNames(Coresult(result)) }
|
||||||
"getEnv" -> result.success(System.getenv())
|
|
||||||
"edit" -> {
|
"edit" -> {
|
||||||
val title = call.argument<String>("title")
|
val title = call.argument<String>("title")
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
|
|
@ -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<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
if (uri == null) {
|
||||||
|
result.error("getBitmapDecoderInfo-args", "failed because of missing arguments", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val metadataMap = HashMap<String, String>()
|
||||||
|
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<String>("mimeType")
|
||||||
|
val uri = call.argument<String>("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<String, Any?>()
|
||||||
|
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<String>("mimeType")
|
||||||
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
||||||
|
if (mimeType == null || uri == null) {
|
||||||
|
result.error("getExifInterfaceMetadata-args", "failed because of missing arguments", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val metadataMap = HashMap<String, String?>()
|
||||||
|
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<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
if (uri == null) {
|
||||||
|
result.error("getMediaMetadataRetrieverMetadata-args", "failed because of missing arguments", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val metadataMap = HashMap<String, String>()
|
||||||
|
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<String>("mimeType")
|
||||||
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
||||||
|
if (mimeType == null || uri == null) {
|
||||||
|
result.error("getMetadataExtractorSummary-args", "failed because of missing arguments", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val metadataMap = HashMap<String, String>()
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,8 @@
|
||||||
package deckers.thibault.aves.channel.calls
|
package deckers.thibault.aves.channel.calls
|
||||||
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.ContentUris
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.adobe.internal.xmp.XMPException
|
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.gif.GifAnimationDirectory
|
||||||
import com.drew.metadata.webp.WebpDirectory
|
import com.drew.metadata.webp.WebpDirectory
|
||||||
import com.drew.metadata.xmp.XmpDirectory
|
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.describeAll
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDouble
|
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.BitmapUtils.getBytes
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
import deckers.thibault.aves.utils.MimeTypes
|
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.isMultimedia
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isSupportedByExifInterface
|
import deckers.thibault.aves.utils.MimeTypes.isSupportedByExifInterface
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor
|
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 io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
@ -73,11 +64,6 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
||||||
"getAllMetadata" -> GlobalScope.launch { getAllMetadata(call, Coresult(result)) }
|
"getAllMetadata" -> GlobalScope.launch { getAllMetadata(call, Coresult(result)) }
|
||||||
"getCatalogMetadata" -> GlobalScope.launch { getCatalogMetadata(call, Coresult(result)) }
|
"getCatalogMetadata" -> GlobalScope.launch { getCatalogMetadata(call, Coresult(result)) }
|
||||||
"getOverlayMetadata" -> GlobalScope.launch { getOverlayMetadata(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)) }
|
"getEmbeddedPictures" -> GlobalScope.launch { getEmbeddedPictures(call, Coresult(result)) }
|
||||||
"getExifThumbnails" -> GlobalScope.launch { getExifThumbnails(call, Coresult(result)) }
|
"getExifThumbnails" -> GlobalScope.launch { getExifThumbnails(call, Coresult(result)) }
|
||||||
"getXmpThumbnails" -> GlobalScope.launch { getXmpThumbnails(call, Coresult(result)) }
|
"getXmpThumbnails" -> GlobalScope.launch { getXmpThumbnails(call, Coresult(result)) }
|
||||||
|
@ -446,180 +432,6 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
||||||
result.success(metadataMap)
|
result.success(metadataMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getContentResolverMetadata(call: MethodCall, result: MethodChannel.Result) {
|
|
||||||
val mimeType = call.argument<String>("mimeType")
|
|
||||||
val uri = call.argument<String>("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<String, Any?>()
|
|
||||||
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<String>("mimeType")
|
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
|
||||||
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
|
||||||
if (mimeType == null || uri == null) {
|
|
||||||
result.error("getExifInterfaceMetadata-args", "failed because of missing arguments", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val metadataMap = HashMap<String, String?>()
|
|
||||||
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<String>("uri")?.let { Uri.parse(it) }
|
|
||||||
if (uri == null) {
|
|
||||||
result.error("getMediaMetadataRetrieverMetadata-args", "failed because of missing arguments", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val metadataMap = HashMap<String, String>()
|
|
||||||
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<String>("uri")?.let { Uri.parse(it) }
|
|
||||||
if (uri == null) {
|
|
||||||
result.error("getBitmapDecoderInfo-args", "failed because of missing arguments", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val metadataMap = HashMap<String, String>()
|
|
||||||
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<String>("mimeType")
|
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
|
||||||
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
|
||||||
if (mimeType == null || uri == null) {
|
|
||||||
result.error("getMetadataExtractorSummary-args", "failed because of missing arguments", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val metadataMap = HashMap<String, String>()
|
|
||||||
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) {
|
private fun getEmbeddedPictures(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
|
|
|
@ -31,16 +31,6 @@ class AndroidAppService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Map> 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<bool> edit(String uri, String mimeType) async {
|
static Future<bool> edit(String uri, String mimeType) async {
|
||||||
try {
|
try {
|
||||||
return await platform.invokeMethod('edit', <String, dynamic>{
|
return await platform.invokeMethod('edit', <String, dynamic>{
|
||||||
|
|
91
lib/services/android_debug_service.dart
Normal file
91
lib/services/android_debug_service.dart
Normal file
|
@ -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<Map> 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<Map> getBitmapFactoryInfo(ImageEntry entry) async {
|
||||||
|
try {
|
||||||
|
// return map with all data available when decoding image bounds with `BitmapFactory`
|
||||||
|
final result = await platform.invokeMethod('getBitmapFactoryInfo', <String, dynamic>{
|
||||||
|
'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<Map> getContentResolverMetadata(ImageEntry entry) async {
|
||||||
|
try {
|
||||||
|
// return map with all data available from the content resolver
|
||||||
|
final result = await platform.invokeMethod('getContentResolverMetadata', <String, dynamic>{
|
||||||
|
'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<Map> getExifInterfaceMetadata(ImageEntry entry) async {
|
||||||
|
try {
|
||||||
|
// return map with all data available from the `ExifInterface` library
|
||||||
|
final result = await platform.invokeMethod('getExifInterfaceMetadata', <String, dynamic>{
|
||||||
|
'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<Map> getMediaMetadataRetrieverMetadata(ImageEntry entry) async {
|
||||||
|
try {
|
||||||
|
// return map with all data available from `MediaMetadataRetriever`
|
||||||
|
final result = await platform.invokeMethod('getMediaMetadataRetrieverMetadata', <String, dynamic>{
|
||||||
|
'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<Map> 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', <String, dynamic>{
|
||||||
|
'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 {};
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,76 +80,6 @@ class MetadataService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Map> getBitmapFactoryInfo(ImageEntry entry) async {
|
|
||||||
try {
|
|
||||||
// return map with all data available when decoding image bounds with `BitmapFactory`
|
|
||||||
final result = await platform.invokeMethod('getBitmapFactoryInfo', <String, dynamic>{
|
|
||||||
'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<Map> getContentResolverMetadata(ImageEntry entry) async {
|
|
||||||
try {
|
|
||||||
// return map with all data available from the content resolver
|
|
||||||
final result = await platform.invokeMethod('getContentResolverMetadata', <String, dynamic>{
|
|
||||||
'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<Map> getExifInterfaceMetadata(ImageEntry entry) async {
|
|
||||||
try {
|
|
||||||
// return map with all data available from the `ExifInterface` library
|
|
||||||
final result = await platform.invokeMethod('getExifInterfaceMetadata', <String, dynamic>{
|
|
||||||
'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<Map> getMediaMetadataRetrieverMetadata(ImageEntry entry) async {
|
|
||||||
try {
|
|
||||||
// return map with all data available from `MediaMetadataRetriever`
|
|
||||||
final result = await platform.invokeMethod('getMediaMetadataRetrieverMetadata', <String, dynamic>{
|
|
||||||
'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<Map> 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', <String, dynamic>{
|
|
||||||
'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<List<Uint8List>> getEmbeddedPictures(String uri) async {
|
static Future<List<Uint8List>> getEmbeddedPictures(String uri) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getEmbeddedPictures', <String, dynamic>{
|
final result = await platform.invokeMethod('getEmbeddedPictures', <String, dynamic>{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:collection';
|
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/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/common.dart';
|
import 'package:aves/widgets/fullscreen/info/common.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -16,7 +16,7 @@ class _DebugAndroidEnvironmentSectionState extends State<DebugAndroidEnvironment
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loader = AndroidAppService.getEnv();
|
_loader = AndroidDebugService.getEnv();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:collection';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/services/metadata_service.dart';
|
import 'package:aves/services/android_debug_service.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/common.dart';
|
import 'package:aves/widgets/fullscreen/info/common.dart';
|
||||||
|
@ -33,11 +33,11 @@ class _MetadataTabState extends State<MetadataTab> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadMetadata() {
|
void _loadMetadata() {
|
||||||
_bitmapFactoryLoader = MetadataService.getBitmapFactoryInfo(entry);
|
_bitmapFactoryLoader = AndroidDebugService.getBitmapFactoryInfo(entry);
|
||||||
_contentResolverMetadataLoader = MetadataService.getContentResolverMetadata(entry);
|
_contentResolverMetadataLoader = AndroidDebugService.getContentResolverMetadata(entry);
|
||||||
_exifInterfaceMetadataLoader = MetadataService.getExifInterfaceMetadata(entry);
|
_exifInterfaceMetadataLoader = AndroidDebugService.getExifInterfaceMetadata(entry);
|
||||||
_mediaMetadataLoader = MetadataService.getMediaMetadataRetrieverMetadata(entry);
|
_mediaMetadataLoader = AndroidDebugService.getMediaMetadataRetrieverMetadata(entry);
|
||||||
_metadataExtractorLoader = MetadataService.getMetadataExtractorSummary(entry);
|
_metadataExtractorLoader = AndroidDebugService.getMetadataExtractorSummary(entry);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue