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 {
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
versionCode flutterVersionCode.toInteger()
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 {
debug {
applicationIdSuffix ".debug"
@ -56,6 +65,9 @@ flutter {
}
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.github.bumptech.glide:glide:4.11.0'
implementation 'com.google.guava:guava:29.0-android'

View file

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

View file

@ -1,7 +1,6 @@
package deckers.thibault.aves.channelhandlers;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
@ -13,6 +12,8 @@ import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import androidx.annotation.RequiresApi;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key;
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);
}
@TargetApi(Build.VERSION_CODES.Q)
@RequiresApi(api = Build.VERSION_CODES.Q)
private Bitmap getThumbnailBytesByResolver(Params params) throws IOException {
ImageEntry entry = params.entry;
int width = params.width;

View file

@ -5,14 +5,11 @@ import android.os.Handler;
import android.os.Looper;
import deckers.thibault.aves.model.provider.MediaStoreImageProvider;
import deckers.thibault.aves.utils.Utils;
import io.flutter.plugin.common.EventChannel;
public class MediaStoreStreamHandler implements EventChannel.StreamHandler {
public static final String CHANNEL = "deckers.thibault/aves/mediastore";
private static final String LOG_TAG = Utils.createLogTag(MediaStoreStreamHandler.class);
private EventChannel.EventSink eventSink;
@Override
@ -26,11 +23,8 @@ public class MediaStoreStreamHandler implements EventChannel.StreamHandler {
}
void fetchAll(Activity activity) {
// Log.d(LOG_TAG, "fetchAll start");
// Instant start = Instant.now();
Handler handler = new Handler(Looper.getMainLooper());
new MediaStoreImageProvider().fetchAll(activity, (entry) -> handler.post(() -> eventSink.success(entry))); // 350ms
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;
import android.app.Activity;
import android.os.Build;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.io.File;
import java.util.ArrayList;
@ -30,27 +32,12 @@ public class StorageHandler implements MethodChannel.MethodCallHandler {
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "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
}
}
List<Map<String, Object>> volumes = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
volumes = getStorageVolumes();
} else {
// TODO TLAD find alternative for Android <N
volumes = new ArrayList<>();
}
result.success(volumes);
break;
@ -66,4 +53,31 @@ public class StorageHandler implements MethodChannel.MethodCallHandler {
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);
int updatedRowCount = contentResolver.update(uri, values, null, null);
if (updatedRowCount > 0) {
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> {
callback.onSuccess(newFields);
});
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> callback.onSuccess(newFields));
} else {
Log.w(LOG_TAG, "failed to update fields in Media Store for uri=" + uri);
callback.onSuccess(newFields);

View file

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

View file

@ -5,7 +5,7 @@ buildscript {
}
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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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