support for older device/os
This commit is contained in:
parent
fb63b8ca33
commit
8b8056b179
3 changed files with 38 additions and 18 deletions
|
@ -3,9 +3,6 @@ package deckers.thibault.aves.channelhandlers;
|
|||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import deckers.thibault.aves.model.provider.MediaStoreImageProvider;
|
||||
import deckers.thibault.aves.utils.Utils;
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
|
@ -29,9 +26,9 @@ public class MediaStoreStreamHandler implements EventChannel.StreamHandler {
|
|||
|
||||
void fetchAll(Activity activity) {
|
||||
Log.d(LOG_TAG, "fetchAll start");
|
||||
Instant start = Instant.now();
|
||||
// Instant start = Instant.now();
|
||||
new MediaStoreImageProvider().fetchAll(activity, eventSink); // 350ms
|
||||
eventSink.endOfStream();
|
||||
Log.d(LOG_TAG, "fetchAll complete in " + Duration.between(start, Instant.now()).toMillis() + "ms");
|
||||
// Log.d(LOG_TAG, "fetchAll complete in " + Duration.between(start, Instant.now()).toMillis() + "ms");
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package deckers.thibault.aves.model.provider;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentUris;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -21,7 +23,8 @@ import io.flutter.plugin.common.EventChannel;
|
|||
public class MediaStoreImageProvider extends ImageProvider {
|
||||
private static final String LOG_TAG = Utils.createLogTag(MediaStoreImageProvider.class);
|
||||
|
||||
private static final String[] IMAGE_PROJECTION = {
|
||||
|
||||
private static final String[] BASE_PROJECTION = {
|
||||
MediaStore.MediaColumns._ID,
|
||||
MediaStore.MediaColumns.DATA,
|
||||
MediaStore.MediaColumns.MIME_TYPE,
|
||||
|
@ -29,16 +32,28 @@ public class MediaStoreImageProvider extends ImageProvider {
|
|||
MediaStore.MediaColumns.TITLE,
|
||||
MediaStore.MediaColumns.WIDTH,
|
||||
MediaStore.MediaColumns.HEIGHT,
|
||||
MediaStore.MediaColumns.ORIENTATION,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||
MediaStore.MediaColumns.DATE_TAKEN,
|
||||
MediaStore.MediaColumns.BUCKET_DISPLAY_NAME,
|
||||
};
|
||||
|
||||
private static final String[] VIDEO_PROJECTION = Stream.of(IMAGE_PROJECTION, new String[]{
|
||||
MediaStore.MediaColumns.DURATION
|
||||
@SuppressLint("InlinedApi")
|
||||
private static final String[] IMAGE_PROJECTION = Stream.of(BASE_PROJECTION, new String[]{
|
||||
// uses MediaStore.Images.Media instead of MediaStore.MediaColumns for APIs < Q
|
||||
MediaStore.Images.Media.DATE_TAKEN,
|
||||
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
|
||||
MediaStore.Images.Media.ORIENTATION,
|
||||
}).flatMap(Stream::of).toArray(String[]::new);
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private static final String[] VIDEO_PROJECTION = Stream.of(BASE_PROJECTION, new String[]{
|
||||
// uses MediaStore.Video.Media instead of MediaStore.MediaColumns for APIs < Q
|
||||
MediaStore.Video.Media.DATE_TAKEN,
|
||||
MediaStore.Video.Media.BUCKET_DISPLAY_NAME,
|
||||
MediaStore.Video.Media.DURATION,
|
||||
}, (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ?
|
||||
new String[]{
|
||||
MediaStore.Video.Media.ORIENTATION,
|
||||
} : new String[0]).flatMap(Stream::of).toArray(String[]::new);
|
||||
|
||||
public void fetchAll(Activity activity, EventChannel.EventSink entrySink) {
|
||||
NewEntryHandler success = entrySink::success;
|
||||
fetchFrom(activity, success, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION);
|
||||
|
@ -65,6 +80,7 @@ public class MediaStoreImageProvider extends ImageProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private int fetchFrom(final Activity activity, NewEntryHandler newEntryHandler, final Uri contentUri, String[] projection) {
|
||||
String orderBy = MediaStore.MediaColumns.DATE_TAKEN + " DESC";
|
||||
int entryCount = 0;
|
||||
|
@ -80,10 +96,14 @@ public class MediaStoreImageProvider extends ImageProvider {
|
|||
int titleColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE);
|
||||
int widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH);
|
||||
int heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT);
|
||||
int orientationColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION);
|
||||
int dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED);
|
||||
|
||||
int dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_TAKEN);
|
||||
int bucketDisplayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME);
|
||||
|
||||
// image & video for API >= Q, only for images for API < Q
|
||||
int orientationColumn = cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION);
|
||||
|
||||
// video only
|
||||
int durationColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION);
|
||||
|
||||
|
@ -91,6 +111,7 @@ public class MediaStoreImageProvider extends ImageProvider {
|
|||
long contentId = cursor.getLong(idColumn);
|
||||
// this is fine if `contentUri` does not already contain the ID
|
||||
Uri itemUri = ContentUris.withAppendedId(contentUri, contentId);
|
||||
String path = cursor.getString(pathColumn);
|
||||
int width = cursor.getInt(widthColumn);
|
||||
// TODO TLAD sanitize mimeType
|
||||
// problem: some images were added as image/jpeg, but they're actually image/png
|
||||
|
@ -102,12 +123,12 @@ public class MediaStoreImageProvider extends ImageProvider {
|
|||
newEntryHandler.handleEntry(
|
||||
new HashMap<String, Object>() {{
|
||||
put("uri", itemUri.toString());
|
||||
put("path", cursor.getString(pathColumn));
|
||||
put("path", path);
|
||||
put("contentId", contentId);
|
||||
put("mimeType", cursor.getString(mimeTypeColumn));
|
||||
put("width", width);
|
||||
put("height", cursor.getInt(heightColumn));
|
||||
put("orientationDegrees", cursor.getInt(orientationColumn));
|
||||
put("orientationDegrees", orientationColumn != -1 ? cursor.getInt(orientationColumn) : 0);
|
||||
put("sizeBytes", cursor.getLong(sizeColumn));
|
||||
put("title", cursor.getString(titleColumn));
|
||||
put("dateModifiedSecs", cursor.getLong(dateModifiedColumn));
|
||||
|
@ -116,7 +137,8 @@ public class MediaStoreImageProvider extends ImageProvider {
|
|||
put("durationMillis", durationColumn != -1 ? cursor.getLong(durationColumn) : 0);
|
||||
}});
|
||||
entryCount++;
|
||||
// } else {
|
||||
} else {
|
||||
Log.w(LOG_TAG, "failed to get size for uri=" + itemUri + ", path=" + path);
|
||||
// // some images are incorrectly registered in the MediaStore,
|
||||
// // they are valid but miss some attributes, such as width, height, orientation
|
||||
// try {
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:path/path.dart';
|
|||
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
||||
|
||||
class AndroidFileUtils {
|
||||
String externalStorage, dcimPath, downloadPath, picturesPath;
|
||||
String externalStorage, dcimPath, downloadPath, moviesPath, picturesPath;
|
||||
|
||||
static Map appNameMap = {};
|
||||
|
||||
|
@ -15,6 +15,7 @@ class AndroidFileUtils {
|
|||
externalStorage = '/storage/emulated/0';
|
||||
dcimPath = join(externalStorage, 'DCIM');
|
||||
downloadPath = join(externalStorage, 'Download');
|
||||
moviesPath = join(externalStorage, 'Movies');
|
||||
picturesPath = join(externalStorage, 'Pictures');
|
||||
appNameMap = await AndroidAppService.getAppNames()
|
||||
..addAll({'KakaoTalkDownload': 'com.kakao.talk'});
|
||||
|
@ -22,9 +23,9 @@ class AndroidFileUtils {
|
|||
|
||||
bool isCameraPath(String path) => path != null && path.startsWith(dcimPath) && (path.endsWith('Camera') || path.endsWith('100ANDRO'));
|
||||
|
||||
bool isScreenshotsPath(String path) => path != null && path.startsWith(dcimPath) && path.endsWith('Screenshots');
|
||||
bool isScreenshotsPath(String path) => path != null && (path.startsWith(dcimPath) || path.startsWith(picturesPath)) && path.endsWith('Screenshots');
|
||||
|
||||
bool isScreenRecordingsPath(String path) => path != null && path.startsWith(dcimPath) && path.endsWith('Screen recordings');
|
||||
bool isScreenRecordingsPath(String path) => path != null && (path.startsWith(dcimPath) || path.startsWith(moviesPath)) && (path.endsWith('Screen recordings') || path.endsWith('ScreenRecords'));
|
||||
|
||||
bool isDownloadPath(String path) => path == downloadPath;
|
||||
|
||||
|
|
Loading…
Reference in a new issue