Commit 921652cd by Jeroen Weener Committed by GitHub

Handle special permissions correctly when requesting multiple permissions (#1139)

* Handle special permissions for multirequest

* Update PI docs (#1133)

* Add background location section to faq (#1134)

* Open settings screen (#1138)

* Update Android version mentions (#1135)

* Update Android version references (#1136)

* Refactor launching of special permissions

* Handle special permissions for multirequest

* Refactor launching of special permissions
parent 9da1209b
## 10.3.6
* Fixes a bug where requesting multiple permissions would crash the app if at least one of the permissions was a [special permission](https://developer.android.com/guide/topics/permissions/overview#special).
## 10.3.5
* Fixes a bug where `Permission.ScheduleExactAlarm` was not opening the settings
......
......@@ -15,9 +15,9 @@ import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.app.AlarmManagerCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
......@@ -36,22 +36,39 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
@Nullable
private Activity activity;
/**
* The number of pending permission requests.
* <p>
* This number is set by {@link this#requestPermissions(List, Activity, RequestPermissionsSuccessCallback, ErrorCallback)}
* and then reduced when receiving results in {@link this#onActivityResult(int, int, Intent)}
* and {@link this#onRequestPermissionsResult(int, String[], int[])}.
*/
private int pendingRequestCount;
/**
* The results of resolved permission requests.
* <p>
* This map holds the results to resolved permission requests received through
* {@link this#onActivityResult(int, int, Intent)} and
* {@link this#onRequestPermissionsResult(int, String[], int[])}.
* It is (re)initialized when new permissions are requested through
* {@link this#requestPermissions(List, Activity, RequestPermissionsSuccessCallback, ErrorCallback)}.
*/
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 &&
requestCode != PermissionConstants.PERMISSION_CODE_SYSTEM_ALERT_WINDOW &&
requestCode != PermissionConstants.PERMISSION_CODE_REQUEST_INSTALL_PACKAGES &&
requestCode != PermissionConstants.PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY &&
requestCode != PermissionConstants.PERMISSION_CODE_SCHEDULE_EXACT_ALARM) {
requestCode != PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE &&
requestCode != PermissionConstants.PERMISSION_CODE_SYSTEM_ALERT_WINDOW &&
requestCode != PermissionConstants.PERMISSION_CODE_REQUEST_INSTALL_PACKAGES &&
requestCode != PermissionConstants.PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY &&
requestCode != PermissionConstants.PERMISSION_CODE_SCHEDULE_EXACT_ALARM) {
return false;
}
int status = resultCode == Activity.RESULT_OK
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
int permission;
......@@ -60,8 +77,8 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
} else if (requestCode == PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
status = Environment.isExternalStorageManager()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
} else {
return false;
}
......@@ -69,8 +86,8 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
} else if (requestCode == PermissionConstants.PERMISSION_CODE_SYSTEM_ALERT_WINDOW) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
status = Settings.canDrawOverlays(activity)
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permission = PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW;
} else {
return false;
......@@ -78,8 +95,8 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
} else if (requestCode == PermissionConstants.PERMISSION_CODE_REQUEST_INSTALL_PACKAGES) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
status = activity.getPackageManager().canRequestPackageInstalls()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permission = PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES;
} else {
return false;
......@@ -88,8 +105,8 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
NotificationManager notificationManager = (NotificationManager) activity.getSystemService(Application.NOTIFICATION_SERVICE);
status = notificationManager.isNotificationPolicyAccessGranted()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permission = PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY;
} else {
return false;
......@@ -98,8 +115,8 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
AlarmManager alarmManager = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
status = alarmManager.canScheduleExactAlarms()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permission = PermissionConstants.PERMISSION_GROUP_SCHEDULE_EXACT_ALARM;
} else {
return false;
......@@ -108,28 +125,36 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
return false;
}
HashMap<Integer, Integer> results = new HashMap<>();
results.put(permission, status);
successCallback.onSuccess(results);
requestResults.put(permission, status);
pendingRequestCount--;
// Post result if all requests have been handled.
if (pendingRequestCount == 0) {
this.successCallback.onSuccess(requestResults);
}
return true;
}
@Override
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
public boolean onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode != PermissionConstants.PERMISSION_CODE) {
ongoing = false;
pendingRequestCount = 0;
return false;
}
if (requestResults == null) {
return false;
return false;
}
for (int i = 0; i < permissions.length; i++) {
final String permissionName = permissions[i];
@PermissionConstants.PermissionGroup final int permission =
PermissionUtils.parseManifestName(permissionName);
PermissionUtils.parseManifestName(permissionName);
if (permission == PermissionConstants.PERMISSION_GROUP_UNKNOWN)
continue;
......@@ -139,51 +164,55 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
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));
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));
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);
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);
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);
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);
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));
permission,
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
PermissionUtils.updatePermissionShouldShowStatus(this.activity, permission);
}
this.successCallback.onSuccess(requestResults);
ongoing = false;
pendingRequestCount -= grantResults.length;
// Post result if all requests have been handled.
if (pendingRequestCount == 0) {
this.successCallback.onSuccess(requestResults);
}
return true;
}
......@@ -202,27 +231,53 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
void onSuccess(boolean shouldShowRequestPermissionRationale);
}
private boolean ongoing = false;
void checkPermissionStatus(
@PermissionConstants.PermissionGroup int permission,
Context context,
CheckPermissionsSuccessCallback successCallback) {
@PermissionConstants.PermissionGroup int permission,
Context context,
CheckPermissionsSuccessCallback successCallback) {
successCallback.onSuccess(determinePermissionStatus(
permission,
context));
permission,
context));
}
/**
* Requests the user for the provided permissions.
* <p>
* This method will throw an error if it is called before all permission requests that were
* requested in a previous call have been resolved.
* <p>
* Android distinguishes between
* <a href="https://developer.android.com/guide/topics/permissions/overview#runtime">runtime permissions</a>
* and
* <a href="https://developer.android.com/guide/topics/permissions/overview#special">special permissions</a>.
* Runtime permissions give an app additional access to restricted data or let the app perform
* restricted actions that more substantially affect the system and other apps. These
* permissions present the user with a dialog where they can choose to grant or deny the
* permission. Special permissions guard access to system resources that are particularly
* sensitive or not directly related to user privacy. These permissions are requested by sending
* an {@link Intent} to the OS. The OS will open a special settings page where the user can
* grant the permission.
* Runtime permission request results will be reported through
* {@link this#onRequestPermissionsResult(int, String[], int[])}, while special permissions
* request results will be reported through {@link this#onActivityResult(int, int, Intent)}.
* When these methods receive request results, they check whether all permissions that were
* requested through this method were handled, and if so, return the result back to Dart.
*
* @param permissions the permissions that are requested.
* @param activity the activity.
* @param successCallback the callback for returning the permission results.
* @param errorCallback the callback to call in case of an error.
*/
void requestPermissions(
List<Integer> permissions,
Activity activity,
RequestPermissionsSuccessCallback successCallback,
ErrorCallback errorCallback) {
if (ongoing) {
List<Integer> permissions,
Activity activity,
RequestPermissionsSuccessCallback successCallback,
ErrorCallback errorCallback) {
if (pendingRequestCount > 0) {
errorCallback.onError(
"PermissionHandler.PermissionManager",
"A request for permissions is already running, please wait for it to finish before doing another request (note that you can request multiple permissions at the same time).");
"PermissionHandler.PermissionManager",
"A request for permissions is already running, please wait for it to finish before doing another request (note that you can request multiple permissions at the same time).");
return;
}
......@@ -230,14 +285,15 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity.");
errorCallback.onError(
"PermissionHandler.PermissionManager",
"Unable to detect current Android Activity.");
"PermissionHandler.PermissionManager",
"Unable to detect current Android Activity.");
return;
}
this.successCallback = successCallback;
this.activity = activity;
this.requestResults = new HashMap<>();
this.pendingRequestCount = 0; // sanity check
ArrayList<String> permissionsToRequest = new ArrayList<>();
for (Integer permission : permissions) {
......@@ -274,55 +330,56 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
continue;
}
// Request special permissions.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
executeIntent(
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS);
launchSpecialPermission(
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) {
executeIntent(
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE);
launchSpecialPermission(
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW) {
executeIntent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
PermissionConstants.PERMISSION_CODE_SYSTEM_ALERT_WINDOW);
launchSpecialPermission(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
PermissionConstants.PERMISSION_CODE_SYSTEM_ALERT_WINDOW);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && permission == PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES) {
executeIntent(
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
PermissionConstants.PERMISSION_CODE_REQUEST_INSTALL_PACKAGES);
launchSpecialPermission(
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
PermissionConstants.PERMISSION_CODE_REQUEST_INSTALL_PACKAGES);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY) {
executeSimpleIntent(
Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS,
PermissionConstants.PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY);
launchSpecialPermission(
Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS,
PermissionConstants.PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && permission == PermissionConstants.PERMISSION_GROUP_SCHEDULE_EXACT_ALARM) {
executeIntent(
Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM,
PermissionConstants.PERMISSION_CODE_SCHEDULE_EXACT_ALARM);
launchSpecialPermission(
Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM,
PermissionConstants.PERMISSION_CODE_SCHEDULE_EXACT_ALARM);
} else {
permissionsToRequest.addAll(names);
pendingRequestCount += names.size();
}
}
final String[] requestPermissions = permissionsToRequest.toArray(new String[0]);
// Request runtime permissions.
if (permissionsToRequest.size() > 0) {
ongoing = true;
final String[] requestPermissions = permissionsToRequest.toArray(new String[0]);
ActivityCompat.requestPermissions(
activity,
requestPermissions,
PermissionConstants.PERMISSION_CODE);
} else {
ongoing = false;
if (requestResults.size() > 0) {
successCallback.onSuccess(requestResults);
}
activity,
requestPermissions,
PermissionConstants.PERMISSION_CODE);
}
// Post results immediately if no requests are pending.
if (pendingRequestCount == 0) {
this.successCallback.onSuccess(requestResults);
}
}
@PermissionConstants.PermissionStatus
private int determinePermissionStatus(
@PermissionConstants.PermissionGroup int permission,
Context context) {
@PermissionConstants.PermissionGroup int permission,
Context context) {
if (permission == PermissionConstants.PERMISSION_GROUP_NOTIFICATION) {
return checkNotificationPermissionStatus(context);
......@@ -333,8 +390,8 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
}
if (permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH_CONNECT
|| permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH_SCAN
|| permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH_ADVERTISE){
|| permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH_SCAN
|| permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH_ADVERTISE) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return checkBluetoothPermissionStatus(context);
}
......@@ -399,23 +456,23 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
}
return Environment.isExternalStorageManager()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
}
if (permission == PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Settings.canDrawOverlays(context)
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
}
}
if (permission == PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return context.getPackageManager().canRequestPackageInstalls()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
}
}
......@@ -423,8 +480,8 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Application.NOTIFICATION_SERVICE);
return notificationManager.isNotificationPolicyAccessGranted()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
}
}
......@@ -432,8 +489,8 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
return alarmManager.canScheduleExactAlarms()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
} else {
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
......@@ -448,29 +505,37 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
private void executeIntent(String action, int requestCode) {
String packageName = activity.getPackageName();
Intent intent = new Intent();
intent.setAction(action);
intent.setData(Uri.parse("package:" + packageName));
/**
* Launches a request for a <a href="https://developer.android.com/training/permissions/requesting-special">special permission</a>.
* <p>
* There is a special case for {@link Settings#ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS}. See
* <a href="https://github.com/Baseflow/flutter-permission-handler/pull/587#discussion_r649295489">this comment</a>
* for more details.
*
* @param permissionAction the action for launching the settings page for a particular permission.
* @param requestCode a request code to verify incoming results.
*/
private void launchSpecialPermission(String permissionAction, int requestCode) {
Intent intent = new Intent(permissionAction);
if (!permissionAction.equals(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)) {
String packageName = activity.getPackageName();
intent.setData(Uri.parse("package:" + packageName));
}
activity.startActivityForResult(intent, requestCode);
}
private void executeSimpleIntent(String action, int requestCode) {
activity.startActivityForResult(new Intent(action), requestCode);
pendingRequestCount++;
}
void shouldShowRequestPermissionRationale(
int permission,
Activity activity,
ShouldShowRequestPermissionRationaleSuccessCallback successCallback,
ErrorCallback errorCallback) {
int permission,
Activity activity,
ShouldShowRequestPermissionRationaleSuccessCallback successCallback,
ErrorCallback errorCallback) {
if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity.");
errorCallback.onError(
"PermissionHandler.PermissionManager",
"Unable to detect current Android Activity.");
"PermissionHandler.PermissionManager",
"Unable to detect current Android Activity.");
return;
}
......
name: permission_handler_android
description: Permission plugin for Flutter. This plugin provides the Android API to request and check permissions.
homepage: https://github.com/baseflow/flutter-permission-handler
version: 10.3.5
version: 10.3.6
environment:
sdk: ">=2.15.0 <4.0.0"
......
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