Commit 7460789d by Maurits van Beusekom

Production version 4.4.0

parents 95158f7a 3a1a7dd8
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>permission_handler</name>
<comment>Project permission_handler created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>
package com.baseflow.permissionhandler;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.PowerManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationManagerCompat;
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import androidx.annotation.IntDef;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
public class PermissionHandlerPlugin implements MethodCallHandler {
private static final String LOG_TAG = "permissions_handler";
private static final int PERMISSION_CODE = 24;
private static final int PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS = 5672353;
//PERMISSION_GROUP
private static final int PERMISSION_GROUP_CALENDAR = 0;
private static final int PERMISSION_GROUP_CAMERA = 1;
private static final int PERMISSION_GROUP_CONTACTS = 2;
private static final int PERMISSION_GROUP_LOCATION = 3;
private static final int PERMISSION_GROUP_LOCATION_ALWAYS = 4;
private static final int PERMISSION_GROUP_LOCATION_WHEN_IN_USE = 5;
private static final int PERMISSION_GROUP_MEDIA_LIBRARY = 6;
private static final int PERMISSION_GROUP_MICROPHONE = 7;
private static final int PERMISSION_GROUP_PHONE = 8;
private static final int PERMISSION_GROUP_PHOTOS = 9;
private static final int PERMISSION_GROUP_REMINDERS = 10;
private static final int PERMISSION_GROUP_SENSORS = 11;
private static final int PERMISSION_GROUP_SMS = 12;
private static final int PERMISSION_GROUP_SPEECH = 13;
private static final int PERMISSION_GROUP_STORAGE = 14;
private static final int PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS = 15;
private static final int PERMISSION_GROUP_NOTIFICATION = 16;
private static final int PERMISSION_GROUP_ACCESS_MEDIA_LOCATION = 17;
private static final int PERMISSION_GROUP_ACTIVITY_RECOGNITION = 18;
private static final int PERMISSION_GROUP_UNKNOWN = 19;
private PermissionHandlerPlugin(Registrar mRegistrar) {
this.mRegistrar = mRegistrar;
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PERMISSION_GROUP_CALENDAR,
PERMISSION_GROUP_CAMERA,
PERMISSION_GROUP_CONTACTS,
PERMISSION_GROUP_LOCATION,
PERMISSION_GROUP_LOCATION_ALWAYS,
PERMISSION_GROUP_LOCATION_WHEN_IN_USE,
PERMISSION_GROUP_MEDIA_LIBRARY,
PERMISSION_GROUP_MICROPHONE,
PERMISSION_GROUP_PHONE,
PERMISSION_GROUP_PHOTOS,
PERMISSION_GROUP_REMINDERS,
PERMISSION_GROUP_SENSORS,
PERMISSION_GROUP_SMS,
PERMISSION_GROUP_SPEECH,
PERMISSION_GROUP_STORAGE,
PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS,
PERMISSION_GROUP_NOTIFICATION,
PERMISSION_GROUP_ACCESS_MEDIA_LOCATION,
PERMISSION_GROUP_ACTIVITY_RECOGNITION,
PERMISSION_GROUP_UNKNOWN,
})
private @interface PermissionGroup {
}
//PERMISSION_STATUS
private static final int PERMISSION_STATUS_DENIED = 0;
private static final int PERMISSION_STATUS_GRANTED = 1;
private static final int PERMISSION_STATUS_RESTRICTED = 2;
private static final int PERMISSION_STATUS_UNKNOWN = 3;
private static final int PERMISSION_STATUS_NEWER_ASK_AGAIN = 4;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PERMISSION_STATUS_DENIED,
PERMISSION_STATUS_GRANTED,
PERMISSION_STATUS_RESTRICTED,
PERMISSION_STATUS_UNKNOWN,
PERMISSION_STATUS_NEWER_ASK_AGAIN,
})
private @interface PermissionStatus {
}
//SERVICE_STATUS
private static final int SERVICE_STATUS_DISABLED = 0;
private static final int SERVICE_STATUS_ENABLED = 1;
private static final int SERVICE_STATUS_NOT_APPLICABLE = 2;
private static final int SERVICE_STATUS_UNKNOWN = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
SERVICE_STATUS_DISABLED,
SERVICE_STATUS_ENABLED,
SERVICE_STATUS_NOT_APPLICABLE,
SERVICE_STATUS_UNKNOWN,
})
private @interface ServiceStatus {
}
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter.baseflow.com/permissions/methods");
final PermissionHandlerPlugin permissionHandlerPlugin = new PermissionHandlerPlugin(registrar);
channel.setMethodCallHandler(permissionHandlerPlugin);
registrar.addRequestPermissionsResultListener(new PluginRegistry.RequestPermissionsResultListener() {
@Override
public boolean onRequestPermissionsResult(int id, String[] permissions, int[] grantResults) {
if (id == PERMISSION_CODE) {
permissionHandlerPlugin.handlePermissionsRequest(permissions, grantResults);
return true;
} else {
return false;
}
}
});
registrar.addActivityResultListener(new ActivityResultListener() {
@Override
public boolean onActivityResult(int requestCode, int responseCode, Intent intent) {
if (requestCode == PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS) {
permissionHandlerPlugin.handleIgnoreBatteryOptimizationsRequest(responseCode == Activity.RESULT_OK);
return true;
}
return false;
}
});
}
@PermissionGroup
private static int parseManifestName(String permission) {
switch (permission) {
case Manifest.permission.READ_CALENDAR:
case Manifest.permission.WRITE_CALENDAR:
return PERMISSION_GROUP_CALENDAR;
case Manifest.permission.CAMERA:
return PERMISSION_GROUP_CAMERA;
case Manifest.permission.READ_CONTACTS:
case Manifest.permission.WRITE_CONTACTS:
case Manifest.permission.GET_ACCOUNTS:
return PERMISSION_GROUP_CONTACTS;
case Manifest.permission.ACCESS_BACKGROUND_LOCATION:
return PERMISSION_GROUP_LOCATION_ALWAYS;
case Manifest.permission.ACCESS_COARSE_LOCATION:
case Manifest.permission.ACCESS_FINE_LOCATION:
return PERMISSION_GROUP_LOCATION;
case Manifest.permission.RECORD_AUDIO:
return PERMISSION_GROUP_MICROPHONE;
case Manifest.permission.READ_PHONE_STATE:
case Manifest.permission.CALL_PHONE:
case Manifest.permission.READ_CALL_LOG:
case Manifest.permission.WRITE_CALL_LOG:
case Manifest.permission.ADD_VOICEMAIL:
case Manifest.permission.USE_SIP:
case Manifest.permission.BIND_CALL_REDIRECTION_SERVICE:
return PERMISSION_GROUP_PHONE;
case Manifest.permission.BODY_SENSORS:
return PERMISSION_GROUP_SENSORS;
case Manifest.permission.SEND_SMS:
case Manifest.permission.RECEIVE_SMS:
case Manifest.permission.READ_SMS:
case Manifest.permission.RECEIVE_WAP_PUSH:
case Manifest.permission.RECEIVE_MMS:
return PERMISSION_GROUP_SMS;
case Manifest.permission.READ_EXTERNAL_STORAGE:
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
return PERMISSION_GROUP_STORAGE;
case Manifest.permission.ACCESS_MEDIA_LOCATION:
return PERMISSION_GROUP_ACCESS_MEDIA_LOCATION;
case Manifest.permission.ACTIVITY_RECOGNITION:
return PERMISSION_GROUP_ACTIVITY_RECOGNITION;
default:
return PERMISSION_GROUP_UNKNOWN;
}
}
private final Registrar mRegistrar;
private Result mResult;
private ArrayList<String> mRequestedPermissions;
@SuppressLint("UseSparseArrays")
private Map<Integer, Integer> mRequestResults = new HashMap<>();
@Override
public void onMethodCall(MethodCall call, @NonNull Result result) {
switch (call.method) {
case "checkPermissionStatus": {
@PermissionGroup final int permission = (int) call.arguments;
@PermissionStatus final int permissionStatus = checkPermissionStatus(permission);
result.success(permissionStatus);
break;
}
case "checkServiceStatus": {
@PermissionGroup final int permission = (int) call.arguments;
@ServiceStatus final int serviceStatus = checkServiceStatus(permission);
result.success(serviceStatus);
break;
}
case "requestPermissions":
if (mResult != null) {
result.error(
"ERROR_ALREADY_REQUESTING_PERMISSIONS",
"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).",
null);
return;
}
mResult = result;
final List<Integer> permissions = call.arguments();
requestPermissions(permissions);
break;
case "shouldShowRequestPermissionRationale": {
@PermissionGroup final int permission = (int) call.arguments;
result.success(shouldShowRequestPermissionRationale(permission));
break;
}
case "openAppSettings":
boolean isOpen = openAppSettings();
result.success(isOpen);
break;
default:
result.notImplemented();
break;
}
}
@PermissionStatus
private int checkPermissionStatus(@PermissionGroup int permission) {
final Context context = mRegistrar.activity() == null ? mRegistrar.activeContext() : mRegistrar.activity();
if (context == null) {
Log.d(LOG_TAG, "Unable to detect current Activity or App Context.");
return PERMISSION_STATUS_UNKNOWN;
}
if (permission == PERMISSION_GROUP_NOTIFICATION) {
return checkNotificationPermissionStatus(context);
}
final List<String> names = getManifestNames(permission);
if (names == null) {
Log.d(LOG_TAG, "No android specific permissions needed for: " + permission);
return PERMISSION_STATUS_GRANTED;
}
//if no permissions were found then there is an issue and permission is not set in Android manifest
if (names.size() == 0) {
Log.d(LOG_TAG, "No permissions found in manifest for: " + permission);
return PERMISSION_STATUS_UNKNOWN;
}
final boolean targetsMOrHigher = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.M;
for (String name : names) {
// Only handle them if the client app actually targets a API level greater than M.
if (targetsMOrHigher) {
if (permission == PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
String packageName = context.getPackageName();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
// PowerManager.isIgnoringBatteryOptimizations has been included in Android M first.
if (VERSION.SDK_INT >= VERSION_CODES.M) {
if (pm != null && pm.isIgnoringBatteryOptimizations(packageName)) {
return PERMISSION_STATUS_GRANTED;
} else {
return PERMISSION_STATUS_DENIED;
}
} else {
return PERMISSION_STATUS_RESTRICTED;
}
}
final int permissionStatus = ContextCompat.checkSelfPermission(context, name);
if (permissionStatus == PackageManager.PERMISSION_DENIED) {
if (VERSION.SDK_INT >= VERSION_CODES.M && isNeverAskAgainSelected(permission)) {
return PERMISSION_STATUS_NEWER_ASK_AGAIN;
} else return PERMISSION_STATUS_DENIED;
} else if (permissionStatus != PackageManager.PERMISSION_GRANTED) {
return PERMISSION_STATUS_UNKNOWN;
}
}
}
return PERMISSION_STATUS_GRANTED;
}
@ServiceStatus
private int checkServiceStatus(int permission) {
final Context context = mRegistrar.activity() == null ? mRegistrar.activeContext() : mRegistrar.activity();
if (context == null) {
Log.d(LOG_TAG, "Unable to detect current Activity or App Context.");
return SERVICE_STATUS_UNKNOWN;
}
if (permission == PERMISSION_GROUP_LOCATION || permission == PERMISSION_GROUP_LOCATION_ALWAYS || permission == PERMISSION_GROUP_LOCATION_WHEN_IN_USE) {
return isLocationServiceEnabled(context) ? SERVICE_STATUS_ENABLED : SERVICE_STATUS_DISABLED;
}
if (permission == PERMISSION_GROUP_PHONE) {
PackageManager pm = context.getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return SERVICE_STATUS_NOT_APPLICABLE;
}
TelephonyManager telephonyManager = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager == null || telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
return SERVICE_STATUS_NOT_APPLICABLE;
}
Intent callIntent = new Intent(Intent.ACTION_CALL);
callIntent.setData(Uri.parse("tel:123123"));
List<ResolveInfo> callAppsList = pm.queryIntentActivities(callIntent, 0);
if (callAppsList.isEmpty()) {
return SERVICE_STATUS_NOT_APPLICABLE;
}
if (telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY) {
return SERVICE_STATUS_DISABLED;
}
return SERVICE_STATUS_ENABLED;
}
if (permission == PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? SERVICE_STATUS_ENABLED : SERVICE_STATUS_NOT_APPLICABLE;
}
return SERVICE_STATUS_NOT_APPLICABLE;
}
private boolean shouldShowRequestPermissionRationale(int permission) {
Activity activity = mRegistrar.activity();
if (activity == null) {
Log.d(LOG_TAG, "Unable to detect current Activity.");
return false;
}
List<String> names = getManifestNames(permission);
// if isn't an android specific group then go ahead and return false;
if (names == null) {
Log.d(LOG_TAG, "No android specific permissions needed for: " + permission);
return false;
}
if (names.isEmpty()) {
Log.d(LOG_TAG, "No permissions found in manifest for: " + permission + " no need to show request rationale");
return false;
}
//noinspection LoopStatementThatDoesntLoop
for (String name : names) {
return ActivityCompat.shouldShowRequestPermissionRationale(activity, name);
}
return false;
}
private void requestPermissions(List<Integer> permissions) {
if (mRegistrar.activity() == null) {
Log.d(LOG_TAG, "Unable to detect current Activity.");
for (Integer permission : permissions) {
mRequestResults.put(permission, PERMISSION_STATUS_UNKNOWN);
}
processResult();
return;
}
ArrayList<String> permissionsToRequest = new ArrayList<>();
for (Integer permission : permissions) {
@PermissionStatus final int permissionStatus = checkPermissionStatus(permission);
if (permissionStatus != PERMISSION_STATUS_GRANTED) {
final List<String> names = getManifestNames(permission);
//check to see if we can find manifest names
//if we can't add as unknown and continue
if (names == null || names.isEmpty()) {
if (!mRequestResults.containsKey(permission)) {
mRequestResults.put(permission, PERMISSION_STATUS_UNKNOWN);
}
continue;
}
if (VERSION.SDK_INT >= VERSION_CODES.M && permission == PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
String packageName = mRegistrar.context().getPackageName();
Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
mRegistrar.activity().startActivityForResult(intent, PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS);
} else {
permissionsToRequest.addAll(names);
}
} else {
if (!mRequestResults.containsKey(permission)) {
mRequestResults.put(permission, PERMISSION_STATUS_GRANTED);
}
}
}
final String[] requestPermissions = permissionsToRequest.toArray(new String[0]);
if (permissionsToRequest.size() > 0) {
ActivityCompat.requestPermissions(mRegistrar.activity(), requestPermissions, PERMISSION_CODE);
} else if (mRequestResults.size() > 0) {
processResult();
}
}
private void handlePermissionsRequest(String[] permissions, int[] grantResults) {
if (mResult == null) {
return;
}
for (int i = 0; i < permissions.length; i++) {
@PermissionGroup final int permission = parseManifestName(permissions[i]);
if (permission == PERMISSION_GROUP_UNKNOWN)
continue;
final int result = grantResults[i];
if (permission == PERMISSION_GROUP_MICROPHONE) {
if (!mRequestResults.containsKey(PERMISSION_GROUP_MICROPHONE)) {
mRequestResults.put(PERMISSION_GROUP_MICROPHONE, toPermissionStatus(permission, result));
}
if (!mRequestResults.containsKey(PERMISSION_GROUP_SPEECH)) {
mRequestResults.put(PERMISSION_GROUP_SPEECH, toPermissionStatus(permission, result));
}
} else if (permission == PERMISSION_GROUP_LOCATION_ALWAYS) {
@PermissionStatus int permissionStatus = toPermissionStatus(permission, result);
if (!mRequestResults.containsKey(PERMISSION_GROUP_LOCATION_ALWAYS)) {
mRequestResults.put(PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus);
}
} else if (permission == PERMISSION_GROUP_LOCATION) {
@PermissionStatus int permissionStatus = toPermissionStatus(permission, result);
if (VERSION.SDK_INT < VERSION_CODES.Q) {
if (!mRequestResults.containsKey(PERMISSION_GROUP_LOCATION_ALWAYS)) {
mRequestResults.put(PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus);
}
}
if (!mRequestResults.containsKey(PERMISSION_GROUP_LOCATION_WHEN_IN_USE)) {
mRequestResults.put(PERMISSION_GROUP_LOCATION_WHEN_IN_USE, permissionStatus);
}
mRequestResults.put(permission, permissionStatus);
} else if (!mRequestResults.containsKey(permission)) {
mRequestResults.put(permission, toPermissionStatus(permission, result));
}
updatePermissionShouldShowStatus(permission);
}
processResult();
}
private void handleIgnoreBatteryOptimizationsRequest(boolean granted) {
if (mResult == null) {
return;
}
int status = granted ? PERMISSION_STATUS_GRANTED : PERMISSION_STATUS_DENIED;
mRequestResults.put(PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS, status);
processResult();
}
@PermissionStatus
private int toPermissionStatus(@PermissionGroup int permission, int grantResult) {
if (grantResult == PackageManager.PERMISSION_DENIED) {
return VERSION.SDK_INT >= VERSION_CODES.M && isNeverAskAgainSelected(permission)
? PERMISSION_STATUS_NEWER_ASK_AGAIN
: PERMISSION_STATUS_DENIED;
}
return PERMISSION_STATUS_GRANTED;
}
private void processResult() {
mResult.success(mRequestResults);
mRequestResults.clear();
mResult = null;
}
private boolean openAppSettings() {
final Context context = mRegistrar.activity() == null ? mRegistrar.activeContext() : mRegistrar.activity();
if (context == null) {
Log.d(LOG_TAG, "Unable to detect current Activity or App Context.");
return false;
}
try {
Intent settingsIntent = new Intent();
settingsIntent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
settingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
settingsIntent.setData(android.net.Uri.parse("package:" + context.getPackageName()));
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(settingsIntent);
return true;
} catch (Exception ex) {
return false;
}
}
private List<String> getManifestNames(@PermissionGroup int permission) {
final ArrayList<String> permissionNames = new ArrayList<>();
switch (permission) {
case PERMISSION_GROUP_CALENDAR:
if (hasPermissionInManifest(Manifest.permission.READ_CALENDAR))
permissionNames.add(Manifest.permission.READ_CALENDAR);
if (hasPermissionInManifest(Manifest.permission.WRITE_CALENDAR))
permissionNames.add(Manifest.permission.WRITE_CALENDAR);
break;
case PERMISSION_GROUP_CAMERA:
if (hasPermissionInManifest(Manifest.permission.CAMERA))
permissionNames.add(Manifest.permission.CAMERA);
break;
case PERMISSION_GROUP_CONTACTS:
if (hasPermissionInManifest(Manifest.permission.READ_CONTACTS))
permissionNames.add(Manifest.permission.READ_CONTACTS);
if (hasPermissionInManifest(Manifest.permission.WRITE_CONTACTS))
permissionNames.add(Manifest.permission.WRITE_CONTACTS);
if (hasPermissionInManifest(Manifest.permission.GET_ACCOUNTS))
permissionNames.add(Manifest.permission.GET_ACCOUNTS);
break;
case PERMISSION_GROUP_LOCATION_ALWAYS:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (hasPermissionInManifest(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
case PERMISSION_GROUP_LOCATION_WHEN_IN_USE:
case PERMISSION_GROUP_LOCATION:
if (hasPermissionInManifest(Manifest.permission.ACCESS_COARSE_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_COARSE_LOCATION);
if (hasPermissionInManifest(Manifest.permission.ACCESS_FINE_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_FINE_LOCATION);
break;
case PERMISSION_GROUP_SPEECH:
case PERMISSION_GROUP_MICROPHONE:
if (hasPermissionInManifest(Manifest.permission.RECORD_AUDIO))
permissionNames.add(Manifest.permission.RECORD_AUDIO);
break;
case PERMISSION_GROUP_PHONE:
if (hasPermissionInManifest(Manifest.permission.READ_PHONE_STATE))
permissionNames.add(Manifest.permission.READ_PHONE_STATE);
if (hasPermissionInManifest(Manifest.permission.CALL_PHONE))
permissionNames.add(Manifest.permission.CALL_PHONE);
if (hasPermissionInManifest(Manifest.permission.READ_CALL_LOG))
permissionNames.add(Manifest.permission.READ_CALL_LOG);
if (hasPermissionInManifest(Manifest.permission.WRITE_CALL_LOG))
permissionNames.add(Manifest.permission.WRITE_CALL_LOG);
if (hasPermissionInManifest(Manifest.permission.ADD_VOICEMAIL))
permissionNames.add(Manifest.permission.ADD_VOICEMAIL);
if (hasPermissionInManifest(Manifest.permission.USE_SIP))
permissionNames.add(Manifest.permission.USE_SIP);
if (VERSION.SDK_INT >= VERSION_CODES.Q && hasPermissionInManifest(Manifest.permission.BIND_CALL_REDIRECTION_SERVICE))
permissionNames.add(Manifest.permission.BIND_CALL_REDIRECTION_SERVICE);
if (VERSION.SDK_INT >= VERSION_CODES.O && hasPermissionInManifest(Manifest.permission.ANSWER_PHONE_CALLS))
permissionNames.add(Manifest.permission.ANSWER_PHONE_CALLS);
break;
case PERMISSION_GROUP_SENSORS:
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT_WATCH) {
if (hasPermissionInManifest(Manifest.permission.BODY_SENSORS)) {
permissionNames.add(Manifest.permission.BODY_SENSORS);
}
}
break;
case PERMISSION_GROUP_SMS:
if (hasPermissionInManifest(Manifest.permission.SEND_SMS))
permissionNames.add(Manifest.permission.SEND_SMS);
if (hasPermissionInManifest(Manifest.permission.RECEIVE_SMS))
permissionNames.add(Manifest.permission.RECEIVE_SMS);
if (hasPermissionInManifest(Manifest.permission.READ_SMS))
permissionNames.add(Manifest.permission.READ_SMS);
if (hasPermissionInManifest(Manifest.permission.RECEIVE_WAP_PUSH))
permissionNames.add(Manifest.permission.RECEIVE_WAP_PUSH);
if (hasPermissionInManifest(Manifest.permission.RECEIVE_MMS))
permissionNames.add(Manifest.permission.RECEIVE_MMS);
break;
case PERMISSION_GROUP_STORAGE:
if (hasPermissionInManifest(Manifest.permission.READ_EXTERNAL_STORAGE))
permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (hasPermissionInManifest(Manifest.permission.WRITE_EXTERNAL_STORAGE))
permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
break;
case PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS:
if (VERSION.SDK_INT >= VERSION_CODES.M && hasPermissionInManifest(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS))
permissionNames.add(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
break;
case PERMISSION_GROUP_ACCESS_MEDIA_LOCATION:
if (VERSION.SDK_INT >= VERSION_CODES.Q && hasPermissionInManifest(Manifest.permission.ACCESS_MEDIA_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
break;
case PERMISSION_GROUP_ACTIVITY_RECOGNITION:
if (VERSION.SDK_INT >= VERSION_CODES.Q && hasPermissionInManifest(Manifest.permission.ACTIVITY_RECOGNITION))
permissionNames.add(Manifest.permission.ACTIVITY_RECOGNITION);
break;
case PERMISSION_GROUP_NOTIFICATION:
case PERMISSION_GROUP_MEDIA_LIBRARY:
case PERMISSION_GROUP_PHOTOS:
case PERMISSION_GROUP_REMINDERS:
case PERMISSION_GROUP_UNKNOWN:
return null;
}
return permissionNames;
}
private boolean hasPermissionInManifest(String permission) {
try {
if (mRequestedPermissions != null) {
for (String r : mRequestedPermissions) {
if (r.equals(permission)) {
return true;
}
}
}
final Context context = mRegistrar.activity() == null ? mRegistrar.activeContext() : mRegistrar.activity();
if (context == null) {
Log.d(LOG_TAG, "Unable to detect current Activity or App Context.");
return false;
}
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
if (info == null) {
Log.d(LOG_TAG, "Unable to get Package info, will not be able to determine permissions to request.");
return false;
}
mRequestedPermissions = new ArrayList<>(Arrays.asList(info.requestedPermissions));
for (String r : mRequestedPermissions) {
if (r.equals(permission)) {
return true;
}
}
} catch (Exception ex) {
Log.d(LOG_TAG, "Unable to check manifest for permission: ", ex);
}
return false;
}
private void updatePermissionShouldShowStatus(@PermissionGroup int permission) {
List<String> names = getManifestNames(permission);
if (names == null || names.isEmpty()) {
return;
}
final Context context = mRegistrar.activity() == null ? mRegistrar.activeContext() : mRegistrar.activity();
if (context == null) {
return;
}
for (String name : names) {
PermissionUtils.setRequestedPermission(context, name);
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean isNeverAskAgainSelected(@PermissionGroup int permission) {
List<String> names = getManifestNames(permission);
if (names == null || names.isEmpty()) {
return false;
}
final Activity activity = mRegistrar.activity();
if (activity == null) {
return false;
}
boolean isNeverAskAgainSelected = false;
for (String name : names) {
isNeverAskAgainSelected |= PermissionUtils.neverAskAgainSelected(activity, name);
}
return isNeverAskAgainSelected;
}
private boolean isLocationServiceEnabled(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
final LocationManager locationManager = context.getSystemService(LocationManager.class);
if (locationManager == null) {
return false;
}
return locationManager.isLocationEnabled();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final int locationMode;
try {
locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
return false;
}
return locationMode != Settings.Secure.LOCATION_MODE_OFF;
} else {
final String locationProviders = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
return !TextUtils.isEmpty(locationProviders);
}
}
private int checkNotificationPermissionStatus(Context context) {
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
boolean isGranted = manager.areNotificationsEnabled();
if (isGranted) {
return PERMISSION_STATUS_GRANTED;
}
return PERMISSION_STATUS_DENIED;
}
}
package com.baseflow.permissionhandler;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
public class PermissionUtils {
@RequiresApi(api = Build.VERSION_CODES.M)
static boolean neverAskAgainSelected(final Activity activity, final String permission) {
final boolean hasRequestedPermissionBefore = getRequestedPermissionBefore(activity, permission);
final boolean shouldShowRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
return hasRequestedPermissionBefore && !shouldShowRequestPermissionRationale;
}
static void setRequestedPermission(final Context context, final String permission) {
SharedPreferences genPrefs = context.getSharedPreferences("GENERIC_PREFERENCES", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = genPrefs.edit();
editor.putBoolean(permission, true);
editor.apply();
}
private static boolean getRequestedPermissionBefore(final Context context, final String permission) {
SharedPreferences genPrefs = context.getSharedPreferences("GENERIC_PREFERENCES", Context.MODE_PRIVATE);
return genPrefs.getBoolean(permission, false);
}
}
<component name="libraryTable">
<library name="Dart SDK">
<CLASSES>
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/async" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/collection" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/convert" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/core" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/developer" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/html" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/io" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/isolate" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/math" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/mirrors" />
<root url="file:///Users/long1eu/IDE/flutter/bin/cache/dart-sdk/lib/typed_data" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="Flutter for Android">
<CLASSES>
<root url="jar:///Users/long1eu/IDE/flutter/bin/cache/artifacts/engine/android-arm/flutter.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/permission_handler_example.iml" filepath="$PROJECT_DIR$/permission_handler_example.iml" />
<module fileurl="file://$PROJECT_DIR$/android/permission_handler_example_android.iml" filepath="$PROJECT_DIR$/android/permission_handler_example_android.iml" />
</modules>
</component>
</project>
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method />
</configuration>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FileEditorManager">
<leaf>
<file leaf-file-name="main.dart" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/lib/main.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="ToolWindowManager">
<editor active="true" />
<layout>
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
</layout>
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
</navigator>
<panes>
<pane id="ProjectPane">
<option name="show-excluded-files" value="false" />
</pane>
</panes>
</component>
<component name="PropertiesComponent">
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="dart.analysis.tool.window.force.activate" value="true" />
<property name="show.migrate.to.gradle.popup" value="false" />
</component>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>android</name>
<comment>Project android created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.permissionhandlerexample">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Permissions options for the `contacts` group -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!-- Permissions options for the `storage` group -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Permissions options for the `camera` group -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- Permissions options for the `sms` group -->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!-- Permissions options for the `phone` group -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<!-- Permissions options for the `calendar` group -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<!-- Permissions options for the `location` group -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Permissions options for the `microphone` or `speech` group -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Permissions options for the `sensors` group -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- Permissions options for the `access_media_location` group -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
</manifest>
package com.example.permissionhandlerexample;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>
\ No newline at end of file
library permission_handler;
export 'src/permission_enums.dart';
export 'src/permission_handler.dart';
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
</component>
</module>
\ No newline at end of file
## 4.4.0
* Updated plugin structure to confirm to the Flutter federated plugin architecture. This will make it easier to add new platform implementations (see: https://medium.com/flutter/how-to-write-a-flutter-web-plugin-part-2-afdddb69ece6);
* Android: Migrate to FlutterPlugin Android API (better support for Add-to-App);
* Android: Suppress JAVA warnings which are generated to old platform code (only executes on older platforms);
* Android: Fixed issue which sometimes resulting in illegal cast exception.
## 4.3.0
* Allow requesting location permissions when location services are disabled (on iOS this will redirect the user to the Location settings page);
......
......@@ -17,7 +17,7 @@ To use this plugin, add `permission_handler` as a [dependency in your pubspec.ya
```yaml
dependencies:
permission_handler: '^4.3.0'
permission_handler: '^4.4.0'
```
> **NOTE:** As of version 3.1.0 the permission_handler plugin switched to the AndroidX version of the Android Support Libraries. This means you need to make sure your Android project is also upgraded to support AndroidX. Detailed instructions can be found [here](https://flutter.dev/docs/development/packages-and-plugins/androidx-compatibility).
......@@ -244,11 +244,21 @@ enum PermissionGroup {
/// Android: None
/// iOS: MPMediaLibrary
mediaLibrary
mediaLibrary,
/// Android: Check notification enable
/// iOS: Check and request notification permission
notification
notification,
/// Android Q: Check and request permissions to read from the media location (ACCESS_MEDIA_LOCATION)
/// Android pre-Q: Nothing
/// iOS: Nothing
accessMediaLocation,
/// Android Q: Check and request permissions to access the Activity Recognition API
/// Android pre-Q: Nothing
/// iOS: Nothing (should implement access to CMMotionActivity, see issue #219)
activityRecognition,
}
```
......@@ -261,9 +271,6 @@ enum PermissionStatus {
/// Permission to access the requested feature is denied by the user.
denied,
/// Permissions to access the feature is granted by the user but the feature is disabled.
disabled,
/// Permission to access the requested feature is granted by the user.
granted,
......@@ -305,7 +312,7 @@ Please file any issues, bugs or feature request as an issue on our [GitHub](http
## Want to contribute
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](CONTRIBUTING.md) and send us your [pull request](https://github.com/Baseflow/flutter-permission-handler/pulls).
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](../CONTRIBUTING.md) and send us your [pull request](https://github.com/Baseflow/flutter-permission-handler/pulls).
## Author
......
......@@ -32,6 +32,10 @@ android {
lintOptions {
disable 'InvalidPackage'
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
}
dependencies {
......
package com.baseflow.permissionhandler;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
final class AppSettingsManager {
boolean openAppSettings(Context applicationContext) {
if (applicationContext == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context.");
return false;
}
try {
Intent settingsIntent = new Intent();
settingsIntent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
settingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
settingsIntent.setData(android.net.Uri.parse("package:" + applicationContext.getPackageName()));
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
applicationContext.startActivity(settingsIntent);
return true;
} catch (Exception ex) {
return false;
}
}
}
package com.baseflow.permissionhandler;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
import com.baseflow.permissionhandler.PermissionManager.ActivityRegistry;
import com.baseflow.permissionhandler.PermissionManager.PermissionRegistry;
import java.util.List;
final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
private final Context applicationContext;
private final Activity activity;
private final AppSettingsManager appSettingsManager;
private final PermissionManager permissionManager;
private final ServiceManager serviceManager;
private final ActivityRegistry activityRegistry;
private final PermissionRegistry permissionRegistry;
private final MethodChannel methodChannel;
MethodCallHandlerImpl(
Context applicationContext,
Activity activity,
BinaryMessenger messenger,
AppSettingsManager appSettingsManager,
PermissionManager permissionManager,
ServiceManager serviceManager,
ActivityRegistry activityRegistry,
PermissionRegistry permissionRegistry) {
this.applicationContext = applicationContext;
this.activity = activity;
this.appSettingsManager = appSettingsManager;
this.permissionManager = permissionManager;
this.serviceManager = serviceManager;
this.activityRegistry = activityRegistry;
this.permissionRegistry = permissionRegistry;
methodChannel = new MethodChannel(
messenger,
"flutter.baseflow.com/permissions/methods");
methodChannel.setMethodCallHandler(this);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
{
switch (call.method) {
case "checkPermissionStatus": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
@PermissionConstants.PermissionStatus final int permissionStatus =
permissionManager.checkPermissionStatus(
permission,
activity);
result.success(permissionStatus);
break;
}
case "checkServiceStatus": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
@PermissionConstants.ServiceStatus final int serviceStatus =
serviceManager.checkServiceStatus(
permission,
activity);
result.success(serviceStatus);
break;
}
case "requestPermissions":
final List<Integer> permissions = call.arguments();
permissionManager.requestPermissions(
permissions,
activity,
activityRegistry,
permissionRegistry,
result::success,
(String errorCode, String errorDescription) -> {
result.error(errorCode, errorDescription, null);
});
break;
case "shouldShowRequestPermissionRationale": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
final boolean showRationale = permissionManager
.shouldShowRequestPermissionRationale(permission, activity);
result.success(showRationale);
break;
}
case "openAppSettings":
boolean isOpen = appSettingsManager.openAppSettings(applicationContext);
result.success(isOpen);
break;
default:
result.notImplemented();
break;
}
}
void stopListening() {
methodChannel.setMethodCallHandler(null);
}
}
package com.baseflow.permissionhandler;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
final class PermissionConstants {
static final String LOG_TAG = "permissions_handler";
static final int PERMISSION_CODE = 24;
static final int PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS = 5672353;
//PERMISSION_GROUP
static final int PERMISSION_GROUP_CALENDAR = 0;
static final int PERMISSION_GROUP_CAMERA = 1;
static final int PERMISSION_GROUP_CONTACTS = 2;
static final int PERMISSION_GROUP_LOCATION = 3;
static final int PERMISSION_GROUP_LOCATION_ALWAYS = 4;
static final int PERMISSION_GROUP_LOCATION_WHEN_IN_USE = 5;
static final int PERMISSION_GROUP_MEDIA_LIBRARY = 6;
static final int PERMISSION_GROUP_MICROPHONE = 7;
static final int PERMISSION_GROUP_PHONE = 8;
static final int PERMISSION_GROUP_PHOTOS = 9;
static final int PERMISSION_GROUP_REMINDERS = 10;
static final int PERMISSION_GROUP_SENSORS = 11;
static final int PERMISSION_GROUP_SMS = 12;
static final int PERMISSION_GROUP_SPEECH = 13;
static final int PERMISSION_GROUP_STORAGE = 14;
static final int PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS = 15;
static final int PERMISSION_GROUP_NOTIFICATION = 16;
static final int PERMISSION_GROUP_ACCESS_MEDIA_LOCATION = 17;
static final int PERMISSION_GROUP_ACTIVITY_RECOGNITION = 18;
static final int PERMISSION_GROUP_UNKNOWN = 19;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PERMISSION_GROUP_CALENDAR,
PERMISSION_GROUP_CAMERA,
PERMISSION_GROUP_CONTACTS,
PERMISSION_GROUP_LOCATION,
PERMISSION_GROUP_LOCATION_ALWAYS,
PERMISSION_GROUP_LOCATION_WHEN_IN_USE,
PERMISSION_GROUP_MEDIA_LIBRARY,
PERMISSION_GROUP_MICROPHONE,
PERMISSION_GROUP_PHONE,
PERMISSION_GROUP_PHOTOS,
PERMISSION_GROUP_REMINDERS,
PERMISSION_GROUP_SENSORS,
PERMISSION_GROUP_SMS,
PERMISSION_GROUP_SPEECH,
PERMISSION_GROUP_STORAGE,
PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS,
PERMISSION_GROUP_NOTIFICATION,
PERMISSION_GROUP_ACCESS_MEDIA_LOCATION,
PERMISSION_GROUP_ACTIVITY_RECOGNITION,
PERMISSION_GROUP_UNKNOWN,
})
@interface PermissionGroup {
}
//PERMISSION_STATUS
static final int PERMISSION_STATUS_DENIED = 0;
static final int PERMISSION_STATUS_GRANTED = 1;
static final int PERMISSION_STATUS_RESTRICTED = 2;
static final int PERMISSION_STATUS_UNKNOWN = 3;
static final int PERMISSION_STATUS_NEWER_ASK_AGAIN = 4;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PERMISSION_STATUS_DENIED,
PERMISSION_STATUS_GRANTED,
PERMISSION_STATUS_RESTRICTED,
PERMISSION_STATUS_UNKNOWN,
PERMISSION_STATUS_NEWER_ASK_AGAIN,
})
@interface PermissionStatus {
}
//SERVICE_STATUS
static final int SERVICE_STATUS_DISABLED = 0;
static final int SERVICE_STATUS_ENABLED = 1;
static final int SERVICE_STATUS_NOT_APPLICABLE = 2;
static final int SERVICE_STATUS_UNKNOWN = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
SERVICE_STATUS_DISABLED,
SERVICE_STATUS_ENABLED,
SERVICE_STATUS_NOT_APPLICABLE,
SERVICE_STATUS_UNKNOWN,
})
@interface ServiceStatus {
}
}
package com.baseflow.permissionhandler;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import com.baseflow.permissionhandler.PermissionManager.ActivityRegistry;
import com.baseflow.permissionhandler.PermissionManager.PermissionRegistry;
/**
* Platform implementation of the permission_handler Flutter plugin.
*
* <p>Instantiate this in an add to app scenario to gracefully handle activity and context changes.
* See {@code com.example.permissionhandlerexample.MainActivity} for an example.
*
* <p>Call {@link #registerWith(Registrar)} to register an implementation of this that uses the
* stable {@code io.flutter.plugin.common} package.
*/
public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAware {
private @Nullable FlutterPluginBinding flutterPluginBinding;
private @Nullable MethodCallHandlerImpl methodCallHandler;
/**
* Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common}
* package.
*
* <p>Calling this automatically initializes the plugin. However plugins initialized this way
* won't react to changes in activity or context, unlike {@link PermissionHandlerPlugin}.
*/
public static void registerWith(Registrar registrar) {
final PermissionHandlerPlugin permissionHandlerPlugin = new PermissionHandlerPlugin();
permissionHandlerPlugin.startListening(
registrar.context(),
registrar.activity(),
registrar.messenger(),
registrar::addActivityResultListener,
registrar::addRequestPermissionsResultListener
);
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
this.flutterPluginBinding = binding;
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
this.flutterPluginBinding = null;
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
if(flutterPluginBinding == null) {
return;
}
startListening(
flutterPluginBinding.getApplicationContext(),
binding.getActivity(),
flutterPluginBinding.getBinaryMessenger(),
binding::addActivityResultListener,
binding::addRequestPermissionsResultListener);
}
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}
@Override
public void onDetachedFromActivity() {
if (methodCallHandler == null) {
return;
}
methodCallHandler.stopListening();
methodCallHandler = null;
}
private void startListening(
Context applicationContext,
Activity activity,
BinaryMessenger messenger,
ActivityRegistry activityRegistry,
PermissionRegistry permissionRegistry) {
methodCallHandler = new MethodCallHandlerImpl(
applicationContext,
activity,
messenger,
new AppSettingsManager(),
new PermissionManager(),
new ServiceManager(),
activityRegistry,
permissionRegistry);
}
}
package com.baseflow.permissionhandler;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.PluginRegistry;
final class PermissionManager {
interface ActivityRegistry {
void addListener(PluginRegistry.ActivityResultListener handler);
}
interface PermissionRegistry {
void addListener(PluginRegistry.RequestPermissionsResultListener handler);
}
interface ResultCallback {
void onResult(Map<Integer, Integer> results);
}
interface ErrorCallback {
void onError(String errorCode, String errorDescription);
}
private boolean ongoing = false;
@PermissionConstants.PermissionStatus
int checkPermissionStatus(
@PermissionConstants.PermissionGroup int permission,
Activity activity) {
if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context.");
return PermissionConstants.PERMISSION_STATUS_UNKNOWN;
}
if (permission == PermissionConstants.PERMISSION_GROUP_NOTIFICATION) {
return checkNotificationPermissionStatus(activity);
}
final List<String> names = PermissionUtils.getManifestNames(activity, permission);
if (names == null) {
Log.d(PermissionConstants.LOG_TAG, "No android specific permissions needed for: " + permission);
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
//if no permissions were found then there is an issue and permission is not set in Android manifest
if (names.size() == 0) {
Log.d(PermissionConstants.LOG_TAG, "No permissions found in manifest for: " + permission);
return PermissionConstants.PERMISSION_STATUS_UNKNOWN;
}
final boolean targetsMOrHigher = activity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
for (String name : names) {
// Only handle them if the client app actually targets a API level greater than M.
if (targetsMOrHigher) {
if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
String packageName = activity.getPackageName();
PowerManager pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
// PowerManager.isIgnoringBatteryOptimizations has been included in Android M first.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (pm != null && pm.isIgnoringBatteryOptimizations(packageName)) {
return PermissionConstants.PERMISSION_STATUS_GRANTED;
} else {
return PermissionConstants.PERMISSION_STATUS_DENIED;
}
} else {
return PermissionConstants.PERMISSION_STATUS_RESTRICTED;
}
}
final int permissionStatus = ContextCompat.checkSelfPermission(activity, name);
if (permissionStatus == PackageManager.PERMISSION_DENIED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
PermissionUtils.isNeverAskAgainSelected(activity, permission)) {
return PermissionConstants.PERMISSION_STATUS_NEWER_ASK_AGAIN;
} else return PermissionConstants.PERMISSION_STATUS_DENIED;
} else if (permissionStatus != PackageManager.PERMISSION_GRANTED) {
return PermissionConstants.PERMISSION_STATUS_UNKNOWN;
}
}
}
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
void requestPermissions(
List<Integer> permissions,
Activity activity,
ActivityRegistry activityRegistry,
PermissionRegistry permissionRegistry,
ResultCallback resultCallback,
ErrorCallback errorCallback) {
if(ongoing) {
errorCallback.onError(
"ERROR_ALREADY_REQUESTING_PERMISSIONS",
"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).");
}
if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity.");
errorCallback.onError("ERROR_ANDROID_ACTIVITY_MISSING", "Unable to detect current Android Activity.");
return;
}
Map<Integer, Integer> requestResults = new HashMap<>();
ArrayList<String> permissionsToRequest = new ArrayList<>();
for (Integer permission : permissions) {
@PermissionConstants.PermissionStatus final int permissionStatus = checkPermissionStatus(permission, activity);
if (permissionStatus == PermissionConstants.PERMISSION_STATUS_GRANTED) {
if (!requestResults.containsKey(permission)) {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_GRANTED);
}
continue;
}
final List<String> names = PermissionUtils.getManifestNames(activity, permission);
// check to see if we can find manifest names
// if we can't add as unknown and continue
if (names == null || names.isEmpty()) {
if (!requestResults.containsKey(permission)) {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_UNKNOWN);
}
continue;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
activityRegistry.addListener(
new ActivityResultListener(resultCallback)
);
String packageName = activity.getPackageName();
Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
activity.startActivityForResult(intent, PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS);
} else {
permissionsToRequest.addAll(names);
}
}
final String[] requestPermissions = permissionsToRequest.toArray(new String[0]);
if (permissionsToRequest.size() > 0) {
permissionRegistry.addListener(
new RequestPermissionsListener(
activity,
requestResults,
(Map<Integer, Integer> results) -> {
ongoing = false;
resultCallback.onResult(results);
})
);
ongoing = true;
ActivityCompat.requestPermissions(
activity,
requestPermissions,
PermissionConstants.PERMISSION_CODE);
} else {
ongoing = false;
if (requestResults.size() > 0) {
resultCallback.onResult(requestResults);
}
}
}
boolean shouldShowRequestPermissionRationale(int permission, Activity activity) {
if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity.");
return false;
}
List<String> names = PermissionUtils.getManifestNames(activity, permission);
// if isn't an android specific group then go ahead and return false;
if (names == null) {
Log.d(PermissionConstants.LOG_TAG, "No android specific permissions needed for: " + permission);
return false;
}
if (names.isEmpty()) {
Log.d(PermissionConstants.LOG_TAG, "No permissions found in manifest for: " + permission + " no need to show request rationale");
return false;
}
return ActivityCompat.shouldShowRequestPermissionRationale(activity, names.get(0));
}
private int checkNotificationPermissionStatus(Context context) {
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
boolean isGranted = manager.areNotificationsEnabled();
if (isGranted) {
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
return PermissionConstants.PERMISSION_STATUS_DENIED;
}
@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 ResultCallback callback;
@VisibleForTesting
ActivityResultListener(ResultCallback 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;
callback.onResult(new HashMap<>(PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS, status));
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 ResultCallback callback;
final Map<Integer, Integer> requestResults;
@VisibleForTesting
RequestPermissionsListener(
Activity activity,
Map<Integer, Integer> requestResults,
ResultCallback 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++) {
@PermissionConstants.PermissionGroup final int permission =
PermissionUtils.parseManifestName(permissions[i]);
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, permission, result));
}
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_SPEECH)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_SPEECH,
PermissionUtils.toPermissionStatus(this.activity, permission, result));
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS) {
@PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permission, 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, permission, 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, permission, result));
}
PermissionUtils.updatePermissionShouldShowStatus(this.activity, permission);
}
this.callback.onResult(requestResults);
return true;
}
}
}
package com.baseflow.permissionhandler;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PermissionUtils {
@PermissionConstants.PermissionGroup
static int parseManifestName(String permission) {
switch (permission) {
case Manifest.permission.READ_CALENDAR:
case Manifest.permission.WRITE_CALENDAR:
return PermissionConstants.PERMISSION_GROUP_CALENDAR;
case Manifest.permission.CAMERA:
return PermissionConstants.PERMISSION_GROUP_CAMERA;
case Manifest.permission.READ_CONTACTS:
case Manifest.permission.WRITE_CONTACTS:
case Manifest.permission.GET_ACCOUNTS:
return PermissionConstants.PERMISSION_GROUP_CONTACTS;
case Manifest.permission.ACCESS_BACKGROUND_LOCATION:
return PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS;
case Manifest.permission.ACCESS_COARSE_LOCATION:
case Manifest.permission.ACCESS_FINE_LOCATION:
return PermissionConstants.PERMISSION_GROUP_LOCATION;
case Manifest.permission.RECORD_AUDIO:
return PermissionConstants.PERMISSION_GROUP_MICROPHONE;
case Manifest.permission.READ_PHONE_STATE:
case Manifest.permission.CALL_PHONE:
case Manifest.permission.READ_CALL_LOG:
case Manifest.permission.WRITE_CALL_LOG:
case Manifest.permission.ADD_VOICEMAIL:
case Manifest.permission.USE_SIP:
case Manifest.permission.BIND_CALL_REDIRECTION_SERVICE:
return PermissionConstants.PERMISSION_GROUP_PHONE;
case Manifest.permission.BODY_SENSORS:
return PermissionConstants.PERMISSION_GROUP_SENSORS;
case Manifest.permission.SEND_SMS:
case Manifest.permission.RECEIVE_SMS:
case Manifest.permission.READ_SMS:
case Manifest.permission.RECEIVE_WAP_PUSH:
case Manifest.permission.RECEIVE_MMS:
return PermissionConstants.PERMISSION_GROUP_SMS;
case Manifest.permission.READ_EXTERNAL_STORAGE:
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
return PermissionConstants.PERMISSION_GROUP_STORAGE;
case Manifest.permission.ACCESS_MEDIA_LOCATION:
return PermissionConstants.PERMISSION_GROUP_ACCESS_MEDIA_LOCATION;
case Manifest.permission.ACTIVITY_RECOGNITION:
return PermissionConstants.PERMISSION_GROUP_ACTIVITY_RECOGNITION;
default:
return PermissionConstants.PERMISSION_GROUP_UNKNOWN;
}
}
static List<String> getManifestNames(Context context, @PermissionConstants.PermissionGroup int permission) {
final ArrayList<String> permissionNames = new ArrayList<>();
switch (permission) {
case PermissionConstants.PERMISSION_GROUP_CALENDAR:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CALENDAR))
permissionNames.add(Manifest.permission.READ_CALENDAR);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_CALENDAR))
permissionNames.add(Manifest.permission.WRITE_CALENDAR);
break;
case PermissionConstants.PERMISSION_GROUP_CAMERA:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.CAMERA))
permissionNames.add(Manifest.permission.CAMERA);
break;
case PermissionConstants.PERMISSION_GROUP_CONTACTS:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CONTACTS))
permissionNames.add(Manifest.permission.READ_CONTACTS);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_CONTACTS))
permissionNames.add(Manifest.permission.WRITE_CONTACTS);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.GET_ACCOUNTS))
permissionNames.add(Manifest.permission.GET_ACCOUNTS);
break;
case PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_BACKGROUND_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
case PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE:
case PermissionConstants.PERMISSION_GROUP_LOCATION:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_COARSE_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_COARSE_LOCATION);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_FINE_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_FINE_LOCATION);
break;
case PermissionConstants.PERMISSION_GROUP_SPEECH:
case PermissionConstants.PERMISSION_GROUP_MICROPHONE:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.RECORD_AUDIO))
permissionNames.add(Manifest.permission.RECORD_AUDIO);
break;
case PermissionConstants.PERMISSION_GROUP_PHONE:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_PHONE_STATE))
permissionNames.add(Manifest.permission.READ_PHONE_STATE);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.CALL_PHONE))
permissionNames.add(Manifest.permission.CALL_PHONE);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CALL_LOG))
permissionNames.add(Manifest.permission.READ_CALL_LOG);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_CALL_LOG))
permissionNames.add(Manifest.permission.WRITE_CALL_LOG);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ADD_VOICEMAIL))
permissionNames.add(Manifest.permission.ADD_VOICEMAIL);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.USE_SIP))
permissionNames.add(Manifest.permission.USE_SIP);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && hasPermissionInManifest(context, permissionNames, Manifest.permission.BIND_CALL_REDIRECTION_SERVICE))
permissionNames.add(Manifest.permission.BIND_CALL_REDIRECTION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPermissionInManifest(context, permissionNames, Manifest.permission.ANSWER_PHONE_CALLS))
permissionNames.add(Manifest.permission.ANSWER_PHONE_CALLS);
break;
case PermissionConstants.PERMISSION_GROUP_SENSORS:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.BODY_SENSORS)) {
permissionNames.add(Manifest.permission.BODY_SENSORS);
}
}
break;
case PermissionConstants.PERMISSION_GROUP_SMS:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.SEND_SMS))
permissionNames.add(Manifest.permission.SEND_SMS);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.RECEIVE_SMS))
permissionNames.add(Manifest.permission.RECEIVE_SMS);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_SMS))
permissionNames.add(Manifest.permission.READ_SMS);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.RECEIVE_WAP_PUSH))
permissionNames.add(Manifest.permission.RECEIVE_WAP_PUSH);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.RECEIVE_MMS))
permissionNames.add(Manifest.permission.RECEIVE_MMS);
break;
case PermissionConstants.PERMISSION_GROUP_STORAGE:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_EXTERNAL_STORAGE))
permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_EXTERNAL_STORAGE))
permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
break;
case PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && hasPermissionInManifest(context, permissionNames, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS))
permissionNames.add(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
break;
case PermissionConstants.PERMISSION_GROUP_ACCESS_MEDIA_LOCATION:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_MEDIA_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
break;
case PermissionConstants.PERMISSION_GROUP_ACTIVITY_RECOGNITION:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && hasPermissionInManifest(context, permissionNames, Manifest.permission.ACTIVITY_RECOGNITION))
permissionNames.add(Manifest.permission.ACTIVITY_RECOGNITION);
break;
case PermissionConstants.PERMISSION_GROUP_NOTIFICATION:
case PermissionConstants.PERMISSION_GROUP_MEDIA_LIBRARY:
case PermissionConstants.PERMISSION_GROUP_PHOTOS:
case PermissionConstants.PERMISSION_GROUP_REMINDERS:
case PermissionConstants.PERMISSION_GROUP_UNKNOWN:
return null;
}
return permissionNames;
}
private static boolean hasPermissionInManifest(Context context, ArrayList<String> confirmedPermissions, String permission) {
try {
if (confirmedPermissions != null) {
for (String r : confirmedPermissions) {
if (r.equals(permission)) {
return true;
}
}
}
if (context == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context.");
return false;
}
PackageInfo info = context
.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
if (info == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to get Package info, will not be able to determine permissions to request.");
return false;
}
confirmedPermissions = new ArrayList<>(Arrays.asList(info.requestedPermissions));
for (String r : confirmedPermissions) {
if (r.equals(permission)) {
return true;
}
}
} catch (Exception ex) {
Log.d(PermissionConstants.LOG_TAG, "Unable to check manifest for permission: ", ex);
}
return false;
}
@PermissionConstants.PermissionStatus
static int toPermissionStatus(final Activity activity, @PermissionConstants.PermissionGroup int permission, int grantResult) {
if (grantResult == PackageManager.PERMISSION_DENIED) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && PermissionUtils.isNeverAskAgainSelected(activity, permission)
? PermissionConstants.PERMISSION_STATUS_NEWER_ASK_AGAIN
: PermissionConstants.PERMISSION_STATUS_DENIED;
}
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
static void updatePermissionShouldShowStatus(final Activity activity, @PermissionConstants.PermissionGroup int permission) {
if (activity == null) {
return;
}
List<String> names = getManifestNames(activity, permission);
if (names == null || names.isEmpty()) {
return;
}
for (String name : names) {
PermissionUtils.setRequestedPermission(activity, name);
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
static boolean isNeverAskAgainSelected(final Activity activity, @PermissionConstants.PermissionGroup int permission) {
if (activity == null) {
return false;
}
List<String> names = getManifestNames(activity, permission);
if (names == null || names.isEmpty()) {
return false;
}
boolean isNeverAskAgainSelected = false;
for (String name : names) {
isNeverAskAgainSelected |= PermissionUtils.neverAskAgainSelected(activity, name);
}
return isNeverAskAgainSelected;
}
@RequiresApi(api = Build.VERSION_CODES.M)
static boolean neverAskAgainSelected(final Activity activity, final String permission) {
final boolean hasRequestedPermissionBefore = getRequestedPermissionBefore(activity, permission);
final boolean shouldShowRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
return hasRequestedPermissionBefore && !shouldShowRequestPermissionRationale;
}
static void setRequestedPermission(final Context context, final String permission) {
SharedPreferences genPrefs = context.getSharedPreferences("GENERIC_PREFERENCES", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = genPrefs.edit();
editor.putBoolean(permission, true);
editor.apply();
}
private static boolean getRequestedPermissionBefore(final Context context, final String permission) {
SharedPreferences genPrefs = context.getSharedPreferences("GENERIC_PREFERENCES", Context.MODE_PRIVATE);
return genPrefs.getBoolean(permission, false);
}
}
package com.baseflow.permissionhandler;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import java.util.List;
final class ServiceManager {
@PermissionConstants.ServiceStatus
int checkServiceStatus(
int permission,
Activity activity) {
if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context.");
return PermissionConstants.SERVICE_STATUS_UNKNOWN;
}
if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION ||
permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS ||
permission == PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE) {
return isLocationServiceEnabled(activity)
? PermissionConstants.SERVICE_STATUS_ENABLED
: PermissionConstants.SERVICE_STATUS_DISABLED;
}
if (permission == PermissionConstants.PERMISSION_GROUP_PHONE) {
PackageManager pm = activity.getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
TelephonyManager telephonyManager = (TelephonyManager) activity
.getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager == null || telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
Intent callIntent = new Intent(Intent.ACTION_CALL);
callIntent.setData(Uri.parse("tel:123123"));
List<ResolveInfo> callAppsList = pm.queryIntentActivities(callIntent, 0);
if (callAppsList.isEmpty()) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
if (telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY) {
return PermissionConstants.SERVICE_STATUS_DISABLED;
}
return PermissionConstants.SERVICE_STATUS_ENABLED;
}
if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
? PermissionConstants.SERVICE_STATUS_ENABLED
: PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
private boolean isLocationServiceEnabled(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
final LocationManager locationManager = context.getSystemService(LocationManager.class);
if (locationManager == null) {
return false;
}
return locationManager.isLocationEnabled();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return isLocationServiceEnablePrePie(context);
} else {
return isLocationServiceEnablePreKitKat(context);
}
}
// Suppress deprecation warnings since it's purpose is to support to be backwards compatible with
// pre Pie versions of Android.
@SuppressWarnings("deprecation")
private static boolean isLocationServiceEnablePrePie(Context context)
{
if (VERSION.SDK_INT < VERSION_CODES.P)
return false;
final int locationMode;
try {
locationMode = Settings.Secure.getInt(
context.getContentResolver(),
Settings.Secure.LOCATION_MODE);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
return false;
}
return locationMode != Settings.Secure.LOCATION_MODE_OFF;
}
// Suppress deprecation warnings since it's purpose is to support to be backwards compatible with
// pre KitKat versions of Android.
@SuppressWarnings("deprecation")
private static boolean isLocationServiceEnablePreKitKat(Context context)
{
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT)
return false;
final String locationProviders = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
return !TextUtils.isEmpty(locationProviders);
}
}
......@@ -51,28 +51,30 @@
<!-- Permissions options for the `sensors` group -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- Permissions options for the `access_media_location` group -->
<!-- Permissions options for the `accessMediaLocation` group -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- Permissions options for the `activityRecognition` group -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<application
android:name="io.flutter.app.FlutterApplication"
android:icon="@mipmap/ic_launcher"
android:label="permission_handler_example"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
<activity android:name="io.flutter.embedding.android.FlutterActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
......@@ -3,7 +3,7 @@
#
Pod::Spec.new do |s|
s.name = 'permission_handler'
s.version = '4.3.0'
s.version = '4.4.0'
s.summary = 'Permission plugin for Flutter.'
s.description = <<-DESC
Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
......
import 'dart:async';
import 'dart:io';
import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart';
export 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart'
show PermissionGroup, PermissionStatus, ServiceStatus;
/// The Android and iOS implementation of [PermissionHandlerPlatform].
///
/// This class implements the `package:permission_handler` functionality for
/// the Android and iOS platforms.
class PermissionHandler extends PermissionHandlerPlatform {
@override
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) {
return PermissionHandlerPlatform.instance.checkServiceStatus(permission);
}
@override
Future<PermissionStatus> checkPermissionStatus(PermissionGroup permission) {
return PermissionHandlerPlatform.instance.checkPermissionStatus(permission);
}
@override
Future<bool> openAppSettings() {
return PermissionHandlerPlatform.instance.openAppSettings();
}
@override
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) {
return PermissionHandlerPlatform.instance.requestPermissions(permissions);
}
@override
Future<bool> shouldShowRequestPermissionRationale(
PermissionGroup permission) {
if(!Platform.isAndroid) {
return Future.value(false);
}
return PermissionHandlerPlatform.instance
.shouldShowRequestPermissionRationale(permission);
}
}
name: permission_handler
description: Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
version: 4.3.0
version: 4.4.0
homepage: https://github.com/baseflowit/flutter-permission-handler
environment:
......@@ -9,13 +9,18 @@ environment:
dependencies:
flutter:
sdk: flutter
meta: ^1.1.6
permission_handler_platform_interface: ^1.0.0
dev_dependencies:
effective_dart: ^1.2.1
plugin_platform_interface: ^1.0.1
flutter:
plugin:
androidPackage: com.baseflow.permissionhandler
pluginClass: PermissionHandlerPlugin
platforms:
android:
package: com.baseflow.permissionhandler
pluginClass: PermissionHandlerPlugin
ios:
pluginClass: PermissionHandlerPlugin
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/android/gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/android/gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/android/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/android/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/android/assets" />
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/android/libs" />
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/android/proguard_logs" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/android">
<sourceFolder url="file://$MODULE_DIR$/android/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/android/gen" isTestSource="false" generated="true" />
</content>
<content url="file://$MODULE_DIR$/example/android">
<sourceFolder url="file://$MODULE_DIR$/example/android/app/src/main/java" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Flutter for Android" level="project" />
</component>
</module>
## 1.0.0
- Initial open-source release.
\ No newline at end of file
MIT License
Copyright (c) 2018 Baseflow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# permission_handler_platform_interface
[![pub package](https://img.shields.io/pub/v/permission_handler.svg)](https://pub.dartlang.org/packages/permission_handler) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://github.com/tenhobi/effective_dart)
A common platform interface for the [`permission_handler`][1] plugin.
This interface allows platform-specific implementations of the
`permission_handler` plugin, as well as the plugin itself, to ensure they are
supporting the same interface.
# Usage
To implement a new platform-specific implementation of `permission_handler`,
extend [`PermissionHandlerPlatform`][2] with an implementation that performs
the platform-specific behavior, and when you register your plugin, set the
default `PermissionHandlerPlatform` by calling
`PermissionHandlerPlatform.instance = MyPlatformPermissionHandler()`.
# Issues
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Baseflow/flutter-permission-handler/issues) page.
# Want to contribute
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](../CONTRIBUTING.md) and send us your [pull request](https://github.com/Baseflow/flutter-permission-handler/pulls).
## Note on breaking changes
Strongly prefer non-breaking changes (such as adding a method to the interface)
over breaking changes for this package.
See https://flutter.dev/go/platform-interface-breaking-changes for a discussion
on why a less-clean interface is preferable to a breaking change.
# Author
This Permission handler plugin for Flutter is developed by [Baseflow](https://baseflow.com). You can contact us at <hello@baseflow.com>
[1]: ../permission_handler
[2]: lib/permission_handler_platform_interface.dart
\ No newline at end of file
library permission_handler_platform_interface;
import 'dart:async';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'src/method_channel/method_channel_permission_handler.dart';
part 'src/permission_handler_platform_interface.dart';
part 'src/permission_handler_enums.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'permission_enums.dart';
import 'utils/codec.dart';
/// Provides a cross-platform (iOS, Android) API to request and check
/// permissions.
class PermissionHandler {
/// Constructs a singleton instance of [Geolocator].
///
/// When a second instance is created, the first instance will not be able to
/// listen to the EventChannel because it is overridden. Forcing the class to
/// be a singleton class can prevent misuse of creating a second instance
/// from a programmer.
factory PermissionHandler() {
if (_instance == null) {
const methodChannel =
MethodChannel('flutter.baseflow.com/permissions/methods');
_instance = PermissionHandler.private(methodChannel);
}
return _instance;
}
/// This constructor is only used for testing and shouldn't be accessed by
/// users of the plugin.
@visibleForTesting
PermissionHandler.private(this._methodChannel);
static PermissionHandler _instance;
import '../../permission_handler_platform_interface.dart';
import 'utils/codec.dart';
final MethodChannel _methodChannel;
const MethodChannel _methodChannel =
MethodChannel('flutter.baseflow.com/permissions/methods');
/// An implementation of [PermissionHandlerPlatform] that uses method channels.
class MethodChannelPermissionHandler extends PermissionHandlerPlatform {
/// Check current permission status.
///
/// Returns a [Future] containing the current permission status for the
......@@ -105,10 +80,6 @@ class PermissionHandler {
/// returns [false].
Future<bool> shouldShowRequestPermissionRationale(
PermissionGroup permission) async {
if (!Platform.isAndroid) {
return false;
}
final shouldShowRationale = await _methodChannel.invokeMethod(
'shouldShowRequestPermissionRationale', permission.value);
......
import '../permission_enums.dart';
import '../../../permission_handler_platform_interface.dart';
/// Provides utility methods for encoding messages that are send on the Flutter
/// message channel.
......
part of permission_handler_platform_interface;
/// Defines the state of a permission group
class PermissionStatus {
const PermissionStatus._(this.value);
......
part of permission_handler_platform_interface;
/// The interface that implementations of permission_handler must implement.
///
/// Platform implementations should extend this class rather than implement it
/// as `permission_handler` does not consider newly added methods to be
/// breaking changes. Extending this class (using `extends`) ensures that the
/// subclass will get the default implementation, while platform
/// implementations that `implements` this interface will be broken by newly
/// added [PermissionHandlerPlatform] methods.
abstract class PermissionHandlerPlatform extends PlatformInterface {
/// Constructs a PermissionHandlerPlatform.
PermissionHandlerPlatform() : super(token: _token);
static final Object _token = Object();
static PermissionHandlerPlatform _instance = MethodChannelPermissionHandler();
/// The default instance of [PermissionHandlerPlatform] to use.
///
/// Defaults to [MethodChannelPermissionHandler].
static PermissionHandlerPlatform get instance => _instance;
/// Platform-specific plugins should set this with their own platform-specific
/// class that extends [PermissionHandlerPlatform] when they register themselves.
static set instance(PermissionHandlerPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// Check current permission status.
///
/// Returns a [Future] containing the current permission status for the
/// supplied [PermissionGroup].
Future<PermissionStatus> checkPermissionStatus(PermissionGroup permission) {
throw UnimplementedError(
'checkPermissionStatus() has not been implemented.');
}
/// Check current service status.
///
/// Returns a [Future] containing the current service status for the supplied
/// [PermissionGroup].
///
/// Notes about specific PermissionGroups:
/// - **PermissionGroup.phone**
/// - Android:
/// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the device lacks the TELEPHONY feature
/// 1. TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE
/// 1. when no Intents can be resolved to handle the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when:
/// 1. the SIM card is missing
/// - iOS:
/// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the native code can not find a handler for the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when:
/// 1. the mobile network code (MNC) is either 0 or 65535. See
/// https://stackoverflow.com/a/11595365 for details
/// - **PLEASE NOTE that this is still not a perfect indication** of the
/// devices' capability to place & connect phone calls
/// as it also depends on the network condition.
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) {
throw UnimplementedError('checkServiceStatus() has not been implemented.');
}
/// Open the App settings page.
///
/// Returns [true] if the app settings page could be opened,
/// otherwise [false] is returned.
Future<bool> openAppSettings() {
throw UnimplementedError('openAppSettings() has not been implemented.');
}
/// Request the user for access to the supplied list of permissiongroups.
///
/// Returns a [Map] containing the status per requested permissiongroup.
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) {
throw UnimplementedError('requestPermissions() has not been implemented.');
}
/// Request to see if you should show a rationale for requesting permission.
///
/// This method is only implemented on Android, calling this on iOS always
/// returns [false].
Future<bool> shouldShowRequestPermissionRationale(
PermissionGroup permission) {
throw UnimplementedError(
'shouldShowRequestPermissionRationale() has not been implemented.');
}
}
name: permission_handler_platform_interface
description: A common platform interface for the permission_handler plugin.
homepage: https://github.com/baseflow/flutter-permission-handler/tree/master/permission_handler_platform_interface
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
version: 1.0.0
dependencies:
flutter:
sdk: flutter
meta: ^1.0.5
plugin_platform_interface: ^1.0.1
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^4.1.1
effective_dart: ^1.2.1
environment:
sdk: ">=2.0.0-dev.28.0 <3.0.0"
flutter: ">=1.9.1+hotfix.4 <2.0.0"
\ No newline at end of file
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