android: desugaring to allow targeting older API

This commit is contained in:
Thibault Deckers 2020-05-25 15:37:44 +09:00
parent 41cf11bc55
commit cdf435420f
9 changed files with 69 additions and 47 deletions

View file

@ -33,12 +33,21 @@ android {
defaultConfig { defaultConfig {
applicationId "deckers.thibault.aves" applicationId "deckers.thibault.aves"
minSdkVersion 24 // Java 8 (stream, lambda, etc.) requires minSdkVersion 24 // some Java 8 APIs (java.util.stream, etc.) require minSdkVersion 24
// but Android Studio 4.0 desugaring features allow targeting older SDKs
minSdkVersion 23
targetSdkVersion 29 // same as compileSdkVersion targetSdkVersion 29 // same as compileSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
compileOptions {
// enable support for Java 8 language APIs (stream, optional, etc.)
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes { buildTypes {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
@ -56,6 +65,9 @@ flutter {
} }
dependencies { dependencies {
// enable support for Java 8 language APIs (stream, optional, etc.)
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5'
implementation 'com.drewnoakes:metadata-extractor:2.14.0' implementation 'com.drewnoakes:metadata-extractor:2.14.0'
implementation 'com.github.bumptech.glide:glide:4.11.0' implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation 'com.google.guava:guava:29.0-android' implementation 'com.google.guava:guava:29.0-android'

View file

@ -71,11 +71,10 @@ public class MainActivity extends FlutterActivity {
Intent data = new Intent(); Intent data = new Intent();
data.setData(Uri.parse(resultUri)); data.setData(Uri.parse(resultUri));
setResult(RESULT_OK, data); setResult(RESULT_OK, data);
finish();
} else { } else {
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);
finish();
} }
finish();
} }
}); });
} }

View file

@ -1,7 +1,6 @@
package deckers.thibault.aves.channelhandlers; package deckers.thibault.aves.channelhandlers;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentUris; import android.content.ContentUris;
@ -13,6 +12,8 @@ import android.provider.MediaStore;
import android.util.Log; import android.util.Log;
import android.util.Size; import android.util.Size;
import androidx.annotation.RequiresApi;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key; import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
@ -104,7 +105,7 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
return new Result(p, data); return new Result(p, data);
} }
@TargetApi(Build.VERSION_CODES.Q) @RequiresApi(api = Build.VERSION_CODES.Q)
private Bitmap getThumbnailBytesByResolver(Params params) throws IOException { private Bitmap getThumbnailBytesByResolver(Params params) throws IOException {
ImageEntry entry = params.entry; ImageEntry entry = params.entry;
int width = params.width; int width = params.width;

View file

@ -5,14 +5,11 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import deckers.thibault.aves.model.provider.MediaStoreImageProvider; import deckers.thibault.aves.model.provider.MediaStoreImageProvider;
import deckers.thibault.aves.utils.Utils;
import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.EventChannel;
public class MediaStoreStreamHandler implements EventChannel.StreamHandler { public class MediaStoreStreamHandler implements EventChannel.StreamHandler {
public static final String CHANNEL = "deckers.thibault/aves/mediastore"; public static final String CHANNEL = "deckers.thibault/aves/mediastore";
private static final String LOG_TAG = Utils.createLogTag(MediaStoreStreamHandler.class);
private EventChannel.EventSink eventSink; private EventChannel.EventSink eventSink;
@Override @Override
@ -26,11 +23,8 @@ public class MediaStoreStreamHandler implements EventChannel.StreamHandler {
} }
void fetchAll(Activity activity) { void fetchAll(Activity activity) {
// Log.d(LOG_TAG, "fetchAll start");
// Instant start = Instant.now();
Handler handler = new Handler(Looper.getMainLooper()); Handler handler = new Handler(Looper.getMainLooper());
new MediaStoreImageProvider().fetchAll(activity, (entry) -> handler.post(() -> eventSink.success(entry))); // 350ms new MediaStoreImageProvider().fetchAll(activity, (entry) -> handler.post(() -> eventSink.success(entry))); // 350ms
handler.post(() -> eventSink.endOfStream()); handler.post(() -> eventSink.endOfStream());
// Log.d(LOG_TAG, "fetchAll complete in " + Duration.between(start, Instant.now()).toMillis() + "ms");
} }
} }

View file

@ -1,10 +1,12 @@
package deckers.thibault.aves.channelhandlers; package deckers.thibault.aves.channelhandlers;
import android.app.Activity; import android.app.Activity;
import android.os.Build;
import android.os.storage.StorageManager; import android.os.storage.StorageManager;
import android.os.storage.StorageVolume; import android.os.storage.StorageVolume;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -30,27 +32,12 @@ public class StorageHandler implements MethodChannel.MethodCallHandler {
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) { switch (call.method) {
case "getStorageVolumes": { case "getStorageVolumes": {
List<Map<String, Object>> volumes = new ArrayList<>(); List<Map<String, Object>> volumes = null;
StorageManager sm = activity.getSystemService(StorageManager.class); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
if (sm != null) { volumes = getStorageVolumes();
for (String path : Env.getStorageVolumes(activity)) { } else {
try { // TODO TLAD find alternative for Android <N
File file = new File(path); volumes = new ArrayList<>();
StorageVolume volume = sm.getStorageVolume(file);
if (volume != null) {
Map<String, Object> 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); result.success(volumes);
break; break;
@ -66,4 +53,31 @@ public class StorageHandler implements MethodChannel.MethodCallHandler {
break; break;
} }
} }
@RequiresApi(api = Build.VERSION_CODES.N)
private List<Map<String, Object>> getStorageVolumes() {
List<Map<String, Object>> 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<String, Object> 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
}
}
}
return volumes;
}
} }

