From cbacb923e7aab0bdfb6c2b96d274e49c9ef9dd50 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 11 Jun 2020 14:28:09 +0900 Subject: [PATCH] thumbnail/app icon: use display metrics in Android instead of devicePixelRatio in Flutter --- .../channelhandlers/AppAdapterHandler.java | 8 +++++-- .../channelhandlers/ImageFileHandler.java | 23 +++++++++++++++---- lib/services/android_app_service.dart | 4 ++-- lib/services/image_file_service.dart | 9 ++++---- .../app_icon_image_provider.dart | 12 +++++----- .../image_providers/thumbnail_provider.dart | 6 +---- 6 files changed, 39 insertions(+), 23 deletions(-) diff --git a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/AppAdapterHandler.java b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/AppAdapterHandler.java index a3476354d..0bf3eb26b 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/AppAdapterHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/AppAdapterHandler.java @@ -121,12 +121,16 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler { private void getAppIcon(MethodCall call, MethodChannel.Result result) { String packageName = call.argument("packageName"); - Integer size = call.argument("size"); - if (packageName == null || size == null) { + Double sizeDip = call.argument("sizeDip"); + if (packageName == null || sizeDip == null) { result.error("getAppIcon-args", "failed because of missing arguments", null); return; } + // convert DIP to physical pixels here, instead of using `devicePixelRatio` in Flutter + float density = context.getResources().getDisplayMetrics().density; + int size = (int) Math.round(sizeDip * density); + byte[] data = null; try { int iconResourceId = context.getPackageManager().getApplicationInfo(packageName, 0).icon; diff --git a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/ImageFileHandler.java b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/ImageFileHandler.java index 3240e3d4a..8a529cf98 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/ImageFileHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/ImageFileHandler.java @@ -21,6 +21,7 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler { public static final String CHANNEL = "deckers.thibault/aves/image"; private Activity activity; + private float density; private MediaStoreStreamHandler mediaStoreStreamHandler; public ImageFileHandler(Activity activity, MediaStoreStreamHandler mediaStoreStreamHandler) { @@ -28,6 +29,13 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler { this.mediaStoreStreamHandler = mediaStoreStreamHandler; } + public float getDensity() { + if (density == 0) { + density = activity.getResources().getDisplayMetrics().density; + } + return density; + } + @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { switch (call.method) { @@ -63,13 +71,20 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler { private void getThumbnail(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { Map entryMap = call.argument("entry"); - Integer width = call.argument("width"); - Integer height = call.argument("height"); - Integer defaultSize = call.argument("defaultSize"); - if (entryMap == null || defaultSize == null) { + Double widthDip = call.argument("widthDip"); + Double heightDip = call.argument("heightDip"); + Double defaultSizeDip = call.argument("defaultSizeDip"); + + if (entryMap == null || widthDip == null || heightDip == null || defaultSizeDip == null) { result.error("getThumbnail-args", "failed because of missing arguments", null); return; } + // convert DIP to physical pixels here, instead of using `devicePixelRatio` in Flutter + float density = getDensity(); + int width = (int) Math.round(widthDip * density); + int height = (int) Math.round(heightDip * density); + int defaultSize = (int) Math.round(defaultSizeDip * density); + ImageEntry entry = new ImageEntry(entryMap); new ImageDecodeTask(activity).execute(new ImageDecodeTask.Params(entry, width, height, defaultSize, result)); } diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart index e2c6a83e0..886149d02 100644 --- a/lib/services/android_app_service.dart +++ b/lib/services/android_app_service.dart @@ -16,11 +16,11 @@ class AndroidAppService { return {}; } - static Future getAppIcon(String packageName, int size) async { + static Future getAppIcon(String packageName, double size) async { try { final result = await platform.invokeMethod('getAppIcon', { 'packageName': packageName, - 'size': size, + 'sizeDip': size, }); return result as Uint8List; } on PlatformException catch (e) { diff --git a/lib/services/image_file_service.dart b/lib/services/image_file_service.dart index dfc6228c3..5cbb68a29 100644 --- a/lib/services/image_file_service.dart +++ b/lib/services/image_file_service.dart @@ -14,6 +14,7 @@ class ImageFileService { static const platform = MethodChannel('deckers.thibault/aves/image'); static final StreamsChannel byteChannel = StreamsChannel('deckers.thibault/aves/imagebytestream'); static final StreamsChannel opChannel = StreamsChannel('deckers.thibault/aves/imageopstream'); + static const double thumbnailDefaultSize = 64.0; static Future getImageEntries(SortFactor sort, GroupFactor group) async { try { @@ -76,15 +77,15 @@ class ImageFileService { return Future.sync(() => Uint8List(0)); } - static Future getThumbnail(ImageEntry entry, int width, int height, {Object taskKey, int priority}) { + static Future getThumbnail(ImageEntry entry, double width, double height, {Object taskKey, int priority}) { return servicePolicy.call( () async { try { final result = await platform.invokeMethod('getThumbnail', { 'entry': entry.toMap(), - 'width': width, - 'height': height, - 'defaultSize': 256, + 'widthDip': width, + 'heightDip': height, + 'defaultSizeDip': thumbnailDefaultSize, }); return result as Uint8List; } on PlatformException catch (e) { diff --git a/lib/widgets/common/image_providers/app_icon_image_provider.dart b/lib/widgets/common/image_providers/app_icon_image_provider.dart index 6e0e77437..ac7a5ace4 100644 --- a/lib/widgets/common/image_providers/app_icon_image_provider.dart +++ b/lib/widgets/common/image_providers/app_icon_image_provider.dart @@ -21,7 +21,7 @@ class AppIconImage extends ImageProvider { Future obtainKey(ImageConfiguration configuration) { return SynchronousFuture(AppIconImageKey( packageName: packageName, - sizePixels: (size * configuration.devicePixelRatio).round(), + size: size, scale: scale, )); } @@ -38,28 +38,28 @@ class AppIconImage extends ImageProvider { } Future _loadAsync(AppIconImageKey key, DecoderCallback decode) async { - final bytes = await AndroidAppService.getAppIcon(key.packageName, key.sizePixels); + final bytes = await AndroidAppService.getAppIcon(key.packageName, key.size); return await decode(bytes ?? Uint8List(0)); } } class AppIconImageKey { final String packageName; - final int sizePixels; + final double size; final double scale; const AppIconImageKey({ @required this.packageName, - @required this.sizePixels, + @required this.size, this.scale, }); @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; - return other is AppIconImageKey && other.packageName == packageName && other.sizePixels == sizePixels && other.scale == scale; + return other is AppIconImageKey && other.packageName == packageName && other.size == size && other.scale == scale; } @override - int get hashCode => hashValues(packageName, sizePixels, scale); + int get hashCode => hashValues(packageName, size, scale); } diff --git a/lib/widgets/common/image_providers/thumbnail_provider.dart b/lib/widgets/common/image_providers/thumbnail_provider.dart index 065922755..5b3164df6 100644 --- a/lib/widgets/common/image_providers/thumbnail_provider.dart +++ b/lib/widgets/common/image_providers/thumbnail_provider.dart @@ -34,7 +34,6 @@ class ThumbnailProvider extends ImageProvider { ThumbnailProviderKey _buildKey(ImageConfiguration configuration) => ThumbnailProviderKey( entry: entry, extent: extent, - devicePixelRatio: configuration.devicePixelRatio, scale: scale, ); @@ -50,8 +49,7 @@ class ThumbnailProvider extends ImageProvider { } Future _loadAsync(ThumbnailProviderKey key, DecoderCallback decode) async { - final dimPixels = (extent * key.devicePixelRatio).round(); - final bytes = await ImageFileService.getThumbnail(key.entry, dimPixels, dimPixels, taskKey: _cancellationKey); + final bytes = await ImageFileService.getThumbnail(key.entry, extent, extent, taskKey: _cancellationKey); return await decode(bytes ?? Uint8List(0)); } @@ -67,13 +65,11 @@ class ThumbnailProvider extends ImageProvider { class ThumbnailProviderKey { final ImageEntry entry; final double extent; - final double devicePixelRatio; // do not include configuration in key hashcode or == operator final double scale; const ThumbnailProviderKey({ @required this.entry, @required this.extent, - @required this.devicePixelRatio, this.scale, });