fixed selecting settings file to import on older devices

This commit is contained in:
Thibault Deckers 2021-12-10 17:45:38 +09:00
parent 8cb88cc12c
commit eb3a8f5626
11 changed files with 40 additions and 33 deletions

View file

@ -155,7 +155,7 @@ class MainActivity : FlutterActivity() {
}
}
@SuppressLint("WrongConstant")
@SuppressLint("WrongConstant", "ObsoleteSdkInt")
private fun onDocumentTreeAccessResult(data: Intent?, resultCode: Int, requestCode: Int) {
val treeUri = data?.data
if (resultCode != RESULT_OK || treeUri == null) {

View file

@ -272,13 +272,13 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
} else {
var mimeType = "*/*"
if (mimeTypes.size == 1) {
// items have the same mime type & subtype
// items have the same MIME type & subtype
mimeType = mimeTypes.first()
} else {
// items have different subtypes
val mimeTypeTypes = mimeTypes.map { it.split("/") }.distinct()
if (mimeTypeTypes.size == 1) {
// items have the same mime type
// items have the same MIME type
mimeType = "${mimeTypeTypes.first()}/*"
}
}

View file

@ -411,8 +411,8 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// File type
for (dir in metadata.getDirectoriesOfType(FileTypeDirectory::class.java)) {
// * `metadata-extractor` sometimes detects the wrong mime type (e.g. `pef` file as `tiff`, `mpeg` as `dvd`)
// * the content resolver / media store sometimes reports the wrong mime type (e.g. `png` file as `jpeg`, `tiff` as `srw`)
// * `metadata-extractor` sometimes detects the wrong MIME type (e.g. `pef` file as `tiff`, `mpeg` as `dvd`)
// * the content resolver / media store sometimes reports the wrong MIME type (e.g. `png` file as `jpeg`, `tiff` as `srw`)
// * `context.getContentResolver().getType()` sometimes returns an incorrect value
// * `MediaMetadataRetriever.setDataSource()` sometimes fails with `status = 0x80000000`
// * file extension is unreliable

View file

@ -1,5 +1,6 @@
package deckers.thibault.aves.channel.streams
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.net.Uri
@ -10,6 +11,7 @@ import android.util.Log
import deckers.thibault.aves.MainActivity
import deckers.thibault.aves.PendingStorageAccessResultHandler
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.PermissionManager
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
@ -90,9 +92,9 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
endOfStream()
}
@SuppressLint("ObsoleteSdkInt")
private fun createFile() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
// TODO TLAD [<=API18] create file
error("createFile-sdk", "unsupported SDK version=${Build.VERSION.SDK_INT}", null)
return
}
@ -133,24 +135,16 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
}
private fun openFile() {
@SuppressLint("ObsoleteSdkInt")
private suspend fun openFile() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
// TODO TLAD [<=API18] open file
error("openFile-sdk", "unsupported SDK version=${Build.VERSION.SDK_INT}", null)
return
}
val mimeType = args["mimeType"] as String?
if (mimeType == null) {
error("openFile-args", "failed because of missing arguments", null)
return
}
val mimeType = args["mimeType"] as String? // optional
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = mimeType
}
MainActivity.pendingStorageAccessResultHandlers[MainActivity.OPEN_FILE_REQUEST] = PendingStorageAccessResultHandler(null, { uri ->
fun onGranted(uri: Uri) {
GlobalScope.launch(Dispatchers.IO) {
activity.contentResolver.openInputStream(uri)?.use { input ->
val buffer = ByteArray(BUFFER_SIZE)
@ -161,11 +155,24 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
endOfStream()
}
}
}, {
}
fun onDenied() {
success(ByteArray(0))
endOfStream()
})
activity.startActivityForResult(intent, MainActivity.OPEN_FILE_REQUEST)
}
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
setTypeAndNormalize(mimeType ?: MimeTypes.ANY)
}
if (intent.resolveActivity(activity.packageManager) != null) {
MainActivity.pendingStorageAccessResultHandlers[MainActivity.OPEN_FILE_REQUEST] = PendingStorageAccessResultHandler(null, ::onGranted, ::onDenied)
activity.startActivityForResult(intent, MainActivity.OPEN_FILE_REQUEST)
} else {
MainActivity.notifyError("failed to resolve activity for intent=$intent")
onDenied()
}
}
override fun onCancel(arguments: Any?) {}

View file

@ -161,7 +161,7 @@ class SourceEntry {
Metadata.openSafeInputStream(context, uri, sourceMimeType, sizeBytes)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input)
// do not switch on specific mime types, as the reported mime type could be wrong
// do not switch on specific MIME types, as the reported MIME type could be wrong
// (e.g. PNG registered as JPG)
if (isVideo) {
for (dir in metadata.getDirectoriesOfType(AviDirectory::class.java)) {

View file

@ -175,7 +175,7 @@ class MediaStoreImageProvider : ImageProvider() {
// but for single items, `contentUri` already contains the ID
val itemUri = if (contentUriContainsId) contentUri else ContentUris.withAppendedId(contentUri, contentId.toLong())
// `mimeType` can be registered as null for file media URIs with unsupported media types (e.g. TIFF on old devices)
// in that case we try to use the mime type provided along the URI
// in that case we try to use the MIME type provided along the URI
val mimeType: String? = cursor.getString(mimeTypeColumn) ?: fileMimeType
val width = cursor.getInt(widthColumn)
val height = cursor.getInt(heightColumn)

View file

@ -3,7 +3,7 @@ package deckers.thibault.aves.utils
import androidx.exifinterface.media.ExifInterface
object MimeTypes {
private const val IMAGE = "image"
const val ANY = "*/*";
// generic raster
const val BMP = "image/bmp"
@ -45,8 +45,6 @@ object MimeTypes {
// vector
const val SVG = "image/svg+xml"
private const val VIDEO = "video"
private const val AVI = "video/avi"
private const val AVI_VND = "video/vnd.avi"
const val DVD = "video/dvd"
@ -58,9 +56,9 @@ object MimeTypes {
private const val OGV = "video/ogg"
private const val WEBM = "video/webm"
fun isImage(mimeType: String?) = mimeType != null && mimeType.startsWith(IMAGE)
fun isImage(mimeType: String?) = mimeType != null && mimeType.startsWith("image")
fun isVideo(mimeType: String?) = mimeType != null && mimeType.startsWith(VIDEO)
fun isVideo(mimeType: String?) = mimeType != null && mimeType.startsWith("video")
fun isHeic(mimeType: String?) = mimeType != null && (mimeType == HEIC || mimeType == HEIF)

View file

@ -144,7 +144,7 @@ class PlatformAndroidAppService implements AndroidAppService {
@override
Future<bool> shareEntries(Iterable<AvesEntry> entries) async {
// loosen mime type to a generic one, so we can share with badly defined apps
// loosen MIME type to a generic one, so we can share with badly defined apps
// e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats
final urisByMimeType = groupBy<AvesEntry, String>(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
try {

View file

@ -133,7 +133,7 @@ class AndroidDebugService {
static Future<Map> getMetadataExtractorSummary(AvesEntry entry) async {
try {
// returns map with the mime type and tag count for each directory found by `metadata-extractor`
// returns 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,

View file

@ -36,7 +36,7 @@ abstract class StorageService {
// return whether operation succeeded (`null` if user cancelled)
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
Future<Uint8List> openFile(String mimeType);
Future<Uint8List> openFile([String? mimeType]);
}
class PlatformStorageService implements StorageService {
@ -231,7 +231,7 @@ class PlatformStorageService implements StorageService {
}
@override
Future<Uint8List> openFile(String mimeType) async {
Future<Uint8List> openFile([String? mimeType]) async {
try {
final completer = Completer<Uint8List>.sync();
final sink = OutputBuffer();

View file

@ -123,7 +123,9 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
}
break;
case SettingsAction.import:
final bytes = await storageService.openFile(MimeTypes.json);
// specifying the JSON MIME type to restrict openable files is correct in theory,
// but older devices (e.g. SM-P580, API 27) that do not recognize JSON files as such would filter them out
final bytes = await storageService.openFile();
if (bytes.isNotEmpty) {
try {
await settings.fromJson(utf8.decode(bytes));