Query files from media store

This commit is contained in:
Ming Ming 2022-05-04 22:40:44 +08:00
parent c6e6b99128
commit b71620ee28
3 changed files with 129 additions and 0 deletions

View file

@ -3,6 +3,7 @@
package="com.nkming.nc_photos">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />

View file

@ -1,8 +1,11 @@
package com.nkming.nc_photos.plugin
import android.app.Activity
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
@ -16,6 +19,9 @@ import java.io.File
* Write binary content to a file in the Download directory. Return the Uri to
* the file
* fun saveFileToDownload(content: ByteArray, filename: String, subDir: String?): String
*
* Return files under @c relativePath and its sub dirs
* fun queryFiles(relativePath: String): List<Map>
*/
class MediaStoreChannelHandler(context: Context) :
MethodChannel.MethodCallHandler, ActivityAware {
@ -67,6 +73,14 @@ class MediaStoreChannelHandler(context: Context) :
}
}
"queryFiles" -> {
try {
queryFiles(call.argument("relativePath")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
else -> result.notImplemented()
}
}
@ -102,6 +116,86 @@ class MediaStoreChannelHandler(context: Context) :
}
}
private fun queryFiles(relativePath: String, result: MethodChannel.Result) {
if (!PermissionUtil.hasReadExternalStorage(context)) {
activity?.let { PermissionUtil.requestReadExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
return
}
val pathColumnName: String
val pathArg: String
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pathColumnName = MediaStore.Images.Media.RELATIVE_PATH
pathArg = "${relativePath}/%"
} else {
@Suppress("Deprecation")
pathColumnName = MediaStore.Images.Media.DATA
pathArg = "%/${relativePath}/%"
}
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DISPLAY_NAME,
pathColumnName
)
val selection = StringBuilder().apply {
append("${MediaStore.Images.Media.MIME_TYPE} LIKE ?")
append("AND $pathColumnName LIKE ?")
}.toString()
val selectionArgs = arrayOf("image/%", pathArg)
val files = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, selection, selectionArgs, null
)!!.use {
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val dateModifiedColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED)
val mimeTypeColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)
val dateTakenColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
val displayNameColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val pathColumn = it.getColumnIndexOrThrow(pathColumnName)
val products = mutableListOf<Map<String, Any>>()
while (it.moveToNext()) {
val id = it.getLong(idColumn)
val dateModified = it.getLong(dateModifiedColumn)
val mimeType = it.getString(mimeTypeColumn)
val dateTaken = it.getLong(dateTakenColumn)
val displayName = it.getString(displayNameColumn)
val path = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// RELATIVE_PATH
"${it.getString(pathColumn).trimEnd('/')}/$displayName"
} else {
// DATA
it.getString(pathColumn)
}
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id
)
products.add(buildMap {
put("uri", contentUri.toString())
put("displayName", displayName)
put("path", path)
put("dateModified", dateModified * 1000)
put("mimeType", mimeType)
if (dateTaken != 0L) put("dateTaken", dateTaken)
})
Log.d(
TAG,
"[queryEnhancedPhotos] Found $displayName, path=$path, uri=$contentUri"
)
}
products
}
Log.i(TAG, "[queryEnhancedPhotos] Found ${files.size} files")
result.success(files)
}
private fun inputToUri(fromFile: String): Uri {
val testUri = Uri.parse(fromFile)
return if (testUri.scheme == null) {

View file

@ -4,6 +4,18 @@ import 'package:flutter/services.dart';
import 'package:nc_photos_plugin/src/exception.dart';
import 'package:nc_photos_plugin/src/k.dart' as k;
class MediaStoreQueryResult {
const MediaStoreQueryResult(this.uri, this.displayName, this.path,
this.dateModified, this.mimeType, this.dateTaken);
final String uri;
final String displayName;
final String path;
final int dateModified;
final String? mimeType;
final int? dateTaken;
}
class MediaStore {
static Future<String> saveFileToDownload(
Uint8List content,
@ -51,6 +63,28 @@ class MediaStore {
}
}
/// Return files under [relativePath] and its sub dirs
static Future<List<MediaStoreQueryResult>> queryFiles(
String relativePath) async {
try {
final List results =
await _methodChannel.invokeMethod("queryFiles", <String, dynamic>{
"relativePath": relativePath,
});
return results
.cast<Map>()
.map((e) => MediaStoreQueryResult(e["uri"], e["displayName"],
e["path"], e["dateModified"], e["mimeType"], e["dateTaken"]))
.toList();
} on PlatformException catch (e) {
if (e.code == _exceptionCodePermissionError) {
throw const PermissionException();
} else {
rethrow;
}
}
}
static const _methodChannel = MethodChannel("${k.libId}/media_store_method");
static const _exceptionCodePermissionError = "permissionError";