Commit 49acc864 by Jan-Derk

Added MANAGE_EXTERNAL_STORAGE permission

Registered Listeners on plugin level
parent 14368466
## 7.0.0
This release contains the following **breaking changes**:
* Updated compile SDK version to 30 in the build.gradle for handling the MANAGE_EXTERNAL_STORAGE permission;
* Added the MANAGE_EXTERNAL_STORAGE permission for Android R and up;
* Registered listeners on the plugin level to prevent memory leaks or unwanted behaviour.
## 6.1.3 ## 6.1.3
* Implement equality operator on the `Permission` class; * Implement equality operator on the `Permission` class;
......
...@@ -28,7 +28,7 @@ project.getTasks().withType(JavaCompile){ ...@@ -28,7 +28,7 @@ project.getTasks().withType(JavaCompile){
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
compileSdkVersion 29 compileSdkVersion 30
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
......
...@@ -6,12 +6,9 @@ import android.content.Context; ...@@ -6,12 +6,9 @@ import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.MethodChannel.Result;
import com.baseflow.permissionhandler.PermissionManager.ActivityRegistry;
import com.baseflow.permissionhandler.PermissionManager.PermissionRegistry;
import java.util.List; import java.util.List;
...@@ -35,26 +32,10 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { ...@@ -35,26 +32,10 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
@Nullable @Nullable
private Activity activity; private Activity activity;
@Nullable
private ActivityRegistry activityRegistry;
@Nullable
private PermissionRegistry permissionRegistry;
public void setActivity(@Nullable Activity activity) { public void setActivity(@Nullable Activity activity) {
this.activity = activity; this.activity = activity;
} }
public void setActivityRegistry(
@Nullable ActivityRegistry activityRegistry) {
this.activityRegistry = activityRegistry;
}
public void setPermissionRegistry(
@Nullable PermissionRegistry permissionRegistry) {
this.permissionRegistry = permissionRegistry;
}
@Override @Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
{ {
...@@ -91,8 +72,6 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { ...@@ -91,8 +72,6 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
permissionManager.requestPermissions( permissionManager.requestPermissions(
permissions, permissions,
activity, activity,
activityRegistry,
permissionRegistry,
result::success, result::success,
(String errorCode, String errorDescription) -> result.error( (String errorCode, String errorDescription) -> result.error(
errorCode, errorCode,
......
...@@ -8,7 +8,8 @@ import java.lang.annotation.RetentionPolicy; ...@@ -8,7 +8,8 @@ import java.lang.annotation.RetentionPolicy;
final class PermissionConstants { final class PermissionConstants {
static final String LOG_TAG = "permissions_handler"; static final String LOG_TAG = "permissions_handler";
static final int PERMISSION_CODE = 24; static final int PERMISSION_CODE = 24;
static final int PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS = 5672353; static final int PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS = 209;
static final int PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE = 210;
//PERMISSION_GROUP //PERMISSION_GROUP
static final int PERMISSION_GROUP_CALENDAR = 0; static final int PERMISSION_GROUP_CALENDAR = 0;
...@@ -33,6 +34,7 @@ final class PermissionConstants { ...@@ -33,6 +34,7 @@ final class PermissionConstants {
static final int PERMISSION_GROUP_ACTIVITY_RECOGNITION = 19; static final int PERMISSION_GROUP_ACTIVITY_RECOGNITION = 19;
static final int PERMISSION_GROUP_UNKNOWN = 20; static final int PERMISSION_GROUP_UNKNOWN = 20;
static final int PERMISSION_GROUP_BLUETOOTH = 21; static final int PERMISSION_GROUP_BLUETOOTH = 21;
static final int PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE = 22;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
...@@ -57,6 +59,7 @@ final class PermissionConstants { ...@@ -57,6 +59,7 @@ final class PermissionConstants {
PERMISSION_GROUP_ACTIVITY_RECOGNITION, PERMISSION_GROUP_ACTIVITY_RECOGNITION,
PERMISSION_GROUP_UNKNOWN, PERMISSION_GROUP_UNKNOWN,
PERMISSION_GROUP_BLUETOOTH, PERMISSION_GROUP_BLUETOOTH,
PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE
}) })
@interface PermissionGroup { @interface PermissionGroup {
} }
...@@ -74,7 +77,7 @@ final class PermissionConstants { ...@@ -74,7 +77,7 @@ final class PermissionConstants {
PERMISSION_STATUS_GRANTED, PERMISSION_STATUS_GRANTED,
PERMISSION_STATUS_RESTRICTED, PERMISSION_STATUS_RESTRICTED,
PERMISSION_STATUS_LIMITED, PERMISSION_STATUS_LIMITED,
PERMISSION_STATUS_NEVER_ASK_AGAIN, PERMISSION_STATUS_NEVER_ASK_AGAIN
}) })
@interface PermissionStatus { @interface PermissionStatus {
} }
......
...@@ -4,8 +4,6 @@ import android.app.Activity; ...@@ -4,8 +4,6 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.baseflow.permissionhandler.PermissionManager.ActivityRegistry;
import com.baseflow.permissionhandler.PermissionManager.PermissionRegistry;
import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
...@@ -23,11 +21,21 @@ import io.flutter.plugin.common.MethodChannel; ...@@ -23,11 +21,21 @@ import io.flutter.plugin.common.MethodChannel;
*/ */
public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAware { public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAware {
private final PermissionManager permissionManager;
private MethodChannel methodChannel; private MethodChannel methodChannel;
@SuppressWarnings("deprecation")
@Nullable private io.flutter.plugin.common.PluginRegistry.Registrar pluginRegistrar;
@Nullable private ActivityPluginBinding pluginBinding;
@Nullable @Nullable
private MethodCallHandlerImpl methodCallHandler; private MethodCallHandlerImpl methodCallHandler;
public PermissionHandlerPlugin() {
this.permissionManager = new PermissionManager();
}
/** /**
* Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common}
* package. * package.
...@@ -38,13 +46,15 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa ...@@ -38,13 +46,15 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
final PermissionHandlerPlugin plugin = new PermissionHandlerPlugin(); final PermissionHandlerPlugin plugin = new PermissionHandlerPlugin();
plugin.pluginRegistrar = registrar;
plugin.registerListeners();
plugin.startListening(registrar.context(), registrar.messenger()); plugin.startListening(registrar.context(), registrar.messenger());
if (registrar.activeContext() instanceof Activity) { if (registrar.activeContext() instanceof Activity) {
plugin.startListeningToActivity( plugin.startListeningToActivity(
registrar.activity(), registrar.activity()
registrar::addActivityResultListener,
registrar::addRequestPermissionsResultListener
); );
} }
} }
...@@ -65,10 +75,11 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa ...@@ -65,10 +75,11 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa
@Override @Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
startListeningToActivity( startListeningToActivity(
binding.getActivity(), binding.getActivity()
binding::addActivityResultListener,
binding::addRequestPermissionsResultListener
); );
this.pluginBinding = binding;
registerListeners();
} }
@Override @Override
...@@ -79,6 +90,8 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa ...@@ -79,6 +90,8 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa
@Override @Override
public void onDetachedFromActivity() { public void onDetachedFromActivity() {
stopListeningToActivity(); stopListeningToActivity();
deregisterListeners();
} }
@Override @Override
...@@ -95,7 +108,7 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa ...@@ -95,7 +108,7 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa
methodCallHandler = new MethodCallHandlerImpl( methodCallHandler = new MethodCallHandlerImpl(
applicationContext, applicationContext,
new AppSettingsManager(), new AppSettingsManager(),
new PermissionManager(), this.permissionManager,
new ServiceManager() new ServiceManager()
); );
...@@ -109,22 +122,33 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa ...@@ -109,22 +122,33 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa
} }
private void startListeningToActivity( private void startListeningToActivity(
Activity activity, Activity activity
ActivityRegistry activityRegistry,
PermissionRegistry permissionRegistry
) { ) {
if (methodCallHandler != null) { if (methodCallHandler != null) {
methodCallHandler.setActivity(activity); methodCallHandler.setActivity(activity);
methodCallHandler.setActivityRegistry(activityRegistry);
methodCallHandler.setPermissionRegistry(permissionRegistry);
} }
} }
private void stopListeningToActivity() { private void stopListeningToActivity() {
if (methodCallHandler != null) { if (methodCallHandler != null) {
methodCallHandler.setActivity(null); methodCallHandler.setActivity(null);
methodCallHandler.setActivityRegistry(null); }
methodCallHandler.setPermissionRegistry(null); }
private void registerListeners() {
if (this.pluginRegistrar != null) {
this.pluginRegistrar.addActivityResultListener(this.permissionManager);
this.pluginRegistrar.addRequestPermissionsResultListener(this.permissionManager);
} else if (pluginBinding != null) {
this.pluginBinding.addActivityResultListener(this.permissionManager);
this.pluginBinding.addRequestPermissionsResultListener(this.permissionManager);
}
}
private void deregisterListeners() {
if (this.pluginBinding != null) {
this.pluginBinding.removeActivityResultListener(this.permissionManager);
this.pluginBinding.removeRequestPermissionsResultListener(this.permissionManager);
} }
} }
} }
...@@ -6,12 +6,12 @@ import android.content.Intent; ...@@ -6,12 +6,12 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
...@@ -23,15 +23,115 @@ import java.util.Map; ...@@ -23,15 +23,115 @@ import java.util.Map;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
final class PermissionManager { final class PermissionManager implements PluginRegistry.ActivityResultListener, PluginRegistry.RequestPermissionsResultListener {
@FunctionalInterface
interface ActivityRegistry { @Nullable
void addListener(PluginRegistry.ActivityResultListener handler); private ErrorCallback errorCallback;
@Nullable
private RequestPermissionsSuccessCallback successCallback;
@Nullable
private Activity activity;
private Map<Integer, Integer> requestResults;
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS && requestCode != PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE) {
return false;
}
int status = resultCode == Activity.RESULT_OK
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
int permission;
if (requestCode == PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS) {
permission = PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS;
} else if (requestCode == PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE) {
status = Environment.isExternalStorageManager()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permission = PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE;
} else {
return false;
}
HashMap<Integer, Integer> results = new HashMap<>();
results.put(permission, status);
successCallback.onSuccess(results);
return true;
} }
@FunctionalInterface @Override
interface PermissionRegistry { public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
void addListener(PluginRegistry.RequestPermissionsResultListener handler); if (requestCode != PermissionConstants.PERMISSION_CODE) {
ongoing = false;
return false;
}
for (int i = 0; i < permissions.length; i++) {
final String permissionName = permissions[i];
@PermissionConstants.PermissionGroup final int permission =
PermissionUtils.parseManifestName(permissionName);
if (permission == PermissionConstants.PERMISSION_GROUP_UNKNOWN)
continue;
final int result = grantResults[i];
if (permission == PermissionConstants.PERMISSION_GROUP_MICROPHONE) {
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_MICROPHONE)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_MICROPHONE,
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_SPEECH)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_SPEECH,
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS) {
@PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permissionName, result);
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) {
requestResults.put(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus);
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION) {
@PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permissionName, result);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS,
permissionStatus);
}
}
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE,
permissionStatus);
}
requestResults.put(permission, permissionStatus);
} else if (!requestResults.containsKey(permission)) {
requestResults.put(
permission,
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
PermissionUtils.updatePermissionShouldShowStatus(this.activity, permission);
}
this.successCallback.onSuccess(requestResults);
ongoing = false;
return true;
} }
@FunctionalInterface @FunctionalInterface
...@@ -67,8 +167,6 @@ final class PermissionManager { ...@@ -67,8 +167,6 @@ final class PermissionManager {
void requestPermissions( void requestPermissions(
List<Integer> permissions, List<Integer> permissions,
Activity activity, Activity activity,
ActivityRegistry activityRegistry,
PermissionRegistry permissionRegistry,
RequestPermissionsSuccessCallback successCallback, RequestPermissionsSuccessCallback successCallback,
ErrorCallback errorCallback) { ErrorCallback errorCallback) {
if (ongoing) { if (ongoing) {
...@@ -87,7 +185,11 @@ final class PermissionManager { ...@@ -87,7 +185,11 @@ final class PermissionManager {
return; return;
} }
Map<Integer, Integer> requestResults = new HashMap<>(); this.errorCallback = errorCallback;
this.successCallback = successCallback;
this.activity = activity;
this.requestResults = new HashMap<>();
ArrayList<String> permissionsToRequest = new ArrayList<>(); ArrayList<String> permissionsToRequest = new ArrayList<>();
for (Integer permission : permissions) { for (Integer permission : permissions) {
@PermissionConstants.PermissionStatus final int permissionStatus = determinePermissionStatus(permission, activity, activity); @PermissionConstants.PermissionStatus final int permissionStatus = determinePermissionStatus(permission, activity, activity);
...@@ -111,21 +213,30 @@ final class PermissionManager { ...@@ -111,21 +213,30 @@ final class PermissionManager {
} else { } else {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_DENIED); requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_DENIED);
} }
// On Android below R, the android.permission.MANAGE_EXTERNAL_STORAGE flag in AndroidManifest.xml
// may be ignored and not visible to the App as it's a new permission setting as a whole.
if (permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_RESTRICTED);
} else {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_DENIED);
}
} }
continue; continue;
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
activityRegistry.addListener(
new ActivityResultListener(successCallback)
);
String packageName = activity.getPackageName(); String packageName = activity.getPackageName();
Intent intent = new Intent(); Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName)); intent.setData(Uri.parse("package:" + packageName));
activity.startActivityForResult(intent, PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS); activity.startActivityForResult(intent, PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) {
String packageName = activity.getPackageName();
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + packageName));
activity.startActivityForResult(intent, PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE);
} else { } else {
permissionsToRequest.addAll(names); permissionsToRequest.addAll(names);
} }
...@@ -133,16 +244,6 @@ final class PermissionManager { ...@@ -133,16 +244,6 @@ final class PermissionManager {
final String[] requestPermissions = permissionsToRequest.toArray(new String[0]); final String[] requestPermissions = permissionsToRequest.toArray(new String[0]);
if (permissionsToRequest.size() > 0) { if (permissionsToRequest.size() > 0) {
permissionRegistry.addListener(
new RequestPermissionsListener(
activity,
requestResults,
(Map<Integer, Integer> results) -> {
ongoing = false;
successCallback.onSuccess(results);
})
);
ongoing = true; ongoing = true;
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(
...@@ -166,7 +267,7 @@ final class PermissionManager { ...@@ -166,7 +267,7 @@ final class PermissionManager {
if (permission == PermissionConstants.PERMISSION_GROUP_NOTIFICATION) { if (permission == PermissionConstants.PERMISSION_GROUP_NOTIFICATION) {
return checkNotificationPermissionStatus(context); return checkNotificationPermissionStatus(context);
} }
if(permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH){ if (permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH) {
return checkBluetoothPermissionStatus(context); return checkBluetoothPermissionStatus(context);
} }
...@@ -190,6 +291,14 @@ final class PermissionManager { ...@@ -190,6 +291,14 @@ final class PermissionManager {
} }
} }
// On Android below R, the android.permission.MANAGE_EXTERNAL_STORAGE flag in AndroidManifest.xml
// may be ignored and not visible to the App as it's a new permission setting as a whole.
if (permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return PermissionConstants.PERMISSION_STATUS_RESTRICTED;
}
}
return PermissionConstants.PERMISSION_STATUS_DENIED; return PermissionConstants.PERMISSION_STATUS_DENIED;
} }
...@@ -212,6 +321,17 @@ final class PermissionManager { ...@@ -212,6 +321,17 @@ final class PermissionManager {
return PermissionConstants.PERMISSION_STATUS_RESTRICTED; return PermissionConstants.PERMISSION_STATUS_RESTRICTED;
} }
} }
if (permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return PermissionConstants.PERMISSION_STATUS_RESTRICTED;
}
return Environment.isExternalStorageManager()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
}
final int permissionStatus = ContextCompat.checkSelfPermission(context, name); final int permissionStatus = ContextCompat.checkSelfPermission(context, name);
if (permissionStatus != PackageManager.PERMISSION_GRANTED) { if (permissionStatus != PackageManager.PERMISSION_GRANTED) {
return PermissionConstants.PERMISSION_STATUS_DENIED; return PermissionConstants.PERMISSION_STATUS_DENIED;
...@@ -265,139 +385,10 @@ final class PermissionManager { ...@@ -265,139 +385,10 @@ final class PermissionManager {
private int checkBluetoothPermissionStatus(Context context) { private int checkBluetoothPermissionStatus(Context context) {
List<String> names = PermissionUtils.getManifestNames(context, PermissionConstants.PERMISSION_GROUP_BLUETOOTH); List<String> names = PermissionUtils.getManifestNames(context, PermissionConstants.PERMISSION_GROUP_BLUETOOTH);
boolean missingInManifest = names == null || names.isEmpty(); boolean missingInManifest = names == null || names.isEmpty();
if(missingInManifest) { if (missingInManifest) {
Log.d(PermissionConstants.LOG_TAG, "Bluetooth permission missing in manifest"); Log.d(PermissionConstants.LOG_TAG, "Bluetooth permission missing in manifest");
return PermissionConstants.PERMISSION_STATUS_DENIED; return PermissionConstants.PERMISSION_STATUS_DENIED;
} }
return PermissionConstants.PERMISSION_STATUS_GRANTED; return PermissionConstants.PERMISSION_STATUS_GRANTED;
} }
@VisibleForTesting
static final class ActivityResultListener
implements PluginRegistry.ActivityResultListener {
// There's no way to unregister permission listeners in the v1 embedding, so we'll be called
// duplicate times in cases where the user denies and then grants a permission. Keep track of if
// we've responded before and bail out of handling the callback manually if this is a repeat
// call.
boolean alreadyCalled = false;
final RequestPermissionsSuccessCallback callback;
@VisibleForTesting
ActivityResultListener(RequestPermissionsSuccessCallback callback) {
this.callback = callback;
}
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (alreadyCalled || requestCode != PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS) {
return false;
}
alreadyCalled = true;
final int status = resultCode == Activity.RESULT_OK
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
HashMap<Integer, Integer> results = new HashMap<>();
results.put(PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS, status);
callback.onSuccess(results);
return true;
}
}
@VisibleForTesting
static final class RequestPermissionsListener
implements PluginRegistry.RequestPermissionsResultListener {
// There's no way to unregister permission listeners in the v1 embedding, so we'll be called
// duplicate times in cases where the user denies and then grants a permission. Keep track of if
// we've responded before and bail out of handling the callback manually if this is a repeat
// call.
boolean alreadyCalled = false;
final Activity activity;
final RequestPermissionsSuccessCallback callback;
final Map<Integer, Integer> requestResults;
@VisibleForTesting
RequestPermissionsListener(
Activity activity,
Map<Integer, Integer> requestResults,
RequestPermissionsSuccessCallback callback) {
this.activity = activity;
this.callback = callback;
this.requestResults = requestResults;
}
@Override
public boolean onRequestPermissionsResult(int id, String[] permissions, int[] grantResults) {
if (alreadyCalled || id != PermissionConstants.PERMISSION_CODE) {
return false;
}
alreadyCalled = true;
for (int i = 0; i < permissions.length; i++) {
final String permissionName = permissions[i];
@PermissionConstants.PermissionGroup final int permission =
PermissionUtils.parseManifestName(permissionName);
if (permission == PermissionConstants.PERMISSION_GROUP_UNKNOWN)
continue;
final int result = grantResults[i];
if (permission == PermissionConstants.PERMISSION_GROUP_MICROPHONE) {
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_MICROPHONE)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_MICROPHONE,
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_SPEECH)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_SPEECH,
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS) {
@PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permissionName, result);
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) {
requestResults.put(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus);
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION) {
@PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permissionName, result);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS,
permissionStatus);
}
}
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE,
permissionStatus);
}
requestResults.put(permission, permissionStatus);
} else if (!requestResults.containsKey(permission)) {
requestResults.put(
permission,
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
PermissionUtils.updatePermissionShouldShowStatus(this.activity, permission);
}
this.callback.onSuccess(requestResults);
return true;
}
}
} }
...@@ -6,6 +6,7 @@ import android.content.Context; ...@@ -6,6 +6,7 @@ import android.content.Context;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.util.Log; import android.util.Log;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
...@@ -60,6 +61,8 @@ public class PermissionUtils { ...@@ -60,6 +61,8 @@ public class PermissionUtils {
return PermissionConstants.PERMISSION_GROUP_ACCESS_MEDIA_LOCATION; return PermissionConstants.PERMISSION_GROUP_ACCESS_MEDIA_LOCATION;
case Manifest.permission.ACTIVITY_RECOGNITION: case Manifest.permission.ACTIVITY_RECOGNITION:
return PermissionConstants.PERMISSION_GROUP_ACTIVITY_RECOGNITION; return PermissionConstants.PERMISSION_GROUP_ACTIVITY_RECOGNITION;
case Manifest.permission.MANAGE_EXTERNAL_STORAGE:
return PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE;
default: default:
return PermissionConstants.PERMISSION_GROUP_UNKNOWN; return PermissionConstants.PERMISSION_GROUP_UNKNOWN;
} }
...@@ -177,8 +180,11 @@ public class PermissionUtils { ...@@ -177,8 +180,11 @@ public class PermissionUtils {
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_EXTERNAL_STORAGE)) if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_EXTERNAL_STORAGE))
permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE); permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_EXTERNAL_STORAGE)) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && Environment.isExternalStorageLegacy())) {
permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_EXTERNAL_STORAGE))
permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
break;
}
break; break;
case PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS: case PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS:
...@@ -197,7 +203,7 @@ public class PermissionUtils { ...@@ -197,7 +203,7 @@ public class PermissionUtils {
break; break;
case PermissionConstants.PERMISSION_GROUP_ACTIVITY_RECOGNITION: case PermissionConstants.PERMISSION_GROUP_ACTIVITY_RECOGNITION:
// The ACCESS_MEDIA_LOCATION permission is introduced in Android Q, meaning we should // The ACTIVITY_RECOGNITION permission is introduced in Android Q, meaning we should
// not handle permissions on pre Android Q devices. // not handle permissions on pre Android Q devices.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
return null; return null;
...@@ -210,6 +216,14 @@ public class PermissionUtils { ...@@ -210,6 +216,14 @@ public class PermissionUtils {
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.BLUETOOTH)) if (hasPermissionInManifest(context, permissionNames, Manifest.permission.BLUETOOTH))
permissionNames.add(Manifest.permission.BLUETOOTH); permissionNames.add(Manifest.permission.BLUETOOTH);
break; break;
case PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE:
// The MANAGE_EXTERNAL_STORAGE permission is introduced in Android R, meaning we should
// not handle permissions on pre Android R devices.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && hasPermissionInManifest(context, permissionNames, Manifest.permission.MANAGE_EXTERNAL_STORAGE ))
permissionNames.add(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
break;
case PermissionConstants.PERMISSION_GROUP_NOTIFICATION: case PermissionConstants.PERMISSION_GROUP_NOTIFICATION:
case PermissionConstants.PERMISSION_GROUP_MEDIA_LIBRARY: case PermissionConstants.PERMISSION_GROUP_MEDIA_LIBRARY:
case PermissionConstants.PERMISSION_GROUP_PHOTOS: case PermissionConstants.PERMISSION_GROUP_PHOTOS:
......
...@@ -25,7 +25,7 @@ apply plugin: 'com.android.application' ...@@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 29 compileSdkVersion 30
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
......
...@@ -63,6 +63,9 @@ ...@@ -63,6 +63,9 @@
<!-- Permissions options for the `bluetooth` group --> <!-- Permissions options for the `bluetooth` group -->
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<!-- Permissions options for the `manage external storage` group -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application <application
android:name="io.flutter.app.FlutterApplication" android:name="io.flutter.app.FlutterApplication"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
......
...@@ -20,7 +20,8 @@ class PermissionList extends StatelessWidget { ...@@ -20,7 +20,8 @@ class PermissionList extends StatelessWidget {
permission != Permission.storage && permission != Permission.storage &&
permission != Permission.ignoreBatteryOptimizations && permission != Permission.ignoreBatteryOptimizations &&
permission != Permission.accessMediaLocation && permission != Permission.accessMediaLocation &&
permission != Permission.activityRecognition; permission != Permission.activityRecognition &&
permission != Permission.manageExternalStorage;
} else { } else {
return permission != Permission.unknown && return permission != Permission.unknown &&
permission != Permission.mediaLibrary && permission != Permission.mediaLibrary &&
......
...@@ -118,6 +118,7 @@ typedef NS_ENUM(int, PermissionGroup) { ...@@ -118,6 +118,7 @@ typedef NS_ENUM(int, PermissionGroup) {
PermissionGroupActivityRecognition, PermissionGroupActivityRecognition,
PermissionGroupUnknown, PermissionGroupUnknown,
PermissionGroupBluetooth, PermissionGroupBluetooth,
PermissionGroupManageExternalStorage
}; };
typedef NS_ENUM(int, PermissionStatus) { typedef NS_ENUM(int, PermissionStatus) {
......
name: permission_handler name: permission_handler
description: Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions. description: Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
version: 6.1.3 version: 7.0.0
homepage: https://github.com/baseflowit/flutter-permission-handler homepage: https://github.com/baseflowit/flutter-permission-handler
flutter: flutter:
...@@ -16,7 +16,7 @@ dependencies: ...@@ -16,7 +16,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
meta: ^1.3.0 meta: ^1.3.0
permission_handler_platform_interface: ^3.1.3 permission_handler_platform_interface: ^3.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment