From 23eac7c3c7276206ea6732a5fb4a27abbe23419c Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 13 Apr 2020 13:19:37 +0900 Subject: [PATCH] get storage volumes --- .../deckers/thibault/aves/MainActivity.java | 2 + .../channelhandlers/FileAdapterHandler.java | 214 +++--------------- lib/utils/android_file_service.dart | 16 ++ lib/utils/android_file_utils.dart | 28 +++ lib/widgets/debug_page.dart | 4 + 5 files changed, 84 insertions(+), 180 deletions(-) diff --git a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java index 0fc854452..346dfa84e 100644 --- a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java +++ b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; import deckers.thibault.aves.channelhandlers.AppAdapterHandler; +import deckers.thibault.aves.channelhandlers.FileAdapterHandler; import deckers.thibault.aves.channelhandlers.ImageFileHandler; import deckers.thibault.aves.channelhandlers.MediaStoreStreamHandler; import deckers.thibault.aves.channelhandlers.MetadataHandler; @@ -39,6 +40,7 @@ public class MainActivity extends FlutterActivity { MediaStoreStreamHandler mediaStoreStreamHandler = new MediaStoreStreamHandler(); FlutterView messenger = getFlutterView(); + new MethodChannel(messenger, FileAdapterHandler.CHANNEL).setMethodCallHandler(new FileAdapterHandler(this)); new MethodChannel(messenger, AppAdapterHandler.CHANNEL).setMethodCallHandler(new AppAdapterHandler(this)); new MethodChannel(messenger, ImageFileHandler.CHANNEL).setMethodCallHandler(new ImageFileHandler(this, mediaStoreStreamHandler)); new MethodChannel(messenger, MetadataHandler.CHANNEL).setMethodCallHandler(new MetadataHandler(this)); diff --git a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/FileAdapterHandler.java b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/FileAdapterHandler.java index 7976027df..f60035ef9 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/FileAdapterHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/FileAdapterHandler.java @@ -1,90 +1,57 @@ package deckers.thibault.aves.channelhandlers; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; -import android.net.Uri; +import android.app.Activity; +import android.os.storage.StorageManager; +import android.os.storage.StorageVolume; import androidx.annotation.NonNull; -import androidx.core.content.FileProvider; -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.Key; -import com.bumptech.glide.request.FutureTarget; -import com.bumptech.glide.request.RequestOptions; -import com.bumptech.glide.signature.ObjectKey; - -import java.io.ByteArrayOutputStream; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import deckers.thibault.aves.utils.Env; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import static com.bumptech.glide.request.RequestOptions.centerCropTransform; +public class FileAdapterHandler implements MethodChannel.MethodCallHandler { + public static final String CHANNEL = "deckers.thibault/aves/file"; -public class AppAdapterHandler implements MethodChannel.MethodCallHandler { - public static final String CHANNEL = "deckers.thibault/aves/app"; + private Activity activity; - private Context context; - - public AppAdapterHandler(Context context) { - this.context = context; + public FileAdapterHandler(Activity activity) { + this.activity = activity; } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { switch (call.method) { - case "getAppIcon": { - new Thread(() -> getAppIcon(call, new MethodResultWrapper(result))).start(); - break; - } - case "getAppNames": { - result.success(getAppNames()); - break; - } - case "edit": { - String title = call.argument("title"); - Uri uri = Uri.parse(call.argument("uri")); - String mimeType = call.argument("mimeType"); - edit(title, uri, mimeType); - result.success(null); - break; - } - case "open": { - String title = call.argument("title"); - Uri uri = Uri.parse(call.argument("uri")); - String mimeType = call.argument("mimeType"); - open(title, uri, mimeType); - result.success(null); - break; - } - case "openMap": { - Uri geoUri = Uri.parse(call.argument("geoUri")); - openMap(geoUri); - result.success(null); - break; - } - case "setAs": { - String title = call.argument("title"); - Uri uri = Uri.parse(call.argument("uri")); - String mimeType = call.argument("mimeType"); - setAs(title, uri, mimeType); - result.success(null); - break; - } - case "share": { - String title = call.argument("title"); - Uri uri = Uri.parse(call.argument("uri")); - String mimeType = call.argument("mimeType"); - share(title, uri, mimeType); - result.success(null); + case "getStorageVolumes": { + List> volumes = new ArrayList<>(); + StorageManager sm = activity.getSystemService(StorageManager.class); + if (sm != null) { + for (String path : Env.getStorageVolumes(activity)) { + try { + File file = new File(path); + StorageVolume volume = sm.getStorageVolume(file); + if (volume != null) { + Map volumeMap = new HashMap<>(); + volumeMap.put("path", path); + volumeMap.put("description", volume.getDescription(activity)); + volumeMap.put("isPrimary", volume.isPrimary()); + volumeMap.put("isRemovable", volume.isRemovable()); + volumeMap.put("isEmulated", volume.isEmulated()); + volumeMap.put("state", volume.getState()); + volumes.add(volumeMap); + } + } catch (IllegalArgumentException e) { + // ignore + } + } + } + result.success(volumes); break; } default: @@ -92,117 +59,4 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler { break; } } - - private Map getAppNames() { - Map nameMap = new HashMap<>(); - Intent intent = new Intent(Intent.ACTION_MAIN, null); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - PackageManager packageManager = context.getPackageManager(); - List resolveInfoList = packageManager.queryIntentActivities(intent, 0); - for (ResolveInfo resolveInfo : resolveInfoList) { - ApplicationInfo applicationInfo = resolveInfo.activityInfo.applicationInfo; - boolean isSystemPackage = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - if (!isSystemPackage) { - String appName = String.valueOf(packageManager.getApplicationLabel(applicationInfo)); - nameMap.put(appName, applicationInfo.packageName); - } - } - return nameMap; - } - - private void getAppIcon(MethodCall call, MethodChannel.Result result) { - String packageName = call.argument("packageName"); - Integer size = call.argument("size"); - if (packageName == null || size == null) { - result.error("getAppIcon-args", "failed because of missing arguments", null); - return; - } - - byte[] data = null; - try { - int iconResourceId = context.getPackageManager().getApplicationInfo(packageName, 0).icon; - Uri uri = new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(packageName) - .path(String.valueOf(iconResourceId)) - .build(); - - // add signature to ignore cache for images which got modified but kept the same URI - Key signature = new ObjectKey(packageName + size); - RequestOptions options = new RequestOptions() - .signature(signature) - .override(size, size); - - FutureTarget target = Glide.with(context) - .asBitmap() - .apply(options) - .apply(centerCropTransform()) - .load(uri) - .signature(signature) - .submit(size, size); - - try { - Bitmap bmp = target.get(); - if (bmp != null) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bmp.compress(Bitmap.CompressFormat.PNG, 100, stream); - data = stream.toByteArray(); - } - } catch (Exception e) { - e.printStackTrace(); - } - Glide.with(context).clear(target); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return; - } - if (data != null) { - result.success(data); - } else { - result.error("getAppIcon-null", "failed to get icon for packageName=" + packageName, null); - } - } - - private void edit(String title, Uri uri, String mimeType) { - Intent intent = new Intent(Intent.ACTION_EDIT); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.setDataAndType(uri, mimeType); - context.startActivity(Intent.createChooser(intent, title)); - } - - private void open(String title, Uri uri, String mimeType) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(uri, mimeType); - context.startActivity(Intent.createChooser(intent, title)); - } - - private void openMap(Uri geoUri) { - Intent intent = new Intent(Intent.ACTION_VIEW, geoUri); - if (intent.resolveActivity(context.getPackageManager()) != null) { - context.startActivity(intent); - } - } - - private void setAs(String title, Uri uri, String mimeType) { - Intent intent = new Intent(Intent.ACTION_ATTACH_DATA); - intent.setDataAndType(uri, mimeType); - context.startActivity(Intent.createChooser(intent, title)); - } - - private void share(String title, Uri uri, String mimeType) { - Intent intent = new Intent(Intent.ACTION_SEND); - if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) { - String path = uri.getPath(); - if (path == null) return; - String applicationId = context.getApplicationContext().getPackageName(); - Uri apkUri = FileProvider.getUriForFile(context, applicationId + ".fileprovider", new File(path)); - intent.putExtra(Intent.EXTRA_STREAM, apkUri); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } else { - intent.putExtra(Intent.EXTRA_STREAM, uri); - } - intent.setType(mimeType); - context.startActivity(Intent.createChooser(intent, title)); - } } diff --git a/lib/utils/android_file_service.dart b/lib/utils/android_file_service.dart index e69de29bb..f43cc7e9b 100644 --- a/lib/utils/android_file_service.dart +++ b/lib/utils/android_file_service.dart @@ -0,0 +1,16 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +class AndroidFileService { + static const platform = MethodChannel('deckers.thibault/aves/file'); + + static Future> getStorageVolumes() async { + try { + final result = await platform.invokeMethod('getStorageVolumes'); + return (result as List).cast(); + } on PlatformException catch (e) { + debugPrint('getStorageVolumes failed with code=${e.code}, exception=${e.message}, details=${e.details}}'); + } + return []; + } +} diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart index 28871ddb9..6d7cbe8db 100644 --- a/lib/utils/android_file_utils.dart +++ b/lib/utils/android_file_utils.dart @@ -1,4 +1,5 @@ import 'package:aves/utils/android_app_service.dart'; +import 'package:aves/utils/android_file_service.dart'; import 'package:path/path.dart'; final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); @@ -6,11 +7,13 @@ final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); class AndroidFileUtils { String externalStorage, dcimPath, downloadPath, moviesPath, picturesPath; + static List storageVolumes = []; static Map appNameMap = {}; AndroidFileUtils._private(); Future init() async { + storageVolumes = (await AndroidFileService.getStorageVolumes()).map((map) => StorageVolume.fromMap(map)).toList(); // path_provider getExternalStorageDirectory() gives '/storage/emulated/0/Android/data/deckers.thibault.aves/files' externalStorage = '/storage/emulated/0'; dcimPath = join(externalStorage, 'DCIM'); @@ -56,3 +59,28 @@ enum AlbumType { ScreenRecordings, Screenshots, } + +class StorageVolume { + final String description, path, state; + final bool isEmulated, isPrimary, isRemovable; + + const StorageVolume({ + this.description, + this.isEmulated, + this.isPrimary, + this.isRemovable, + this.path, + this.state, + }); + + factory StorageVolume.fromMap(Map map) { + return StorageVolume( + description: map['description'] ?? '', + isEmulated: map['isEmulated'] ?? false, + isPrimary: map['isPrimary'] ?? false, + isRemovable: map['isRemovable'] ?? false, + path: map['path'] ?? '', + state: map['string'] ?? '', + ); + } +} diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart index 050de997d..a88230036 100644 --- a/lib/widgets/debug_page.dart +++ b/lib/widgets/debug_page.dart @@ -4,6 +4,7 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/metadata_db.dart'; import 'package:aves/model/settings.dart'; +import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/file_utils.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:flutter/material.dart'; @@ -48,6 +49,9 @@ class DebugPageState extends State { child: ListView( padding: const EdgeInsets.all(8), children: [ + const Text('Storage'), + ...AndroidFileUtils.storageVolumes.map((v) => Text('${v.description}: ${v.path} (removable: ${v.isRemovable}))')), + const Divider(), Row( children: [ const Text('Settings'),