diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt index 9b47d0512..9d34011ea 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt @@ -20,8 +20,8 @@ import deckers.thibault.aves.metadata.XMP.getSafeStructField import deckers.thibault.aves.metadata.XMPPropName import deckers.thibault.aves.metadata.metadataextractor.Helper import deckers.thibault.aves.model.FieldMap -import deckers.thibault.aves.model.provider.ContentImageProvider import deckers.thibault.aves.model.provider.ImageProvider +import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider import deckers.thibault.aves.utils.BitmapUtils import deckers.thibault.aves.utils.BitmapUtils.getBytes import deckers.thibault.aves.utils.FileUtils.transferFrom @@ -341,8 +341,14 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler { "mimeType" to mimeType, ) if (isImage(mimeType) || isVideo(mimeType)) { + val provider = getProvider(context, uri) + if (provider == null) { + result.error("copyEmbeddedBytes-provider", "failed to find provider for uri=$uri", null) + return + } + ioScope.launch { - ContentImageProvider().fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback { + provider.fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback { override fun onSuccess(fields: FieldMap) { resultFields.putAll(fields) result.success(resultFields) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaEditHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaEditHandler.kt index 533a078b4..9e40b4199 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaEditHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaEditHandler.kt @@ -55,7 +55,7 @@ class MediaEditHandler(private val contextWrapper: ContextWrapper) : MethodCallH return } - val provider = getProvider(uri) + val provider = getProvider(contextWrapper, uri) if (provider == null) { result.error("captureFrame-provider", "failed to find provider for uri=$uri", null) return diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt index 665d17a17..d0d317c2a 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt @@ -34,7 +34,7 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler return } - val provider = getProvider(uri) + val provider = getProvider(context, uri) if (provider == null) { result.error("getEntry-provider", "failed to find provider for uri=$uri", null) return diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataEditHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataEditHandler.kt index f8915a8f6..9d1704b27 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataEditHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataEditHandler.kt @@ -62,7 +62,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa return } - val provider = getProvider(uri) + val provider = getProvider(contextWrapper, uri) if (provider == null) { result.error("editOrientation-provider", "failed to find provider for uri=$uri", null) return @@ -90,7 +90,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa return } - val provider = getProvider(uri) + val provider = getProvider(contextWrapper, uri) if (provider == null) { result.error("editDate-provider", "failed to find provider for uri=$uri", null) return @@ -117,7 +117,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa return } - val provider = getProvider(uri) + val provider = getProvider(contextWrapper, uri) if (provider == null) { result.error("editMetadata-provider", "failed to find provider for uri=$uri", null) return @@ -142,7 +142,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa return } - val provider = getProvider(uri) + val provider = getProvider(contextWrapper, uri) if (provider == null) { result.error("removeTrailerVideo-provider", "failed to find provider for uri=$uri", null) return @@ -168,7 +168,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa return } - val provider = getProvider(uri) + val provider = getProvider(contextWrapper, uri) if (provider == null) { result.error("removeTypes-provider", "failed to find provider for uri=$uri", null) return diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt index a2a31e122..c6f10000e 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.kt @@ -107,7 +107,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a result["skipped"] = true } else { result["success"] = false - getProvider(uri)?.let { provider -> + getProvider(activity, uri)?.let { provider -> try { provider.delete(activity, uri, path, mimeType) result["success"] = true @@ -142,7 +142,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a // assume same provider for all entries val firstEntry = entryMapList.first() - val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(it) } + val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(activity, it) } if (provider == null) { error("convert-provider", "failed to find provider for entry=$firstEntry", null) return @@ -231,7 +231,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a entriesToNewName[AvesEntry(rawEntry)] = newName } - val byProvider = entriesToNewName.entries.groupBy { kv -> getProvider(kv.key.uri) } + val byProvider = entriesToNewName.entries.groupBy { kv -> getProvider(activity, kv.key.uri) } for ((provider, entryList) in byProvider) { if (provider == null) { error("rename-provider", "failed to find provider for entry=${entryList.firstOrNull()}", null) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt index b82f1cf12..4553b2c4f 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt @@ -10,7 +10,9 @@ import java.io.File import java.io.InputStream import java.text.ParseException import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale +import java.util.TimeZone import java.util.regex.Pattern object Metadata { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/AvesEmbeddedMediaProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/AvesEmbeddedMediaProvider.kt new file mode 100644 index 000000000..49f0a8895 --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/AvesEmbeddedMediaProvider.kt @@ -0,0 +1,18 @@ +package deckers.thibault.aves.model.provider + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import java.util.Locale + +class AvesEmbeddedMediaProvider : UnknownContentProvider() { + override val reliableProviderMimeType: Boolean + get() = true + + companion object { + fun provides(context: Context, uri: Uri): Boolean { + if (uri.scheme?.lowercase(Locale.ROOT) != ContentResolver.SCHEME_CONTENT) return false + return uri.authority == "${context.applicationContext.packageName}.file_provider" + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProviderFactory.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProviderFactory.kt index 63d0afbde..180720652 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProviderFactory.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProviderFactory.kt @@ -1,20 +1,24 @@ package deckers.thibault.aves.model.provider import android.content.ContentResolver +import android.content.Context import android.net.Uri import deckers.thibault.aves.utils.StorageUtils -import java.util.* +import java.util.Locale object ImageProviderFactory { - fun getProvider(uri: Uri): ImageProvider? { + fun getProvider(context: Context, uri: Uri): ImageProvider? { return when (uri.scheme?.lowercase(Locale.ROOT)) { ContentResolver.SCHEME_CONTENT -> { if (StorageUtils.isMediaStoreContentUri(uri)) { MediaStoreImageProvider() + } else if (AvesEmbeddedMediaProvider.provides(context, uri)) { + AvesEmbeddedMediaProvider() } else { - ContentImageProvider() + UnknownContentProvider() } } + ContentResolver.SCHEME_FILE -> FileImageProvider() else -> null } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/UnknownContentProvider.kt similarity index 62% rename from android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt rename to android/app/src/main/kotlin/deckers/thibault/aves/model/provider/UnknownContentProvider.kt index ef2e87410..8fc4e6db0 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/UnknownContentProvider.kt @@ -13,31 +13,35 @@ import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.StorageUtils -internal class ContentImageProvider : ImageProvider() { +open class UnknownContentProvider : ImageProvider() { + open val reliableProviderMimeType: Boolean + get() = false + override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) { - // source MIME type may be incorrect, so we get a second opinion if possible - var extractorMimeType: String? = null - try { - val safeUri = Uri.fromFile(Metadata.createPreviewFile(context, uri)) - StorageUtils.openInputStream(context, safeUri)?.use { input -> - // `metadata-extractor` is the most reliable, except for `tiff` (false positives, false negatives) - // cf https://github.com/drewnoakes/metadata-extractor/issues/296 - Helper.readMimeType(input)?.takeIf { it != MimeTypes.TIFF }?.let { - extractorMimeType = it - if (extractorMimeType != sourceMimeType) { - Log.d(LOG_TAG, "source MIME type is $sourceMimeType but extracted MIME type is $extractorMimeType for uri=$uri") + var mimeType = sourceMimeType + if (sourceMimeType == null || !reliableProviderMimeType) { + // source MIME type may be incorrect, so we get a second opinion if possible + try { + val safeUri = Uri.fromFile(Metadata.createPreviewFile(context, uri)) + StorageUtils.openInputStream(context, safeUri)?.use { input -> + // `metadata-extractor` is the most reliable, except for `tiff` (false positives, false negatives) + // cf https://github.com/drewnoakes/metadata-extractor/issues/296 + Helper.readMimeType(input)?.takeIf { it != MimeTypes.TIFF }?.let { + if (it != sourceMimeType) { + Log.d(LOG_TAG, "source MIME type is $sourceMimeType but extracted MIME type is $it for uri=$uri") + mimeType = it + } } } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e) + } catch (e: NoClassDefFoundError) { + Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e) + } catch (e: AssertionError) { + Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e) } - } catch (e: Exception) { - Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e) - } catch (e: NoClassDefFoundError) { - Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e) - } catch (e: AssertionError) { - Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e) } - val mimeType = extractorMimeType ?: sourceMimeType val fields: FieldMap = hashMapOf( "origin" to SourceEntry.ORIGIN_UNKNOWN_CONTENT, "uri" to uri.toString(), @@ -75,6 +79,6 @@ internal class ContentImageProvider : ImageProvider() { } companion object { - private val LOG_TAG = LogUtils.createTag() + private val LOG_TAG = LogUtils.createTag() } } \ No newline at end of file