View file

@ -224,9 +224,7 @@ public abstract class ImageProvider {
values.put(MediaStore.MediaColumns.ORIENTATION, orientationDegrees); values.put(MediaStore.MediaColumns.ORIENTATION, orientationDegrees);
int updatedRowCount = contentResolver.update(uri, values, null, null); int updatedRowCount = contentResolver.update(uri, values, null, null);
if (updatedRowCount > 0) { if (updatedRowCount > 0) {
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> { MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> callback.onSuccess(newFields));
callback.onSuccess(newFields);
});
} else { } else {
Log.w(LOG_TAG, "failed to update fields in Media Store for uri=" + uri); Log.w(LOG_TAG, "failed to update fields in Media Store for uri=" + uri);
callback.onSuccess(newFields); callback.onSuccess(newFields);

View file

@ -1,6 +1,5 @@
package deckers.thibault.aves.utils; package deckers.thibault.aves.utils;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -12,6 +11,7 @@ import android.os.storage.StorageVolume;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.util.Log; import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.util.Pair; import androidx.core.util.Pair;
@ -36,7 +36,7 @@ public class PermissionManager {
return uriPermissionOptional.map(UriPermission::getUri).orElse(null); return uriPermissionOptional.map(UriPermission::getUri).orElse(null);
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void showSdCardAccessDialog(final Activity activity, final Runnable pendingRunnable) { public static void showSdCardAccessDialog(final Activity activity, final Runnable pendingRunnable) {
new AlertDialog.Builder(activity) new AlertDialog.Builder(activity)
.setTitle("SD Card Access") .setTitle("SD Card Access")
@ -77,19 +77,23 @@ public class PermissionManager {
runnable.run(); runnable.run();
} }
public static boolean hasGrantedPermissionToVolumeRoot(Context context, String path) { public static boolean hasGrantedPermissionToVolumeRoot(Context context, String path) {
boolean canAccess = false; boolean canAccess = false;
Stream<Uri> permittedUris = context.getContentResolver().getPersistedUriPermissions().stream().map(UriPermission::getUri); Stream<Uri> permittedUris = context.getContentResolver().getPersistedUriPermissions().stream().map(UriPermission::getUri);
// e.g. content://com.android.externalstorage.documents/tree/12A9-8B42%3A // e.g. content://com.android.externalstorage.documents/tree/12A9-8B42%3A
StorageManager sm = context.getSystemService(StorageManager.class); StorageManager sm = context.getSystemService(StorageManager.class);
if (sm != null) { if (sm != null) {
StorageVolume volume = sm.getStorageVolume(new File(path)); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
if (volume != null) { StorageVolume volume = sm.getStorageVolume(new File(path));
// primary storage doesn't have a UUID if (volume != null) {
String uuid = volume.isPrimary() ? "primary" : volume.getUuid(); // primary storage doesn't have a UUID
Uri targetVolumeTreeUri = getVolumeTreeUriFromUuid(uuid); String uuid = volume.isPrimary() ? "primary" : volume.getUuid();
canAccess = permittedUris.anyMatch(uri -> uri.equals(targetVolumeTreeUri)); Uri targetVolumeTreeUri = getVolumeTreeUriFromUuid(uuid);
canAccess = permittedUris.anyMatch(uri -> uri.equals(targetVolumeTreeUri));
}
} else {
// TODO TLAD find alternative for Android <N
canAccess = true;
} }
} }
return canAccess; return canAccess;

View file

@ -5,7 +5,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.android.tools.build:gradle:4.0.0-rc01'
} }
} }

View file

@ -1,6 +1,6 @@
#Tue Apr 21 13:20:37 KST 2020 #Mon May 25 13:52:11 KST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip