android: desugaring to allow targeting older API
This commit is contained in:
parent
41cf11bc55
commit
cdf435420f
9 changed files with 69 additions and 47 deletions
|
@ -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'
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue