Commit 679a01bf by Maurits van Beusekom Committed by GitHub

Merge pull request #232 from marcelgarus/develop

Make API more intuitive
parents 933a560e 057ba422
# Flutter Permission handler Plugin
[![pub package](https://img.shields.io/pub/v/permission_handler.svg)](https://pub.dartlang.org/packages/permission_handler) [![Build Status](https://app.bitrise.io/app/fa4f5d4bf452bcfb/status.svg?token=HorGpL_AOw2llYz39CjmdQ&branch=master)](https://app.bitrise.io/app/fa4f5d4bf452bcfb) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://github.com/tenhobi/effective_dart) [![pub package](https://img.shields.io/pub/v/permission_handler.svg)](https://pub.dartlang.org/packages/permission_handler) [![Build Status](https://app.bitrise.io/app/fa4f5d4bf452bcfb/status.svg?token=HorGpL_AOw2llYz39CjmdQ&branch=master)](https://app.bitrise.io/app/fa4f5d4bf452bcfb) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://github.com/tenhobi/effective_dart)
A permissions plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions. On most operating systems, permissions aren't just granted to apps at install time.
Rather, developers have to ask the user for permissions while the app is running.
## Features
* Check if a permission is granted. This plugin provides a cross-platform (iOS, Android) API to request permissions and check their status.
* Request permission for a specific feature. You can also open the device's app settings so users can grant a permission.
* Open app settings so the user can enable a permission. On Android, you can show a rationale for requesting a permission.
* Show a rationale for requesting permission (Android).
## Usage ## Setup
To use this plugin, add `permission_handler` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). For example: While the permissions are being requested during runtime, you'll still need to tell the OS which permissions your app might potentially use.
That requires adding permission configuration to Android- and iOS-specific files.
```yaml <details>
dependencies: <summary>Android</summary>
permission_handler: '^4.4.0+hotfix.1'
```
> **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). > The current version of the plugin requires AndroidX.
>
>The TL;DR version is:
> >
>1. Add the following to your "gradle.properties" file: > As of version 3.1.0 the <kbd>permission_handler</kbd> 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).
>
>```
>android.useAndroidX=true
>android.enableJetifier=true
>```
>2. Make sure you set the `compileSdkVersion` in your "android/app/build.gradle" file to 28:
>
>```
>android {
> compileSdkVersion 28
> >
> The TL;DR version is:
> 1. Add the following to your "gradle.properties" file:
> ```
> android.useAndroidX=true
> android.enableJetifier=true
> ```
> 2. Make sure you set the `compileSdkVersion` in your "android/app/build.gradle" file to 28:
> ```
> android {
> compileSdkVersion 28
> ... > ...
>} > }
>``` > ```
>3. Make sure you replace all the `android.` dependencies to their AndroidX counterparts (a full list can be found here: https://developer.android.com/jetpack/androidx/migrate). > 3. Make sure you replace all the `android.` dependencies to their AndroidX counterparts (a full list can be found here: https://developer.android.com/jetpack/androidx/migrate).
### Android and iOS specific permissions Add permissions to your `AndroidManifest.xml` file.
There's a `debug`, `main` and `profile` version which are chosen depending on how you start your app.
In general, it's sufficient to add permission only to the `main` version.
[Here]((https://github.com/Baseflow/flutter-permission-handler/blob/develop/example/android/app/src/main/AndroidManifest.xml))'s an example `AndroidManifest.xml` with a complete list of all possible permissions.
For this plugin to work you will have to add permission configuration to your `AndroidManifest.xml` (Android) and `Info.plist` (iOS) files. This will tell the platform which hardware or software features your app needs. Complete lists of these permission options can be found in our example app here: </details>
- [AndroidManifest.xml](https://github.com/Baseflow/flutter-permission-handler/blob/develop/example/android/app/src/main/AndroidManifest.xml) (note that there is a debug, main and profile version which are used depending on how you start your App. In general it is sufficient to add permissions only to the `main` version); <details>
- [Info.plist](https://github.com/Baseflow/flutter-permission-handler/blob/develop/example/ios/Runner/Info.plist) <summary>iOS</summary>
> IMPORTANT: ~~On iOS you will have to include all permission options when you want to submit your App.~~ This is because the `permission_handler` plugin touches all different SDKs and because the static code analyser (run by Apple upon App submission) detects this and will assert if it cannot find a matching permission option in the `Info.plist`. More information about this can be found [here](https://github.com/BaseflowIT/flutter-permission-handler/issues/26). Add permission to your `Info.plist` file.
[Here](https://github.com/Baseflow/flutter-permission-handler/blob/develop/example/ios/Runner/Info.plist)'s an example `Info.plist` with a complete list of all possible permissions.
On iOS, the permission_handler plugin use [macros](https://github.com/BaseflowIT/flutter-permission-handler/blob/develop/ios/Classes/PermissionHandlerEnums.h) to control whether a permission is supported. > IMPORTANT: ~~You will have to include all permission options when you want to submit your App.~~ This is because the `permission_handler` plugin touches all different SDKs and because the static code analyser (run by Apple upon App submission) detects this and will assert if it cannot find a matching permission option in the `Info.plist`. More information about this can be found [here](https://github.com/BaseflowIT/flutter-permission-handler/issues/26).
The <kbd>permission_handler</kbd> plugin use [macros](https://github.com/BaseflowIT/flutter-permission-handler/blob/develop/ios/Classes/PermissionHandlerEnums.h) to control whether a permission is supported.
By default, all the permissions listed [here](https://github.com/Baseflow/flutter-permission-handler#list-of-available-permissions) are supported. By default, all the permissions listed [here](https://github.com/Baseflow/flutter-permission-handler#list-of-available-permissions) are supported.
You can remove permissions you don't use by: You can remove permissions you don't use:
> 1. Add the following to your `Podfile` file: 1. Add the following to your `Podfile` file:
> ```ruby
> ```ruby post_install do |installer|
> post_install do |installer| installer.pods_project.targets.each do |target|
> installer.pods_project.targets.each do |target| target.build_configurations.each do |config|
> target.build_configurations.each do |config| ... # Here are some configurations automatically generated by flutter
> ... # Here are some configurations automatically generated by flutter
>
> # You can remove unused permissions here
> # for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/develop/ios/Classes/PermissionHandlerEnums.h
> # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
> config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
> '$(inherited)',
>
> ## dart: PermissionGroup.calendar
> # 'PERMISSION_EVENTS=0',
>
> ## dart: PermissionGroup.reminders
> # 'PERMISSION_REMINDERS=0',
>
> ## dart: PermissionGroup.contacts
> # 'PERMISSION_CONTACTS=0',
>
> ## dart: PermissionGroup.camera
> # 'PERMISSION_CAMERA=0',
>
> ## dart: PermissionGroup.microphone
> # 'PERMISSION_MICROPHONE=0',
>
> ## dart: PermissionGroup.speech
> # 'PERMISSION_SPEECH_RECOGNIZER=0',
>
> ## dart: PermissionGroup.photos
> # 'PERMISSION_PHOTOS=0',
>
> ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
> # 'PERMISSION_LOCATION=0',
>
> ## dart: PermissionGroup.notification
> # 'PERMISSION_NOTIFICATIONS=0',
>
> ## dart: PermissionGroup.mediaLibrary
> # 'PERMISSION_MEDIA_LIBRARY=0',
>
> ## dart: PermissionGroup.sensors
> # 'PERMISSION_SENSORS=0'
> ]
>
> end
> end
> end
> ```
>
> 2. Delete the corresponding permission description in `Info.plist`
>
> e.g. when you don't need camera permission, just delete 'NSCameraUsageDescription'
>
> The following lists the relationship between `Permission` and `The key of Info.plist`:
>
> | Permission | Info.plist | Macro |
> |---|---|---|
> | PermissionGroup.calendar | NSCalendarsUsageDescription | PERMISSION_EVENTS |
> | PermissionGroup.reminders | NSRemindersUsageDescription | PERMISSION_REMINDERS |
> | PermissionGroup.contacts | NSContactsUsageDescription | PERMISSION_CONTACTS |
> | PermissionGroup.camera | NSCameraUsageDescription | PERMISSION_CAMERA |
> | PermissionGroup.microphone | NSMicrophoneUsageDescription | PERMISSION_MICROPHONE |
> | PermissionGroup.speech | NSSpeechRecognitionUsageDescription | PERMISSION_SPEECH_RECOGNIZER |
> | PermissionGroup.photos | NSPhotoLibraryUsageDescription | PERMISSION_PHOTOS |
> | PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse | NSLocationUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationWhenInUseUsageDescription | PERMISSION_LOCATION |
> | PermissionGroup.notification | PermissionGroupNotification | PERMISSION_NOTIFICATIONS |
> | PermissionGroup.mediaLibrary | NSAppleMusicUsageDescription, kTCCServiceMediaLibrary | PERMISSION_MEDIA_LIBRARY |
> | PermissionGroup.sensors | NSMotionUsageDescription | PERMISSION_SENSORS |
>
> 3. Clean & Rebuild
## API # You can remove unused permissions here
# for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/develop/ios/Classes/PermissionHandlerEnums.h
# e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
### Requesting permission ## dart: PermissionGroup.calendar
# 'PERMISSION_EVENTS=0',
```dart ## dart: PermissionGroup.reminders
import 'package:permission_handler/permission_handler.dart'; # 'PERMISSION_REMINDERS=0',
Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler().requestPermissions([PermissionGroup.contacts]); ## dart: PermissionGroup.contacts
``` # 'PERMISSION_CONTACTS=0',
### Checking permission
```dart
import 'package:permission_handler/permission_handler.dart';
PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.contacts); ## dart: PermissionGroup.camera
``` # 'PERMISSION_CAMERA=0',
### Checking service status ## dart: PermissionGroup.microphone
# 'PERMISSION_MICROPHONE=0',
```dart ## dart: PermissionGroup.speech
import 'package:permission_handler/permission_handler.dart'; # 'PERMISSION_SPEECH_RECOGNIZER=0',
ServiceStatus serviceStatus = await PermissionHandler().checkServiceStatus(PermissionGroup.location); ## dart: PermissionGroup.photos
``` # 'PERMISSION_PHOTOS=0',
Checking the service status only makes sense for the `PermissionGroup.location` on Android and the `PermissionGroup.location`, `PermissionGroup.locationWhenInUse`, `PermissionGroup.locationAlways` or `PermissionGroup.sensors` on iOS. All other permission groups are not backed by a separate service and will always return `ServiceStatus.notApplicable`. ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
# 'PERMISSION_LOCATION=0',
### Open app settings ## dart: PermissionGroup.notification
# 'PERMISSION_NOTIFICATIONS=0',
```dart ## dart: PermissionGroup.mediaLibrary
import 'package:permission_handler/permission_handler.dart'; # 'PERMISSION_MEDIA_LIBRARY=0',
bool isOpened = await PermissionHandler().openAppSettings(); ## dart: PermissionGroup.sensors
``` # 'PERMISSION_SENSORS=0'
]
### Show a rationale for requesting permission (Android only) end
end
end
```
2. Delete the corresponding permission description in `Info.plist`
e.g. when you don't need camera permission, just delete 'NSCameraUsageDescription'
The following lists the relationship between `Permission` and `The key of Info.plist`:
| Permission | Info.plist | Macro |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| PermissionGroup.calendar | NSCalendarsUsageDescription | PERMISSION_EVENTS |
| PermissionGroup.reminders | NSRemindersUsageDescription | PERMISSION_REMINDERS |
| PermissionGroup.contacts | NSContactsUsageDescription | PERMISSION_CONTACTS |
| PermissionGroup.camera | NSCameraUsageDescription | PERMISSION_CAMERA |
| PermissionGroup.microphone | NSMicrophoneUsageDescription | PERMISSION_MICROPHONE |
| PermissionGroup.speech | NSSpeechRecognitionUsageDescription | PERMISSION_SPEECH_RECOGNIZER |
| PermissionGroup.photos | NSPhotoLibraryUsageDescription | PERMISSION_PHOTOS |
| PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse | NSLocationUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationWhenInUseUsageDescription | PERMISSION_LOCATION |
| PermissionGroup.notification | PermissionGroupNotification | PERMISSION_NOTIFICATIONS |
| PermissionGroup.mediaLibrary | NSAppleMusicUsageDescription, kTCCServiceMediaLibrary | PERMISSION_MEDIA_LIBRARY |
| PermissionGroup.sensors | NSMotionUsageDescription | PERMISSION_SENSORS |
3. Clean & Rebuild
```dart </details>
import 'package:permission_handler/permission_handler.dart';
bool isShown = await PermissionHandler().shouldShowRequestPermissionRationale(PermissionGroup.contacts);
```
This will always return `false` on iOS.
### List of available permissions ## How to use
Defines the permission groups for which permissions can be checked or requested. There are a number of [`Permission`](https://pub.dev/documentation/permission_handler_platform_interface/latest/permission_handler_platform_interface/PermissionGroup-class.html)s.
You can get a `Permission`'s `status`, which is either `undetermined`, `granted`, `denied`, `restricted` or `permanentlyDenied`.
```dart ```dart
enum PermissionGroup { var status = await Permission.camera.status;
/// The unknown permission only used for return type, never requested if (status.isUndetermined) {
unknown, // We didn't ask for permission yet.
/// Android: Calendar
/// iOS: Calendar (Events)
calendar,
/// Android: Camera
/// iOS: Photos (Camera Roll and Camera)
camera,
/// Android: Contacts
/// iOS: AddressBook
contacts,
/// Android: Fine and Coarse Location
/// iOS: CoreLocation (Always and WhenInUse)
location,
/// Android: Microphone
/// iOS: Microphone
microphone,
/// Android: Phone
/// iOS: Nothing
phone,
/// Android: Nothing
/// iOS: Photos
photos,
/// Android: Nothing
/// iOS: Reminders
reminders,
/// Android: Body Sensors
/// iOS: CoreMotion
sensors,
/// Android: Sms
/// iOS: Nothing
sms,
/// Android: External Storage
/// iOS: Nothing
storage,
/// Android: Microphone
/// iOS: Speech
speech,
/// Android: Fine and Coarse Location
/// iOS: CoreLocation - Always
locationAlways,
/// Android: Fine and Coarse Location
/// iOS: CoreLocation - WhenInUse
locationWhenInUse,
/// Android: None
/// iOS: MPMediaLibrary
mediaLibrary,
/// Android: Check notification enable
/// iOS: Check and request notification permission
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,
} }
```
### Status of the permission // You can can also directly ask the permission about its status.
if (await Permission.location.isRestricted) {
// The OS restricts access, for example because of parental controls.
}
```
Defines the state of a permission group Call `request()` on a `Permission` to request it.
If it has already been granted before, nothing happens.
`request()` returns the new status of the `Permission`.
```dart ```dart
enum PermissionStatus { if (await Permission.contacts.request().isGranted) {
/// Permission to access the requested feature is denied by the user. // Either the permission was already granted before or the user just granted it.
denied, }
/// Permission to access the requested feature is granted by the user.
granted,
/// The user granted restricted access to the requested feature (only on iOS). // You can request multiple permissions at once.
restricted, Map<Permission, PermissionStatus> statuses = await [
Permission.location,
Permission.storage,
].request();
print(statuses[Permission.location]);
```
/// Permission is in an unknown state Some permissions, for example location or acceleration sensor permissions, have an associated service, which can be `enabled` or `disabled`.
unknown
/// Permission to access the requested feature is denied by the user and never show selected (only on Android). ```dart
neverAskAgain if (await Permission.locationWhenInUse.serviceStatus.isEnabled) {
// Use location.
} }
``` ```
### Overview of possible service statuses You can also open the app settings:
Defines the state of the backing service for the supplied permission group
```dart ```dart
/// Defines the state of a service related to the permission group if (await Permission.speech.isPermanentlyDenied) {
enum ServiceStatus { // The user opted to never again see the permission request dialog for this
/// The unknown service status indicates the state of the service could not be determined. // app. The only way to change the permission's status now is to let the
unknown, // user manually enable it in the system settings.
openAppSettings();
/// There is no service for the supplied permission group. }
notApplicable, ```
/// The service for the supplied permission group is disabled. On Android, you can show a rationale for using a permission:
disabled,
/// The service for the supplied permission group is enabled. ```dart
enabled bool isShown = await Permission.contacts.shouldShowRequestRationale;
}
``` ```
## Issues ## Issues
......
...@@ -5,26 +5,35 @@ import android.content.Intent; ...@@ -5,26 +5,35 @@ import android.content.Intent;
import android.util.Log; import android.util.Log;
final class AppSettingsManager { final class AppSettingsManager {
boolean openAppSettings(Context applicationContext) { @FunctionalInterface
if (applicationContext == null) { interface OpenAppSettingsSuccessCallback {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context."); void onSuccess(boolean appSettingsOpenedSuccessfully);
return false; }
void openAppSettings(
Context context,
OpenAppSettingsSuccessCallback successCallback,
ErrorCallback errorCallback) {
if(context == null) {
Log.d(PermissionConstants.LOG_TAG, "Context cannot be null.");
errorCallback.onError("PermissionHandler.AppSettingsManager", "Android context cannot be null.");
return;
} }
try { try {
Intent settingsIntent = new Intent(); Intent settingsIntent = new Intent();
settingsIntent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); settingsIntent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
settingsIntent.addCategory(Intent.CATEGORY_DEFAULT); settingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
settingsIntent.setData(android.net.Uri.parse("package:" + applicationContext.getPackageName())); settingsIntent.setData(android.net.Uri.parse("package:" + context.getPackageName()));
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); settingsIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
applicationContext.startActivity(settingsIntent); context.startActivity(settingsIntent);
return true; successCallback.onSuccess(true);
} catch (Exception ex) { } catch (Exception ex) {
return false; successCallback.onSuccess(false);
} }
} }
} }
package com.baseflow.permissionhandler;
@FunctionalInterface
interface ErrorCallback {
void onError(String errorCode, String errorDescription);
}
...@@ -51,25 +51,31 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { ...@@ -51,25 +51,31 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
{ {
switch (call.method) { switch (call.method) {
case "checkPermissionStatus": { case "checkServiceStatus": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString()); @PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
@PermissionConstants.PermissionStatus final int permissionStatus = serviceManager.checkServiceStatus(
permissionManager.checkPermissionStatus(
permission, permission,
applicationContext, applicationContext,
activity); result::success,
(String errorCode, String errorDescription) -> result.error(
errorCode,
errorDescription,
null));
result.success(permissionStatus);
break; break;
} }
case "checkServiceStatus": { case "checkPermissionStatus": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString()); @PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
@PermissionConstants.ServiceStatus final int serviceStatus = permissionManager.checkPermissionStatus(
serviceManager.checkServiceStatus(
permission, permission,
applicationContext); applicationContext,
activity,
result::success,
(String errorCode, String errorDescription) -> result.error(
errorCode,
errorDescription,
null));
result.success(serviceStatus);
break; break;
} }
case "requestPermissions": case "requestPermissions":
...@@ -88,14 +94,26 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { ...@@ -88,14 +94,26 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
break; break;
case "shouldShowRequestPermissionRationale": { case "shouldShowRequestPermissionRationale": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString()); @PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
final boolean showRationale = permissionManager permissionManager.shouldShowRequestPermissionRationale(
.shouldShowRequestPermissionRationale(permission, activity); permission,
result.success(showRationale); activity,
result::success,
(String errorCode, String errorDescription) -> result.error(
errorCode,
errorDescription,
null));
break; break;
} }
case "openAppSettings": case "openAppSettings":
boolean isOpen = appSettingsManager.openAppSettings(applicationContext); appSettingsManager.openAppSettings(
result.success(isOpen); applicationContext,
result::success,
(String errorCode, String errorDescription) -> result.error(
errorCode,
errorDescription,
null));
break; break;
default: default:
result.notImplemented(); result.notImplemented();
......
...@@ -62,7 +62,7 @@ final class PermissionConstants { ...@@ -62,7 +62,7 @@ final class PermissionConstants {
static final int PERMISSION_STATUS_DENIED = 0; static final int PERMISSION_STATUS_DENIED = 0;
static final int PERMISSION_STATUS_GRANTED = 1; static final int PERMISSION_STATUS_GRANTED = 1;
static final int PERMISSION_STATUS_RESTRICTED = 2; static final int PERMISSION_STATUS_RESTRICTED = 2;
static final int PERMISSION_STATUS_UNKNOWN = 3; static final int PERMISSION_STATUS_NOT_DETERMINED = 3;
static final int PERMISSION_STATUS_NEWER_ASK_AGAIN = 4; static final int PERMISSION_STATUS_NEWER_ASK_AGAIN = 4;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -70,7 +70,7 @@ final class PermissionConstants { ...@@ -70,7 +70,7 @@ final class PermissionConstants {
PERMISSION_STATUS_DENIED, PERMISSION_STATUS_DENIED,
PERMISSION_STATUS_GRANTED, PERMISSION_STATUS_GRANTED,
PERMISSION_STATUS_RESTRICTED, PERMISSION_STATUS_RESTRICTED,
PERMISSION_STATUS_UNKNOWN, PERMISSION_STATUS_NOT_DETERMINED,
PERMISSION_STATUS_NEWER_ASK_AGAIN, PERMISSION_STATUS_NEWER_ASK_AGAIN,
}) })
@interface PermissionStatus { @interface PermissionStatus {
...@@ -80,14 +80,12 @@ final class PermissionConstants { ...@@ -80,14 +80,12 @@ final class PermissionConstants {
static final int SERVICE_STATUS_DISABLED = 0; static final int SERVICE_STATUS_DISABLED = 0;
static final int SERVICE_STATUS_ENABLED = 1; static final int SERVICE_STATUS_ENABLED = 1;
static final int SERVICE_STATUS_NOT_APPLICABLE = 2; static final int SERVICE_STATUS_NOT_APPLICABLE = 2;
static final int SERVICE_STATUS_UNKNOWN = 3;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
SERVICE_STATUS_DISABLED, SERVICE_STATUS_DISABLED,
SERVICE_STATUS_ENABLED, SERVICE_STATUS_ENABLED,
SERVICE_STATUS_NOT_APPLICABLE, SERVICE_STATUS_NOT_APPLICABLE
SERVICE_STATUS_UNKNOWN,
}) })
@interface ServiceStatus { @interface ServiceStatus {
} }
......
...@@ -23,79 +23,52 @@ import java.util.Map; ...@@ -23,79 +23,52 @@ import java.util.Map;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
final class PermissionManager { final class PermissionManager {
@FunctionalInterface
interface ActivityRegistry { interface ActivityRegistry {
void addListener(PluginRegistry.ActivityResultListener handler); void addListener(PluginRegistry.ActivityResultListener handler);
} }
@FunctionalInterface
interface PermissionRegistry { interface PermissionRegistry {
void addListener(PluginRegistry.RequestPermissionsResultListener handler); void addListener(PluginRegistry.RequestPermissionsResultListener handler);
} }
interface ResultCallback { @FunctionalInterface
void onResult(Map<Integer, Integer> results); interface RequestPermissionsSuccessCallback {
void onSuccess(Map<Integer, Integer> results);
} }
interface ErrorCallback { @FunctionalInterface
void onError(String errorCode, String errorDescription); interface CheckPermissionsSuccessCallback {
void onSuccess(@PermissionConstants.PermissionStatus int permissionStatus);
}
@FunctionalInterface
interface ShouldShowRequestPermissionRationaleSuccessCallback {
void onSuccess(boolean shouldShowRequestPermissionRationale);
} }
private boolean ongoing = false; private boolean ongoing = false;
@PermissionConstants.PermissionStatus void checkPermissionStatus(
int checkPermissionStatus(
@PermissionConstants.PermissionGroup int permission, @PermissionConstants.PermissionGroup int permission,
Context context, Context context,
Activity activity) { Activity activity,
if (permission == PermissionConstants.PERMISSION_GROUP_NOTIFICATION) { CheckPermissionsSuccessCallback successCallback,
return checkNotificationPermissionStatus(context); ErrorCallback errorCallback) {
}
final List<String> names = PermissionUtils.getManifestNames(context, 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 = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
for (String name : names) { if(activity == null) {
// Only handle them if the client app actually targets a API level greater than M. Log.d(PermissionConstants.LOG_TAG, "Activity cannot be null.");
if (targetsMOrHigher) { errorCallback.onError(
if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) { "PermissionHandler.PermissionManager",
String packageName = context.getPackageName(); "Android activity is required to check for permissions and cannot be null.");
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); return;
// 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(context, 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; successCallback.onSuccess(determinePermissionStatus(
permission,
context,
activity));
} }
void requestPermissions( void requestPermissions(
...@@ -103,25 +76,28 @@ final class PermissionManager { ...@@ -103,25 +76,28 @@ final class PermissionManager {
Activity activity, Activity activity,
ActivityRegistry activityRegistry, ActivityRegistry activityRegistry,
PermissionRegistry permissionRegistry, PermissionRegistry permissionRegistry,
ResultCallback resultCallback, RequestPermissionsSuccessCallback successCallback,
ErrorCallback errorCallback) { ErrorCallback errorCallback) {
if(ongoing) { if(ongoing) {
errorCallback.onError( errorCallback.onError(
"ERROR_ALREADY_REQUESTING_PERMISSIONS", "PermissionHandler.PermissionManager",
"A request for permissions is already running, please wait for it to finish before doing another request (note that you can request multiple permissions at the same time)."); "A request for permissions is already running, please wait for it to finish before doing another request (note that you can request multiple permissions at the same time).");
return;
} }
if (activity == null) { if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity."); Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity.");
errorCallback.onError("ERROR_ANDROID_ACTIVITY_MISSING", "Unable to detect current Android Activity."); errorCallback.onError(
"PermissionHandler.PermissionManager",
"Unable to detect current Android Activity.");
return; return;
} }
Map<Integer, Integer> requestResults = new HashMap<>(); Map<Integer, Integer> requestResults = new HashMap<>();
ArrayList<String> permissionsToRequest = new ArrayList<>(); ArrayList<String> permissionsToRequest = new ArrayList<>();
for (Integer permission : permissions) { for (Integer permission : permissions) {
@PermissionConstants.PermissionStatus final int permissionStatus = checkPermissionStatus(permission, activity.getApplicationContext(), activity); @PermissionConstants.PermissionStatus final int permissionStatus = determinePermissionStatus(permission, activity, activity);
if (permissionStatus == PermissionConstants.PERMISSION_STATUS_GRANTED) { if (permissionStatus == PermissionConstants.PERMISSION_STATUS_GRANTED) {
if (!requestResults.containsKey(permission)) { if (!requestResults.containsKey(permission)) {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_GRANTED); requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_GRANTED);
...@@ -135,7 +111,7 @@ final class PermissionManager { ...@@ -135,7 +111,7 @@ final class PermissionManager {
// if we can't add as unknown and continue // if we can't add as unknown and continue
if (names == null || names.isEmpty()) { if (names == null || names.isEmpty()) {
if (!requestResults.containsKey(permission)) { if (!requestResults.containsKey(permission)) {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_UNKNOWN); requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_NOT_DETERMINED);
} }
continue; continue;
...@@ -143,7 +119,7 @@ final class PermissionManager { ...@@ -143,7 +119,7 @@ final class PermissionManager {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
activityRegistry.addListener( activityRegistry.addListener(
new ActivityResultListener(resultCallback) new ActivityResultListener(successCallback)
); );
String packageName = activity.getPackageName(); String packageName = activity.getPackageName();
...@@ -164,7 +140,7 @@ final class PermissionManager { ...@@ -164,7 +140,7 @@ final class PermissionManager {
requestResults, requestResults,
(Map<Integer, Integer> results) -> { (Map<Integer, Integer> results) -> {
ongoing = false; ongoing = false;
resultCallback.onResult(results); successCallback.onSuccess(results);
}) })
); );
...@@ -177,15 +153,86 @@ final class PermissionManager { ...@@ -177,15 +153,86 @@ final class PermissionManager {
} else { } else {
ongoing = false; ongoing = false;
if (requestResults.size() > 0) { if (requestResults.size() > 0) {
resultCallback.onResult(requestResults); successCallback.onSuccess(requestResults);
} }
} }
} }
boolean shouldShowRequestPermissionRationale(int permission, Activity activity) { @PermissionConstants.PermissionStatus
private int determinePermissionStatus(
@PermissionConstants.PermissionGroup int permission,
Context context,
Activity activity) {
if (permission == PermissionConstants.PERMISSION_GROUP_NOTIFICATION) {
return checkNotificationPermissionStatus(context);
}
final List<String> names = PermissionUtils.getManifestNames(context, 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_NOT_DETERMINED;
}
final boolean targetsMOrHigher = context.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 = context.getPackageName();
PowerManager pm = (PowerManager) context.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(context, name);
if (permissionStatus == PackageManager.PERMISSION_DENIED) {
if (!PermissionUtils.getRequestedPermissionBefore(context, name))
{
return PermissionConstants.PERMISSION_STATUS_NOT_DETERMINED;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
PermissionUtils.isNeverAskAgainSelected(activity, name)) {
return PermissionConstants.PERMISSION_STATUS_NEWER_ASK_AGAIN;
} else {
return PermissionConstants.PERMISSION_STATUS_DENIED;
}
} else if (permissionStatus != PackageManager.PERMISSION_GRANTED) {
return PermissionConstants.PERMISSION_STATUS_DENIED;
}
}
}
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
void shouldShowRequestPermissionRationale(
int permission,
Activity activity,
ShouldShowRequestPermissionRationaleSuccessCallback successCallback,
ErrorCallback errorCallback) {
if (activity == null) { if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity."); Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity.");
return false;
errorCallback.onError(
"PermissionHandler.PermissionManager",
"Unable to detect current Android Activity.");
return;
} }
List<String> names = PermissionUtils.getManifestNames(activity, permission); List<String> names = PermissionUtils.getManifestNames(activity, permission);
...@@ -193,15 +240,17 @@ final class PermissionManager { ...@@ -193,15 +240,17 @@ final class PermissionManager {
// if isn't an android specific group then go ahead and return false; // if isn't an android specific group then go ahead and return false;
if (names == null) { if (names == null) {
Log.d(PermissionConstants.LOG_TAG, "No android specific permissions needed for: " + permission); Log.d(PermissionConstants.LOG_TAG, "No android specific permissions needed for: " + permission);
return false; successCallback.onSuccess(false);
return;
} }
if (names.isEmpty()) { if (names.isEmpty()) {
Log.d(PermissionConstants.LOG_TAG, "No permissions found in manifest for: " + permission + " no need to show request rationale"); Log.d(PermissionConstants.LOG_TAG, "No permissions found in manifest for: " + permission + " no need to show request rationale");
return false; successCallback.onSuccess(false);
return;
} }
return ActivityCompat.shouldShowRequestPermissionRationale(activity, names.get(0)); successCallback.onSuccess(ActivityCompat.shouldShowRequestPermissionRationale(activity, names.get(0)));
} }
private int checkNotificationPermissionStatus(Context context) { private int checkNotificationPermissionStatus(Context context) {
...@@ -223,10 +272,10 @@ final class PermissionManager { ...@@ -223,10 +272,10 @@ final class PermissionManager {
// call. // call.
boolean alreadyCalled = false; boolean alreadyCalled = false;
final ResultCallback callback; final RequestPermissionsSuccessCallback callback;
@VisibleForTesting @VisibleForTesting
ActivityResultListener(ResultCallback callback) { ActivityResultListener(RequestPermissionsSuccessCallback callback) {
this.callback = callback; this.callback = callback;
} }
...@@ -243,7 +292,7 @@ final class PermissionManager { ...@@ -243,7 +292,7 @@ final class PermissionManager {
HashMap<Integer, Integer> results = new HashMap<>(); HashMap<Integer, Integer> results = new HashMap<>();
results.put(PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS, status); results.put(PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS, status);
callback.onResult(results); callback.onSuccess(results);
return true; return true;
} }
} }
...@@ -259,14 +308,14 @@ final class PermissionManager { ...@@ -259,14 +308,14 @@ final class PermissionManager {
boolean alreadyCalled = false; boolean alreadyCalled = false;
final Activity activity; final Activity activity;
final ResultCallback callback; final RequestPermissionsSuccessCallback callback;
final Map<Integer, Integer> requestResults; final Map<Integer, Integer> requestResults;
@VisibleForTesting @VisibleForTesting
RequestPermissionsListener( RequestPermissionsListener(
Activity activity, Activity activity,
Map<Integer, Integer> requestResults, Map<Integer, Integer> requestResults,
ResultCallback callback) { RequestPermissionsSuccessCallback callback) {
this.activity = activity; this.activity = activity;
this.callback = callback; this.callback = callback;
this.requestResults = requestResults; this.requestResults = requestResults;
...@@ -282,8 +331,10 @@ final class PermissionManager { ...@@ -282,8 +331,10 @@ final class PermissionManager {
alreadyCalled = true; alreadyCalled = true;
for (int i = 0; i < permissions.length; i++) { for (int i = 0; i < permissions.length; i++) {
final String permissionName = permissions[i];
@PermissionConstants.PermissionGroup final int permission = @PermissionConstants.PermissionGroup final int permission =
PermissionUtils.parseManifestName(permissions[i]); PermissionUtils.parseManifestName(permissionName);
if (permission == PermissionConstants.PERMISSION_GROUP_UNKNOWN) if (permission == PermissionConstants.PERMISSION_GROUP_UNKNOWN)
continue; continue;
...@@ -294,23 +345,23 @@ final class PermissionManager { ...@@ -294,23 +345,23 @@ final class PermissionManager {
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_MICROPHONE)) { if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_MICROPHONE)) {
requestResults.put( requestResults.put(
PermissionConstants.PERMISSION_GROUP_MICROPHONE, PermissionConstants.PERMISSION_GROUP_MICROPHONE,
PermissionUtils.toPermissionStatus(this.activity, permission, result)); PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
} }
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_SPEECH)) { if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_SPEECH)) {
requestResults.put( requestResults.put(
PermissionConstants.PERMISSION_GROUP_SPEECH, PermissionConstants.PERMISSION_GROUP_SPEECH,
PermissionUtils.toPermissionStatus(this.activity, permission, result)); PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
} }
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS) { } else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS) {
@PermissionConstants.PermissionStatus int permissionStatus = @PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permission, result); PermissionUtils.toPermissionStatus(this.activity, permissionName, result);
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) { if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) {
requestResults.put(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus); requestResults.put(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus);
} }
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION) { } else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION) {
@PermissionConstants.PermissionStatus int permissionStatus = @PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permission, result); PermissionUtils.toPermissionStatus(this.activity, permissionName, result);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) { if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) {
...@@ -330,13 +381,13 @@ final class PermissionManager { ...@@ -330,13 +381,13 @@ final class PermissionManager {
} else if (!requestResults.containsKey(permission)) { } else if (!requestResults.containsKey(permission)) {
requestResults.put( requestResults.put(
permission, permission,
PermissionUtils.toPermissionStatus(this.activity, permission, result)); PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
} }
PermissionUtils.updatePermissionShouldShowStatus(this.activity, permission); PermissionUtils.updatePermissionShouldShowStatus(this.activity, permission);
} }
this.callback.onResult(requestResults); this.callback.onSuccess(requestResults);
return true; return true;
} }
} }
......
...@@ -238,9 +238,9 @@ public class PermissionUtils { ...@@ -238,9 +238,9 @@ public class PermissionUtils {
} }
@PermissionConstants.PermissionStatus @PermissionConstants.PermissionStatus
static int toPermissionStatus(final Activity activity, @PermissionConstants.PermissionGroup int permission, int grantResult) { static int toPermissionStatus(final Activity activity, final String permissionName, int grantResult) {
if (grantResult == PackageManager.PERMISSION_DENIED) { if (grantResult == PackageManager.PERMISSION_DENIED) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && PermissionUtils.isNeverAskAgainSelected(activity, permission) return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && PermissionUtils.isNeverAskAgainSelected(activity, permissionName)
? PermissionConstants.PERMISSION_STATUS_NEWER_ASK_AGAIN ? PermissionConstants.PERMISSION_STATUS_NEWER_ASK_AGAIN
: PermissionConstants.PERMISSION_STATUS_DENIED; : PermissionConstants.PERMISSION_STATUS_DENIED;
} }
...@@ -265,23 +265,12 @@ public class PermissionUtils { ...@@ -265,23 +265,12 @@ public class PermissionUtils {
} }
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
static boolean isNeverAskAgainSelected(final Activity activity, @PermissionConstants.PermissionGroup int permission) { static boolean isNeverAskAgainSelected(final Activity activity, final String name) {
if (activity == null) { if (activity == null) {
return false; return false;
} }
List<String> names = getManifestNames(activity, permission); return PermissionUtils.neverAskAgainSelected(activity, name);
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) @RequiresApi(api = Build.VERSION_CODES.M)
...@@ -298,7 +287,7 @@ public class PermissionUtils { ...@@ -298,7 +287,7 @@ public class PermissionUtils {
editor.apply(); editor.apply();
} }
private static boolean getRequestedPermissionBefore(final Context context, final String permission) { static boolean getRequestedPermissionBefore(final Context context, final String permission) {
SharedPreferences genPrefs = context.getSharedPreferences("GENERIC_PREFERENCES", Context.MODE_PRIVATE); SharedPreferences genPrefs = context.getSharedPreferences("GENERIC_PREFERENCES", Context.MODE_PRIVATE);
return genPrefs.getBoolean(permission, false); return genPrefs.getBoolean(permission, false);
} }
......
...@@ -17,34 +17,45 @@ import android.util.Log; ...@@ -17,34 +17,45 @@ import android.util.Log;
import java.util.List; import java.util.List;
final class ServiceManager { final class ServiceManager {
@PermissionConstants.ServiceStatus @FunctionalInterface
int checkServiceStatus( interface SuccessCallback {
void onSuccess(@PermissionConstants.ServiceStatus int serviceStatus);
}
void checkServiceStatus(
int permission, int permission,
Context context) { Context context,
if (context == null) { SuccessCallback successCallback,
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context."); ErrorCallback errorCallback) {
return PermissionConstants.SERVICE_STATUS_UNKNOWN; if(context == null) {
Log.d(PermissionConstants.LOG_TAG, "Context cannot be null.");
errorCallback.onError("PermissionHandler.ServiceManager", "Android context cannot be null.");
return;
} }
if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION || if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION ||
permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS || permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS ||
permission == PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE) { permission == PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE) {
return isLocationServiceEnabled(context) final int serviceStatus = isLocationServiceEnabled(context)
? PermissionConstants.SERVICE_STATUS_ENABLED ? PermissionConstants.SERVICE_STATUS_ENABLED
: PermissionConstants.SERVICE_STATUS_DISABLED; : PermissionConstants.SERVICE_STATUS_DISABLED;
successCallback.onSuccess(serviceStatus);
} }
if (permission == PermissionConstants.PERMISSION_GROUP_PHONE) { if (permission == PermissionConstants.PERMISSION_GROUP_PHONE) {
PackageManager pm = context.getPackageManager(); PackageManager pm = context.getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE; successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE);
return;
} }
TelephonyManager telephonyManager = (TelephonyManager) context TelephonyManager telephonyManager = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE); .getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager == null || telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { if (telephonyManager == null || telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE; successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE);
return;
} }
Intent callIntent = new Intent(Intent.ACTION_CALL); Intent callIntent = new Intent(Intent.ACTION_CALL);
...@@ -52,23 +63,28 @@ final class ServiceManager { ...@@ -52,23 +63,28 @@ final class ServiceManager {
List<ResolveInfo> callAppsList = pm.queryIntentActivities(callIntent, 0); List<ResolveInfo> callAppsList = pm.queryIntentActivities(callIntent, 0);
if (callAppsList.isEmpty()) { if (callAppsList.isEmpty()) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE; successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE);
return;
} }
if (telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY) { if (telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY) {
return PermissionConstants.SERVICE_STATUS_DISABLED; successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_DISABLED);
return;
} }
return PermissionConstants.SERVICE_STATUS_ENABLED; successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_ENABLED);
return;
} }
if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) { if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M final int serviceStatus = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
? PermissionConstants.SERVICE_STATUS_ENABLED ? PermissionConstants.SERVICE_STATUS_ENABLED
: PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE; : PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
successCallback.onSuccess(serviceStatus);
return;
} }
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE; successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE);
} }
private boolean isLocationServiceEnabled(Context context) { private boolean isLocationServiceEnabled(Context context) {
...@@ -86,7 +102,7 @@ final class ServiceManager { ...@@ -86,7 +102,7 @@ final class ServiceManager {
} }
} }
// Suppress deprecation warnings since it's purpose is to support to be backwards compatible with // Suppress deprecation warnings since its purpose is to support to be backwards compatible with
// pre Pie versions of Android. // pre Pie versions of Android.
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private static boolean isLocationServiceEnabledKitKat(Context context) private static boolean isLocationServiceEnabledKitKat(Context context)
...@@ -109,7 +125,7 @@ final class ServiceManager { ...@@ -109,7 +125,7 @@ final class ServiceManager {
return locationMode != Settings.Secure.LOCATION_MODE_OFF; return locationMode != Settings.Secure.LOCATION_MODE_OFF;
} }
// Suppress deprecation warnings since it's purpose is to support to be backwards compatible with // Suppress deprecation warnings since its purpose is to support to be backwards compatible with
// pre KitKat versions of Android. // pre KitKat versions of Android.
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private static boolean isLocationServiceEnablePreKitKat(Context context) private static boolean isLocationServiceEnablePreKitKat(Context context)
......
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
platform :ios, '8.0' # platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'
...@@ -15,96 +15,70 @@ def parse_KV_file(file, separator='=') ...@@ -15,96 +15,70 @@ def parse_KV_file(file, separator='=')
if !File.exists? file_abs_path if !File.exists? file_abs_path
return []; return [];
end end
pods_ary = [] generated_key_values = {}
skip_line_start_symbols = ["#", "/"] skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line| File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator) plugin = line.split(pattern=separator)
if plugin.length == 2 if plugin.length == 2
podname = plugin[0].strip() podname = plugin[0].strip()
path = plugin[1].strip() path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path) podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname, :path => podpath}); generated_key_values[podname] = podpath
else else
puts "Invalid plugin specification: #{line}" puts "Invalid plugin specification: #{line}"
end end
} end
return pods_ary generated_key_values
end end
target 'Runner' do target 'Runner' do
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # Flutter Pod
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks') copied_flutter_dir = File.join(__dir__, 'Flutter')
system('mkdir -p .symlinks/plugins') copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
# Flutter Pods unless File.exist?(copied_framework_path)
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
if generated_xcode_build_settings.empty? end
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end end
generated_xcode_build_settings.map { |p|
if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
symlink = File.join('.symlinks', 'flutter')
File.symlink(File.dirname(p[:path]), symlink)
pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
end end
}
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
# Plugin Pods # Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins') plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.map { |p| plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', p[:name]) symlink = File.join('.symlinks', 'plugins', name)
File.symlink(p[:path], symlink) File.symlink(path, symlink)
pod p[:name], :path => File.join(symlink, 'ios') pod name, :path => File.join(symlink, 'ios')
} end
end end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
target.build_configurations.each do |config| target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO' config.build_settings['ENABLE_BITCODE'] = 'NO'
# You can remove unused permissions here
# for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/develop/ios/Classes/PermissionHandlerEnums.h
# e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.calendar
# 'PERMISSION_EVENTS=0',
## dart: PermissionGroup.reminders
# 'PERMISSION_REMINDERS=0',
## dart: PermissionGroup.contacts
# 'PERMISSION_CONTACTS=0',
## dart: PermissionGroup.camera
# 'PERMISSION_CAMERA=0',
## dart: PermissionGroup.microphone
# 'PERMISSION_MICROPHONE=0',
## dart: PermissionGroup.speech
# 'PERMISSION_SPEECH_RECOGNIZER=0',
## dart: PermissionGroup.photos
# 'PERMISSION_PHOTOS=0',
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
# 'PERMISSION_LOCATION=0',
## dart: PermissionGroup.notification
# 'PERMISSION_NOTIFICATIONS=0',
## dart: PermissionGroup.mediaLibrary
# 'PERMISSION_MEDIA_LIBRARY=0',
## dart: PermissionGroup.sensors
# 'PERMISSION_SENSORS=0'
]
end end
end end
end end
...@@ -9,10 +9,6 @@ ...@@ -9,10 +9,6 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
...@@ -28,8 +24,6 @@ ...@@ -28,8 +24,6 @@
dstPath = ""; dstPath = "";
dstSubfolderSpec = 10; dstSubfolderSpec = 10;
files = ( files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
); );
name = "Embed Frameworks"; name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
...@@ -40,7 +34,6 @@ ...@@ -40,7 +34,6 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
4B425729EC83DC3B462CA8DE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; }; 4B425729EC83DC3B462CA8DE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
...@@ -49,7 +42,6 @@ ...@@ -49,7 +42,6 @@
89CBFAE60E5774D9B3D21DEF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; 89CBFAE60E5774D9B3D21DEF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
...@@ -64,8 +56,6 @@ ...@@ -64,8 +56,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
B7634AD7E1771AEDD2D1A5F7 /* libPods-Runner.a in Frameworks */, B7634AD7E1771AEDD2D1A5F7 /* libPods-Runner.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
...@@ -84,9 +74,7 @@ ...@@ -84,9 +74,7 @@
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */,
...@@ -232,7 +220,7 @@ ...@@ -232,7 +220,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
}; };
8BA3EF5BA9C129C83EB3F474 /* [CP] Embed Pods Frameworks */ = { 8BA3EF5BA9C129C83EB3F474 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
...@@ -241,7 +229,7 @@ ...@@ -241,7 +229,7 @@
); );
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", "${PODS_ROOT}/../Flutter/Flutter.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
......
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1130"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
...@@ -27,8 +27,6 @@ ...@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
...@@ -38,8 +36,8 @@ ...@@ -38,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
...@@ -61,8 +59,6 @@ ...@@ -61,8 +59,6 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"
......
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>BuildSystemType</key> <key>BuildSystemType</key>
<string>Original</string> <string>Latest</string>
</dict> </dict>
</plist> </plist>
...@@ -17,33 +17,31 @@ class MyApp extends StatelessWidget { ...@@ -17,33 +17,31 @@ class MyApp extends StatelessWidget {
actions: <Widget>[ actions: <Widget>[
IconButton( IconButton(
icon: const Icon(Icons.settings), icon: const Icon(Icons.settings),
onPressed: () { onPressed: () async {
PermissionHandler().openAppSettings().then((bool hasOpened) => var hasOpened = openAppSettings();
debugPrint('App Settings opened: ' + hasOpened.toString())); debugPrint('App Settings opened: ' + hasOpened.toString());
}, },
) )
], ],
), ),
body: Center( body: Center(
child: ListView( child: ListView(
children: PermissionGroup.values children: Permission.values
.where((PermissionGroup permission) { .where((Permission permission) {
if (Platform.isIOS) { if (Platform.isIOS) {
return permission != PermissionGroup.unknown && return permission != Permission.unknown &&
permission != PermissionGroup.sms && permission != Permission.sms &&
permission != PermissionGroup.storage && permission != Permission.storage &&
permission != permission != Permission.ignoreBatteryOptimizations &&
PermissionGroup.ignoreBatteryOptimizations && permission != Permission.accessMediaLocation;
permission != PermissionGroup.accessMediaLocation;
} else { } else {
return permission != PermissionGroup.unknown && return permission != Permission.unknown &&
permission != PermissionGroup.mediaLibrary && permission != Permission.mediaLibrary &&
permission != PermissionGroup.photos && permission != Permission.photos &&
permission != PermissionGroup.reminders; permission != Permission.reminders;
} }
}) })
.map((PermissionGroup permission) => .map((permission) => PermissionWidget(permission))
PermissionWidget(permission))
.toList()), .toList()),
), ),
), ),
...@@ -54,20 +52,20 @@ class MyApp extends StatelessWidget { ...@@ -54,20 +52,20 @@ class MyApp extends StatelessWidget {
/// Permission widget which displays a permission and allows users to request /// Permission widget which displays a permission and allows users to request
/// the permissions. /// the permissions.
class PermissionWidget extends StatefulWidget { class PermissionWidget extends StatefulWidget {
/// Constructs a [PermissionWidget] for the supplied [PermissionGroup]. /// Constructs a [PermissionWidget] for the supplied [Permission].
const PermissionWidget(this._permissionGroup); const PermissionWidget(this._permission);
final PermissionGroup _permissionGroup; final Permission _permission;
@override @override
_PermissionState createState() => _PermissionState(_permissionGroup); _PermissionState createState() => _PermissionState(_permission);
} }
class _PermissionState extends State<PermissionWidget> { class _PermissionState extends State<PermissionWidget> {
_PermissionState(this._permissionGroup); _PermissionState(this._permission);
final PermissionGroup _permissionGroup; final Permission _permission;
PermissionStatus _permissionStatus = PermissionStatus.unknown; PermissionStatus _permissionStatus = PermissionStatus.undetermined;
@override @override
void initState() { void initState() {
...@@ -76,15 +74,9 @@ class _PermissionState extends State<PermissionWidget> { ...@@ -76,15 +74,9 @@ class _PermissionState extends State<PermissionWidget> {
_listenForPermissionStatus(); _listenForPermissionStatus();
} }
void _listenForPermissionStatus() { void _listenForPermissionStatus() async {
final Future<PermissionStatus> statusFuture = final status = await _permission.status;
PermissionHandler().checkPermissionStatus(_permissionGroup); setState(() => _permissionStatus = status);
statusFuture.then((PermissionStatus status) {
setState(() {
_permissionStatus = status;
});
});
} }
Color getPermissionColor() { Color getPermissionColor() {
...@@ -101,7 +93,7 @@ class _PermissionState extends State<PermissionWidget> { ...@@ -101,7 +93,7 @@ class _PermissionState extends State<PermissionWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
title: Text(_permissionGroup.toString()), title: Text(_permission.toString()),
subtitle: Text( subtitle: Text(
_permissionStatus.toString(), _permissionStatus.toString(),
style: TextStyle(color: getPermissionColor()), style: TextStyle(color: getPermissionColor()),
...@@ -109,33 +101,26 @@ class _PermissionState extends State<PermissionWidget> { ...@@ -109,33 +101,26 @@ class _PermissionState extends State<PermissionWidget> {
trailing: IconButton( trailing: IconButton(
icon: const Icon(Icons.info), icon: const Icon(Icons.info),
onPressed: () { onPressed: () {
checkServiceStatus(context, _permissionGroup); checkServiceStatus(context, _permission);
}), }),
onTap: () { onTap: () {
requestPermission(_permissionGroup); requestPermission(_permission);
}, },
); );
} }
void checkServiceStatus(BuildContext context, PermissionGroup permission) { void checkServiceStatus(BuildContext context, Permission permission) async {
PermissionHandler() Scaffold.of(context).showSnackBar(SnackBar(
.checkServiceStatus(permission) content: Text((await permission.status).toString()),
.then((ServiceStatus serviceStatus) { ));
final SnackBar snackBar =
SnackBar(content: Text(serviceStatus.toString()));
Scaffold.of(context).showSnackBar(snackBar);
});
} }
Future<void> requestPermission(PermissionGroup permission) async { Future<void> requestPermission(Permission permission) async {
final List<PermissionGroup> permissions = <PermissionGroup>[permission]; final status = await permission.request();
final Map<PermissionGroup, PermissionStatus> permissionRequestResult =
await PermissionHandler().requestPermissions(permissions);
setState(() { setState(() {
print(permissionRequestResult); print(status);
_permissionStatus = permissionRequestResult[permission]; _permissionStatus = status;
print(_permissionStatus); print(_permissionStatus);
}); });
} }
......
...@@ -107,12 +107,11 @@ typedef NS_ENUM(int, PermissionStatus) { ...@@ -107,12 +107,11 @@ typedef NS_ENUM(int, PermissionStatus) {
PermissionStatusDenied = 0, PermissionStatusDenied = 0,
PermissionStatusGranted, PermissionStatusGranted,
PermissionStatusRestricted, PermissionStatusRestricted,
PermissionStatusUnknown, PermissionStatusNotDetermined,
}; };
typedef NS_ENUM(int, ServiceStatus) { typedef NS_ENUM(int, ServiceStatus) {
ServiceStatusDisabled = 0, ServiceStatusDisabled = 0,
ServiceStatusEnabled, ServiceStatusEnabled,
ServiceStatusNotApplicable, ServiceStatusNotApplicable,
ServiceStatusUnknown,
}; };
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
return PermissionStatusUnknown; return PermissionStatusUnknown;
#endif #endif
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission]; PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) { if (status != PermissionStatusNotDetermined) {
completionHandler(status); completionHandler(status);
return; return;
} }
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
return; return;
#endif #endif
} else { } else {
completionHandler(PermissionStatusUnknown); completionHandler(PermissionStatusNotDetermined);
return; return;
} }
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
switch (status) { switch (status) {
case AVAuthorizationStatusNotDetermined: case AVAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case AVAuthorizationStatusRestricted: case AVAuthorizationStatusRestricted:
return PermissionStatusRestricted; return PermissionStatusRestricted;
case AVAuthorizationStatusDenied: case AVAuthorizationStatusDenied:
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
return PermissionStatusGranted; return PermissionStatusGranted;
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
@end @end
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission]; PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) { if (status != PermissionStatusNotDetermined) {
completionHandler(status); completionHandler(status);
} }
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
switch (status) { switch (status) {
case CNAuthorizationStatusNotDetermined: case CNAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case CNAuthorizationStatusRestricted: case CNAuthorizationStatusRestricted:
return PermissionStatusRestricted; return PermissionStatusRestricted;
case CNAuthorizationStatusDenied: case CNAuthorizationStatusDenied:
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
switch (status) { switch (status) {
case kABAuthorizationStatusNotDetermined: case kABAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case kABAuthorizationStatusRestricted: case kABAuthorizationStatusRestricted:
return PermissionStatusRestricted; return PermissionStatusRestricted;
case kABAuthorizationStatusDenied: case kABAuthorizationStatusDenied:
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
} }
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
+ (void)requestPermissionsFromContactStore:(PermissionStatusHandler)completionHandler API_AVAILABLE(ios(9)) { + (void)requestPermissionsFromContactStore:(PermissionStatusHandler)completionHandler API_AVAILABLE(ios(9)) {
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
#endif #endif
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus permissionStatus = [self checkPermissionStatus:permission]; PermissionStatus permissionStatus = [self checkPermissionStatus:permission];
if (permissionStatus != PermissionStatusUnknown) { if (permissionStatus != PermissionStatusNotDetermined) {
completionHandler(permissionStatus); completionHandler(permissionStatus);
return; return;
} }
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
return; return;
#endif #endif
} else { } else {
completionHandler(PermissionStatusUnknown); completionHandler(PermissionStatusNotDetermined);
return; return;
} }
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
switch (status) { switch (status) {
case EKAuthorizationStatusNotDetermined: case EKAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case EKAuthorizationStatusRestricted: case EKAuthorizationStatusRestricted:
return PermissionStatusRestricted; return PermissionStatusRestricted;
case EKAuthorizationStatusDenied: case EKAuthorizationStatusDenied:
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
return PermissionStatusGranted; return PermissionStatusGranted;
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
@end @end
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
PermissionStatus status = [self checkPermissionStatus:permission]; PermissionStatus status = [self checkPermissionStatus:permission];
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse && permission == PermissionGroupLocationAlways) { if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse && permission == PermissionGroupLocationAlways) {
// don't do anything and continue requesting permissions // don't do anything and continue requesting permissions
} else if (status != PermissionStatusUnknown) { } else if (status != PermissionStatusNotDetermined) {
completionHandler(status); completionHandler(status);
} }
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
if (permission == PermissionGroupLocationAlways) { if (permission == PermissionGroupLocationAlways) {
switch (authorizationStatus) { switch (authorizationStatus) {
case kCLAuthorizationStatusNotDetermined: case kCLAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case kCLAuthorizationStatusRestricted: case kCLAuthorizationStatusRestricted:
return PermissionStatusRestricted; return PermissionStatusRestricted;
case kCLAuthorizationStatusDenied: case kCLAuthorizationStatusDenied:
...@@ -118,7 +118,7 @@ ...@@ -118,7 +118,7 @@
switch (authorizationStatus) { switch (authorizationStatus) {
case kCLAuthorizationStatusNotDetermined: case kCLAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case kCLAuthorizationStatusRestricted: case kCLAuthorizationStatusRestricted:
return PermissionStatusRestricted; return PermissionStatusRestricted;
case kCLAuthorizationStatusDenied: case kCLAuthorizationStatusDenied:
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
switch (authorizationStatus) { switch (authorizationStatus) {
case kCLAuthorizationStatusNotDetermined: case kCLAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case kCLAuthorizationStatusRestricted: case kCLAuthorizationStatusRestricted:
return PermissionStatusRestricted; return PermissionStatusRestricted;
case kCLAuthorizationStatusDenied: case kCLAuthorizationStatusDenied:
...@@ -142,7 +142,7 @@ ...@@ -142,7 +142,7 @@
case kCLAuthorizationStatusAuthorized: case kCLAuthorizationStatusAuthorized:
return PermissionStatusGranted; return PermissionStatusGranted;
default: default:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission]; PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) { if (status != PermissionStatusNotDetermined) {
completionHandler(status); completionHandler(status);
return; return;
} }
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
completionHandler([MediaLibraryPermissionStrategy determinePermissionStatus:status]); completionHandler([MediaLibraryPermissionStrategy determinePermissionStatus:status]);
}]; }];
} else { } else {
completionHandler(PermissionStatusUnknown); completionHandler(PermissionStatusNotDetermined);
return; return;
} }
} }
...@@ -41,13 +41,13 @@ ...@@ -41,13 +41,13 @@
return [MediaLibraryPermissionStrategy determinePermissionStatus:status]; return [MediaLibraryPermissionStrategy determinePermissionStatus:status];
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
+ (PermissionStatus)determinePermissionStatus:(MPMediaLibraryAuthorizationStatus)authorizationStatus API_AVAILABLE(ios(9.3)){ + (PermissionStatus)determinePermissionStatus:(MPMediaLibraryAuthorizationStatus)authorizationStatus API_AVAILABLE(ios(9.3)){
switch (authorizationStatus) { switch (authorizationStatus) {
case MPMediaLibraryAuthorizationStatusNotDetermined: case MPMediaLibraryAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case MPMediaLibraryAuthorizationStatusDenied: case MPMediaLibraryAuthorizationStatusDenied:
return PermissionStatusDenied; return PermissionStatusDenied;
case MPMediaLibraryAuthorizationStatusRestricted: case MPMediaLibraryAuthorizationStatusRestricted:
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
return PermissionStatusGranted; return PermissionStatusGranted;
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
@end @end
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission]; PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) { if (status != PermissionStatusNotDetermined) {
completionHandler(status); completionHandler(status);
return; return;
} }
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
if (settings.authorizationStatus == UNAuthorizationStatusDenied) { if (settings.authorizationStatus == UNAuthorizationStatusDenied) {
permissionStatus = PermissionStatusDenied; permissionStatus = PermissionStatusDenied;
} else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { } else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) {
permissionStatus = PermissionStatusUnknown; permissionStatus = PermissionStatusNotDetermined;
} }
dispatch_semaphore_signal(sem); dispatch_semaphore_signal(sem);
}]; }];
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
@implementation PhonePermissionStrategy @implementation PhonePermissionStrategy
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission { - (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission {
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
completionHandler(PermissionStatusUnknown); completionHandler(PermissionStatusNotDetermined);
} }
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission]; PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) { if (status != PermissionStatusNotDetermined) {
completionHandler(status); completionHandler(status);
return; return;
} }
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
+ (PermissionStatus)determinePermissionStatus:(PHAuthorizationStatus)authorizationStatus { + (PermissionStatus)determinePermissionStatus:(PHAuthorizationStatus)authorizationStatus {
switch (authorizationStatus) { switch (authorizationStatus) {
case PHAuthorizationStatusNotDetermined: case PHAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case PHAuthorizationStatusRestricted: case PHAuthorizationStatusRestricted:
return PermissionStatusRestricted; return PermissionStatusRestricted;
case PHAuthorizationStatusDenied: case PHAuthorizationStatusDenied:
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
return PermissionStatusGranted; return PermissionStatusGranted;
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
@end @end
......
...@@ -19,13 +19,13 @@ ...@@ -19,13 +19,13 @@
: ServiceStatusDisabled; : ServiceStatusDisabled;
} }
return ServiceStatusUnknown; return ServiceStatusDisabled;
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission]; PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) { if (status != PermissionStatusNotDetermined) {
completionHandler(status); completionHandler(status);
return; return;
} }
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
} }
}]; }];
} else { } else {
completionHandler(PermissionStatusUnknown); completionHandler(PermissionStatusNotDetermined);
} }
} }
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
switch (status) { switch (status) {
case CMAuthorizationStatusNotDetermined: case CMAuthorizationStatusNotDetermined:
permissionStatus = PermissionStatusUnknown; permissionStatus = PermissionStatusNotDetermined;
break; break;
case CMAuthorizationStatusRestricted: case CMAuthorizationStatusRestricted:
permissionStatus = PermissionStatusRestricted; permissionStatus = PermissionStatusRestricted;
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
return permissionStatus; return permissionStatus;
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
@end @end
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission]; PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) { if (status != PermissionStatusNotDetermined) {
completionHandler(status); completionHandler(status);
return; return;
} }
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
completionHandler([SpeechPermissionStrategy determinePermissionStatus:authorizationStatus]); completionHandler([SpeechPermissionStrategy determinePermissionStatus:authorizationStatus]);
}]; }];
} else { } else {
completionHandler(PermissionStatusUnknown); completionHandler(PermissionStatusNotDetermined);
} }
} }
...@@ -40,13 +40,13 @@ ...@@ -40,13 +40,13 @@
return [SpeechPermissionStrategy determinePermissionStatus:status]; return [SpeechPermissionStrategy determinePermissionStatus:status];
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
+ (PermissionStatus)determinePermissionStatus:(SFSpeechRecognizerAuthorizationStatus)authorizationStatus API_AVAILABLE(ios(10.0)){ + (PermissionStatus)determinePermissionStatus:(SFSpeechRecognizerAuthorizationStatus)authorizationStatus API_AVAILABLE(ios(10.0)){
switch (authorizationStatus) { switch (authorizationStatus) {
case SFSpeechRecognizerAuthorizationStatusNotDetermined: case SFSpeechRecognizerAuthorizationStatusNotDetermined:
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
case SFSpeechRecognizerAuthorizationStatusDenied: case SFSpeechRecognizerAuthorizationStatusDenied:
return PermissionStatusDenied; return PermissionStatusDenied;
case SFSpeechRecognizerAuthorizationStatusRestricted: case SFSpeechRecognizerAuthorizationStatusRestricted:
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
return PermissionStatusGranted; return PermissionStatusGranted;
} }
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
@end @end
......
...@@ -9,14 +9,14 @@ ...@@ -9,14 +9,14 @@
@implementation UnknownPermissionStrategy @implementation UnknownPermissionStrategy
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission { - (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission {
return PermissionStatusUnknown; return PermissionStatusNotDetermined;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusUnknown; return ServiceStatusDisabled;
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
completionHandler(PermissionStatusUnknown); completionHandler(PermissionStatusNotDetermined);
} }
@end @end
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart'; import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart';
export '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; show Permission, PermissionStatus, ServiceStatus;
/// The Android and iOS implementation of [PermissionHandlerPlatform]. PermissionHandlerPlatform get _handler => PermissionHandlerPlatform.instance;
/// Opens the app settings page.
/// ///
/// This class implements the `package:permission_handler` functionality for /// Returns [true] if the app settings page could be opened, otherwise [false].
/// the Android and iOS platforms. Future<bool> openAppSettings() => _handler.openAppSettings();
class PermissionHandler extends PermissionHandlerPlatform {
@override
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) {
return PermissionHandlerPlatform.instance.checkServiceStatus(permission);
}
@override /// Actions that can be executed on a permission.
Future<PermissionStatus> checkPermissionStatus(PermissionGroup permission) { extension PermissionActions on Permission {
return PermissionHandlerPlatform.instance.checkPermissionStatus(permission); /// The current status of this permission.
} Future<PermissionStatus> get status => _handler.checkPermissionStatus(this);
@override /// If you should show a rationale for requesting permission.
Future<bool> openAppSettings() { ///
return PermissionHandlerPlatform.instance.openAppSettings(); /// This is only implemented on Android, calling this on iOS always returns
/// [false].
Future<bool> get shouldShowRequestRationale async {
if (!Platform.isAndroid) {
return false;
} }
@override return _handler.shouldShowRequestPermissionRationale(this);
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) {
return PermissionHandlerPlatform.instance.requestPermissions(permissions);
} }
@override /// Request the user for access to this [Permission], if access hasn't already
Future<bool> shouldShowRequestPermissionRationale( /// been grant access before.
PermissionGroup permission) { ///
if (!Platform.isAndroid) { /// Returns the new [PermissionStatus].
return Future.value(false); Future<PermissionStatus> request() async {
return (await [this].request())[this];
} }
}
return PermissionHandlerPlatform.instance /// Shortcuts for checking the [status] of a [Permission].
.shouldShowRequestPermissionRationale(permission); extension PermissionCheckShortcuts on Permission {
} /// If this permission was never requested before.
Future<bool> get isUndetermined => status.isUndetermined;
/// If the user granted this permission.
Future<bool> get isGranted => status.isGranted;
/// If the user denied this permission.
Future<bool> get isDenied => status.isDenied;
/// If the OS denied this permission. The user cannot change the status,
/// possibly due to active restrictions such as parental controls being in
/// place.
/// *Only supported on iOS.*
Future<bool> get isRestricted => status.isRestricted;
/// If the user denied this permission and selected to never again show a
/// request for it. The user may still change the permission's status in the
/// device settings.
/// *Only supported on Android.*
Future<bool> get isPermanentlyDenied => status.isPermanentlyDenied;
}
/// Actions that apply only to permissions that have an associated service.
extension ServicePermissionActions on PermissionWithService {
/// Checks the current status of the service associated with this permission.
///
/// Notes about specific permissions:
/// - **[Permission.phone]**
/// - Android:
/// - The method will return [ServiceStatus.notApplicable] when:
/// - the device lacks the TELEPHONY feature
/// - TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE
/// - when no Intents can be resolved to handle the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when:
/// - the SIM card is missing
/// - iOS:
/// - The method will return [ServiceStatus.notApplicable] when:
/// - the native code can not find a handler for the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when:
/// - 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
/// device's capability to place & connect phone calls as it also depends
/// on the network condition.
Future<ServiceStatus> get serviceStatus => _handler.checkServiceStatus(this);
}
/// Actions that can be taken on a [List] of [Permission]s.
extension PermissionListActions on List<Permission> {
/// Requests the user for access to these permissions, if they haven't already
/// been granted before.
///
/// Returns a [Map] containing the status per requested [Permission].
Future<Map<Permission, PermissionStatus>> request() =>
_handler.requestPermissions(this);
} }
...@@ -16,12 +16,14 @@ dependencies: ...@@ -16,12 +16,14 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
meta: ^1.1.6 meta: ^1.1.6
permission_handler_platform_interface: ^1.0.0 # permission_handler_platform_interface: ^1.0.0
permission_handler_platform_interface:
path: ../permission_handler_platform_interface
dev_dependencies: dev_dependencies:
effective_dart: ^1.2.1 effective_dart: ^1.2.1
plugin_platform_interface: ^1.0.1 plugin_platform_interface: ^1.0.1
environment: environment:
sdk: ">=2.1.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.12.8 <2.0.0" flutter: ">=1.12.8 <2.0.0"
...@@ -5,4 +5,6 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; ...@@ -5,4 +5,6 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'src/method_channel/method_channel_permission_handler.dart'; import 'src/method_channel/method_channel_permission_handler.dart';
part 'src/permission_handler_platform_interface.dart'; part 'src/permission_handler_platform_interface.dart';
part 'src/permission_handler_enums.dart'; part 'src/permission_status.dart';
part 'src/permissions.dart';
part 'src/service_status.dart';
...@@ -8,78 +8,71 @@ import 'utils/codec.dart'; ...@@ -8,78 +8,71 @@ import 'utils/codec.dart';
const MethodChannel _methodChannel = const MethodChannel _methodChannel =
MethodChannel('flutter.baseflow.com/permissions/methods'); MethodChannel('flutter.baseflow.com/permissions/methods');
/// An implementation of [PermissionHandlerPlatform] that uses method channels. /// An implementation of [PermissionHandlerPlatform] that uses [MethodChannel]s.
class MethodChannelPermissionHandler extends PermissionHandlerPlatform { class MethodChannelPermissionHandler extends PermissionHandlerPlatform {
/// Check current permission status. /// Checks the current status of the given [Permission].
/// Future<PermissionStatus> checkPermissionStatus(Permission permission) async {
/// Returns a [Future] containing the current permission status for the
/// supplied [PermissionGroup].
Future<PermissionStatus> checkPermissionStatus(
PermissionGroup permission) async {
final status = await _methodChannel.invokeMethod( final status = await _methodChannel.invokeMethod(
'checkPermissionStatus', permission.value); 'checkPermissionStatus', permission.value);
return Codec.decodePermissionStatus(status); return Codec.decodePermissionStatus(status);
} }
/// Check current service status. /// Checks the current status of the service associated with the given
/// /// [Permission].
/// Returns a [Future] containing the current service status for the supplied
/// [PermissionGroup].
/// ///
/// Notes about specific PermissionGroups: /// Notes about specific permissions:
/// - **PermissionGroup.phone** /// - **[Permission.phone]**
/// - Android: /// - Android:
/// - The method will return [ServiceStatus.notApplicable] when: /// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the device lacks the TELEPHONY feature /// - the device lacks the TELEPHONY feature
/// 1. TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE /// - TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE
/// 1. when no Intents can be resolved to handle the `tel:` scheme /// - when no Intents can be resolved to handle the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when: /// - The method will return [ServiceStatus.disabled] when:
/// 1. the SIM card is missing /// - the SIM card is missing
/// - iOS: /// - iOS:
/// - The method will return [ServiceStatus.notApplicable] when: /// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the native code can not find a handler for the `tel:` scheme /// - the native code can not find a handler for the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when: /// - The method will return [ServiceStatus.disabled] when:
/// 1. the mobile network code (MNC) is either 0 or 65535. See /// - the mobile network code (MNC) is either 0 or 65535. See
/// https://stackoverflow.com/a/11595365 for details /// https://stackoverflow.com/a/11595365 for details
/// - **PLEASE NOTE that this is still not a perfect indication** of the /// - **PLEASE NOTE that this is still not a perfect indication** of the
/// devices' capability to place & connect phone calls /// device's capability to place & connect phone calls as it also depends
/// as it also depends on the network condition. /// on the network condition.
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) async { Future<ServiceStatus> checkServiceStatus(Permission permission) async {
final status = await _methodChannel.invokeMethod( final status = await _methodChannel.invokeMethod(
'checkServiceStatus', permission.value); 'checkServiceStatus', permission.value);
return Codec.decodeServiceStatus(status); return Codec.decodeServiceStatus(status);
} }
/// Open the App settings page. /// Opens the app settings page.
/// ///
/// Returns [true] if the app settings page could be opened, /// Returns [true] if the app settings page could be opened, otherwise [false].
/// otherwise [false] is returned.
Future<bool> openAppSettings() async { Future<bool> openAppSettings() async {
final hasOpened = await _methodChannel.invokeMethod('openAppSettings'); final wasOpened = await _methodChannel.invokeMethod('openAppSettings');
return wasOpened;
return hasOpened;
} }
/// Request the user for access to the supplied list of permissiongroups. /// Requests the user for access to the supplied list of [Permission]s, if
/// they have not already been granted before.
/// ///
/// Returns a [Map] containing the status per requested permissiongroup. /// Returns a [Map] containing the status per requested [Permission].
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions( Future<Map<Permission, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) async { List<Permission> permissions) async {
final data = Codec.encodePermissionGroups(permissions); final data = Codec.encodePermissions(permissions);
final status = final status =
await _methodChannel.invokeMethod('requestPermissions', data); await _methodChannel.invokeMethod('requestPermissions', data);
return Codec.decodePermissionRequestResult(Map<int, int>.from(status)); return Codec.decodePermissionRequestResult(Map<int, int>.from(status));
} }
/// Request to see if you should show a rationale for requesting permission. /// Checks if you should show a rationale for requesting permission.
/// ///
/// This method is only implemented on Android, calling this on iOS always /// This method is only implemented on Android, calling this on iOS always
/// returns [false]. /// returns [false].
Future<bool> shouldShowRequestPermissionRationale( Future<bool> shouldShowRequestPermissionRationale(
PermissionGroup permission) async { Permission permission) async {
final shouldShowRationale = await _methodChannel.invokeMethod( final shouldShowRationale = await _methodChannel.invokeMethod(
'shouldShowRequestPermissionRationale', permission.value); 'shouldShowRequestPermissionRationale', permission.value);
......
import '../../../permission_handler_platform_interface.dart'; import '../../../permission_handler_platform_interface.dart';
/// Provides utility methods for encoding messages that are send on the Flutter /// Provides utility methods for encoding messages that are sent on the Flutter
/// message channel. /// message channel.
class Codec { class Codec {
/// Converts the supplied integer value into a [PermissionStatus] instance. /// Converts the given [value] into a [PermissionStatus] instance.
static PermissionStatus decodePermissionStatus(int value) { static PermissionStatus decodePermissionStatus(int value) {
return PermissionStatus.values[value]; return PermissionStatusValue.statusByValue(value);
} }
/// Converts the supplied integer value into a [ServiceStatus] instance. /// Converts the given [value] into a [ServiceStatus] instance.
static ServiceStatus decodeServiceStatus(int value) { static ServiceStatus decodeServiceStatus(int value) {
return ServiceStatus.values[value]; return ServiceStatusValue.statusByValue(value);
} }
/// Converts the supplied [Map] of integers into a [Map] of /// Converts the given [Map] of [int]s into a [Map] with [Permission]s as
/// [PermissionGroup] key and [PermissionStatus] value instances. /// keys and their respective [PermissionStatus] as value.
static Map<PermissionGroup, PermissionStatus> decodePermissionRequestResult( static Map<Permission, PermissionStatus> decodePermissionRequestResult(
Map<int, int> value) { Map<int, int> value) {
return value.map((key, value) => return value.map((key, value) => MapEntry<Permission, PermissionStatus>(
MapEntry<PermissionGroup, PermissionStatus>( Permission.byValue(key), PermissionStatusValue.statusByValue(value)));
PermissionGroup.values[key], PermissionStatus.values[value]));
} }
/// Converts the supplied [List] containing [PermissionGroup] instances into /// Converts the given [List] of [Permission]s into a [List] of [int]s which
/// a [List] containing integers which can be used to send on the Flutter /// can be sent on the Flutter method channel.
/// method channel. static List<int> encodePermissions(List<Permission> permissions) {
static List<int> encodePermissionGroups(List<PermissionGroup> permissions) {
return permissions.map((it) => it.value).toList(); return permissions.map((it) => it.value).toList();
} }
} }
part of permission_handler_platform_interface;
/// Defines the state of a permission group
class PermissionStatus {
const PermissionStatus._(this.value);
/// Integer representation of the [PermissionStatus].
final int value;
/// Permission to access the requested feature is denied by the user.
static const PermissionStatus denied = PermissionStatus._(0);
/// Permission to access the requested feature is granted by the user.
static const PermissionStatus granted = PermissionStatus._(1);
/// Permission to access the requested feature is denied by the OS (only on
/// iOS). The user cannot change this app's status, possibly due to active
/// restrictions such as parental controls being in place.
static const PermissionStatus restricted = PermissionStatus._(2);
/// Permission is in an unknown state
static const PermissionStatus unknown = PermissionStatus._(3);
/// Permission to access the requested feature is denied by the user and
/// never show selected (only on Android).
static const PermissionStatus neverAskAgain = PermissionStatus._(4);
/// Returns a list of all possible [PermissionStatus] values.
static const List<PermissionStatus> values = <PermissionStatus>[
denied,
granted,
restricted,
unknown,
neverAskAgain,
];
static const List<String> _names = <String>[
'denied',
'granted',
'restricted',
'unknown',
'neverAskAgain',
];
@override
String toString() => 'PermissionStatus.${_names[value]}';
}
/// Defines the state of a service related to the permission group
class ServiceStatus {
const ServiceStatus._(this.value);
/// Integer representation of the [ServiceStatus].
final int value;
/// The service for the supplied permission group is disabled.
static const ServiceStatus disabled = ServiceStatus._(0);
/// The service for the supplied permission group is enabled.
static const ServiceStatus enabled = ServiceStatus._(1);
/// There is no service for the supplied permission group.
static const ServiceStatus notApplicable = ServiceStatus._(2);
/// The unknown service status indicates the state of the service could not
/// be determined.
static const ServiceStatus unknown = ServiceStatus._(3);
/// Returns a list of all possible [ServiceStatus] values.
static const List<ServiceStatus> values = <ServiceStatus>[
disabled,
enabled,
notApplicable,
unknown,
];
static const List<String> _names = <String>[
'disabled',
'enabled',
'notApplicable',
'unknown',
];
@override
String toString() => 'ServiceStatus.${_names[value]}';
}
/// Defines the permission groups for which permissions can be checked or
/// requested.
class PermissionGroup {
const PermissionGroup._(this.value);
/// Integer representation of the [PermissionGroup].
final int value;
/// Android: Calendar
/// iOS: Calendar (Events)
static const PermissionGroup calendar = PermissionGroup._(0);
/// Android: Camera
/// iOS: Photos (Camera Roll and Camera)
static const PermissionGroup camera = PermissionGroup._(1);
/// Android: Contacts
/// iOS: AddressBook
static const PermissionGroup contacts = PermissionGroup._(2);
/// Android: Fine and Coarse Location
/// iOS: CoreLocation (Always and WhenInUse)
static const PermissionGroup location = PermissionGroup._(3);
/// Android:
/// When running on Android < Q: Fine and Coarse Location
/// When running on Android Q and above: Background Location Permission
/// iOS: CoreLocation - Always
static const PermissionGroup locationAlways = PermissionGroup._(4);
/// Android: Fine and Coarse Location
/// iOS: CoreLocation - WhenInUse
static const PermissionGroup locationWhenInUse = PermissionGroup._(5);
/// Android: None
/// iOS: MPMediaLibrary
static const PermissionGroup mediaLibrary = PermissionGroup._(6);
/// Android: Microphone
/// iOS: Microphone
static const PermissionGroup microphone = PermissionGroup._(7);
/// Android: Phone
/// iOS: Nothing
static const PermissionGroup phone = PermissionGroup._(8);
/// Android: Nothing
/// iOS: Photos
static const PermissionGroup photos = PermissionGroup._(9);
/// Android: Nothing
/// iOS: Reminders
static const PermissionGroup reminders = PermissionGroup._(10);
/// Android: Body Sensors
/// iOS: CoreMotion
static const PermissionGroup sensors = PermissionGroup._(11);
/// Android: Sms
/// iOS: Nothing
static const PermissionGroup sms = PermissionGroup._(12);
/// Android: Microphone
/// iOS: Speech
static const PermissionGroup speech = PermissionGroup._(13);
/// Android: External Storage
/// iOS: Access to folders like `Documents` or `Downloads`. Implicitly
/// granted.
static const PermissionGroup storage = PermissionGroup._(14);
/// Android: Ignore Battery Optimizations
static const PermissionGroup ignoreBatteryOptimizations =
PermissionGroup._(15);
/// Android: Notification
/// iOS: Notification
static const PermissionGroup notification = PermissionGroup._(16);
/// Android: Allows an application to access any geographic locations
/// persisted in the user's shared collection.
static const PermissionGroup accessMediaLocation = PermissionGroup._(17);
/// When running on Android Q and above: Activity Recognition
/// When running on Android < Q: Nothing
/// iOS: Nothing
static const PermissionGroup activityRecognition = PermissionGroup._(18);
/// The unknown permission only used for return type, never requested
static const PermissionGroup unknown = PermissionGroup._(19);
/// Returns a list of all possible [PermissionGroup] values.
static const List<PermissionGroup> values = <PermissionGroup>[
calendar,
camera,
contacts,
location,
locationAlways,
locationWhenInUse,
mediaLibrary,
microphone,
phone,
photos,
reminders,
sensors,
sms,
speech,
storage,
ignoreBatteryOptimizations,
notification,
accessMediaLocation,
activityRecognition,
unknown,
];
static const List<String> _names = <String>[
'calendar',
'camera',
'contacts',
'location',
'locationAlways',
'locationWhenInUse',
'mediaLibrary',
'microphone',
'phone',
'photos',
'reminders',
'sensors',
'sms',
'speech',
'storage',
'ignoreBatteryOptimizations',
'notification',
'access_media_location',
'activity_recognition',
'unknown',
];
@override
String toString() => 'PermissionGroup.${_names[value]}';
}
part of permission_handler_platform_interface; part of permission_handler_platform_interface;
/// The interface that implementations of permission_handler must implement. /// The interface that implementations of `permission_handler` must implement.
/// ///
/// Platform implementations should extend this class rather than implement it /// Platform implementations should extend this class rather than implement it
/// as `permission_handler` does not consider newly added methods to be /// as `permission_handler` does not consider newly added methods to be
...@@ -29,64 +29,58 @@ abstract class PermissionHandlerPlatform extends PlatformInterface { ...@@ -29,64 +29,58 @@ abstract class PermissionHandlerPlatform extends PlatformInterface {
_instance = instance; _instance = instance;
} }
/// Check current permission status. /// Checks the current status of the given [Permission].
/// Future<PermissionStatus> checkPermissionStatus(Permission permission) {
/// Returns a [Future] containing the current permission status for the
/// supplied [PermissionGroup].
Future<PermissionStatus> checkPermissionStatus(PermissionGroup permission) {
throw UnimplementedError( throw UnimplementedError(
'checkPermissionStatus() has not been implemented.'); 'checkPermissionStatus() has not been implemented.');
} }
/// Check current service status. /// Checks the current status of the service associated with the given
/// /// [Permission].
/// Returns a [Future] containing the current service status for the supplied
/// [PermissionGroup].
/// ///
/// Notes about specific PermissionGroups: /// Notes about specific permissions:
/// - **PermissionGroup.phone** /// - **[Permission.phone]**
/// - Android: /// - Android:
/// - The method will return [ServiceStatus.notApplicable] when: /// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the device lacks the TELEPHONY feature /// - the device lacks the TELEPHONY feature
/// 1. TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE /// - TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE
/// 1. when no Intents can be resolved to handle the `tel:` scheme /// - when no Intents can be resolved to handle the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when: /// - The method will return [ServiceStatus.disabled] when:
/// 1. the SIM card is missing /// - the SIM card is missing
/// - iOS: /// - iOS:
/// - The method will return [ServiceStatus.notApplicable] when: /// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the native code can not find a handler for the `tel:` scheme /// - the native code can not find a handler for the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when: /// - The method will return [ServiceStatus.disabled] when:
/// 1. the mobile network code (MNC) is either 0 or 65535. See /// - the mobile network code (MNC) is either 0 or 65535. See
/// https://stackoverflow.com/a/11595365 for details /// https://stackoverflow.com/a/11595365 for details
/// - **PLEASE NOTE that this is still not a perfect indication** of the /// - **PLEASE NOTE that this is still not a perfect indication** of the
/// devices' capability to place & connect phone calls /// device's capability to place & connect phone calls as it also depends
/// as it also depends on the network condition. /// on the network condition.
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) { Future<ServiceStatus> checkServiceStatus(Permission permission) {
throw UnimplementedError('checkServiceStatus() has not been implemented.'); throw UnimplementedError('checkServiceStatus() has not been implemented.');
} }
/// Open the App settings page. /// Opens the app settings page.
/// ///
/// Returns [true] if the app settings page could be opened, /// Returns [true] if the app settings page could be opened, otherwise [false].
/// otherwise [false] is returned.
Future<bool> openAppSettings() { Future<bool> openAppSettings() {
throw UnimplementedError('openAppSettings() has not been implemented.'); throw UnimplementedError('openAppSettings() has not been implemented.');
} }
/// Request the user for access to the supplied list of permissiongroups. /// Requests the user for access to the supplied list of [Permission]s, if
/// they have not already been granted before.
/// ///
/// Returns a [Map] containing the status per requested permissiongroup. /// Returns a [Map] containing the status per requested [Permission].
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions( Future<Map<Permission, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) { List<Permission> permissions) {
throw UnimplementedError('requestPermissions() has not been implemented.'); throw UnimplementedError('requestPermissions() has not been implemented.');
} }
/// Request to see if you should show a rationale for requesting permission. /// Checks if you should show a rationale for requesting permission.
/// ///
/// This method is only implemented on Android, calling this on iOS always /// This method is only implemented on Android, calling this on iOS always
/// returns [false]. /// returns [false].
Future<bool> shouldShowRequestPermissionRationale( Future<bool> shouldShowRequestPermissionRationale(Permission permission) {
PermissionGroup permission) {
throw UnimplementedError( throw UnimplementedError(
'shouldShowRequestPermissionRationale() has not been implemented.'); 'shouldShowRequestPermissionRationale() has not been implemented.');
} }
......
part of permission_handler_platform_interface;
/// Defines the state of a [Permission].
enum PermissionStatus {
/// The permission wasn't requested yet.
undetermined,
/// The user granted access to the requested feature.
granted,
/// The user denied access to the requested feature.
denied,
/// The OS denied access to the requested feature. The user cannot change
/// this app's status, possibly due to active restrictions such as parental
/// controls being in place.
/// *Only supported on iOS.*
restricted,
/// The user denied access to the requested feature and selected to never
/// again show a request for this permission. The user may still change the
/// permission status in the settings.
/// *Only supported on Android.*
permanentlyDenied,
}
extension PermissionStatusValue on PermissionStatus {
int get value {
switch (this) {
case PermissionStatus.denied:
return 0;
case PermissionStatus.granted:
return 1;
case PermissionStatus.restricted:
return 2;
case PermissionStatus.undetermined:
return 3;
case PermissionStatus.permanentlyDenied:
return 4;
default:
throw UnimplementedError();
}
}
static PermissionStatus statusByValue(int value) {
return [
PermissionStatus.denied,
PermissionStatus.granted,
PermissionStatus.restricted,
PermissionStatus.undetermined,
PermissionStatus.permanentlyDenied,
][value];
}
}
extension PermissionStatusGetters on PermissionStatus {
/// If the permission was never requested before.
bool get isUndetermined => this == PermissionStatus.undetermined;
/// If the user granted access to the requested feature.
bool get isGranted => this == PermissionStatus.granted;
/// If the user denied access to the requested feature.
bool get isDenied => this == PermissionStatus.denied;
/// If the OS denied access to the requested feature. The user cannot change
/// this app's status, possibly due to active restrictions such as parental
/// controls being in place.
/// *Only supported on iOS.*
bool get isRestricted => this == PermissionStatus.restricted;
/// If the user denied access to the requested feature and selected to never
/// again show a request for this permission. The user may still change the
/// permission status in the settings.
/// *Only supported on Android.*
bool get isPermanentlyDenied => this == PermissionStatus.permanentlyDenied;
}
extension FuturePermissionStatusGetters on Future<PermissionStatus> {
/// If the permission was never requested before.
Future<bool> get isUndetermined async => (await this).isUndetermined;
/// If the user granted access to the requested feature.
Future<bool> get isGranted async => (await this).isGranted;
/// If the user denied access to the requested feature.
Future<bool> get isDenied async => (await this).isDenied;
/// If the OS denied access to the requested feature. The user cannot change
/// this app's status, possibly due to active restrictions such as parental
/// controls being in place.
/// *Only supported on iOS.*
Future<bool> get isRestricted async => (await this).isRestricted;
/// If the user denied access to the requested feature and selected to never
/// again show a request for this permission. The user may still change the
/// permission status in the settings.
/// *Only supported on Android.*
Future<bool> get isPermanentlyDenied async =>
(await this).isPermanentlyDenied;
}
part of permission_handler_platform_interface;
/// A special kind of permission used to access a service. Additionally to the
/// actions that normal [Permission]s have, you can also query the status of
/// the related service.
class PermissionWithService extends Permission {
const PermissionWithService._(int value) : super._(value);
}
/// Defines the permissions which can be checked and requested.
class Permission {
const Permission._(this.value);
factory Permission.byValue(int value) => values[value];
/// Integer representation of the [Permission].
final int value;
/// Android: Calendar
/// iOS: Calendar (Events)
static const calendar = Permission._(0);
/// Android: Camera
/// iOS: Photos (Camera Roll and Camera)
static const camera = Permission._(1);
/// Android: Contacts
/// iOS: AddressBook
static const contacts = Permission._(2);
/// Android: Fine and Coarse Location
/// iOS: CoreLocation (Always and WhenInUse)
static const location = PermissionWithService._(3);
/// Android:
/// When running on Android < Q: Fine and Coarse Location
/// When running on Android Q and above: Background Location Permission
/// iOS: CoreLocation - Always
static const locationAlways = PermissionWithService._(4);
/// Android: Fine and Coarse Location
/// iOS: CoreLocation - WhenInUse
static const locationWhenInUse = PermissionWithService._(5);
/// Android: None
/// iOS: MPMediaLibrary
static const mediaLibrary = Permission._(6);
/// Android: Microphone
/// iOS: Microphone
static const microphone = Permission._(7);
/// Android: Phone
/// iOS: Nothing
static const phone = Permission._(8);
/// Android: Nothing
/// iOS: Photos
static const photos = Permission._(9);
/// Android: Nothing
/// iOS: Reminders
static const reminders = Permission._(10);
/// Android: Body Sensors
/// iOS: CoreMotion
static const sensors = Permission._(11);
/// Android: Sms
/// iOS: Nothing
static const sms = Permission._(12);
/// Android: Microphone
/// iOS: Speech
static const speech = Permission._(13);
/// Android: External Storage
/// iOS: Access to folders like `Documents` or `Downloads`. Implicitly
/// granted.
static const storage = Permission._(14);
/// Android: Ignore Battery Optimizations
static const ignoreBatteryOptimizations = Permission._(15);
/// Android: Notification
/// iOS: Notification
static const notification = Permission._(16);
/// Android: Allows an application to access any geographic locations
/// persisted in the user's shared collection.
static const accessMediaLocation = Permission._(17);
/// When running on Android Q and above: Activity Recognition
/// When running on Android < Q: Nothing
/// iOS: Nothing
static const activityRecognition = Permission._(18);
/// The unknown only used for return type, never requested
static const unknown = Permission._(19);
/// Returns a list of all possible [PermissionGroup] values.
static const List<Permission> values = <Permission>[
calendar,
camera,
contacts,
location,
locationAlways,
locationWhenInUse,
mediaLibrary,
microphone,
phone,
photos,
reminders,
sensors,
sms,
speech,
storage,
ignoreBatteryOptimizations,
notification,
accessMediaLocation,
activityRecognition,
unknown,
];
static const List<String> _names = <String>[
'calendar',
'camera',
'contacts',
'location',
'locationAlways',
'locationWhenInUse',
'mediaLibrary',
'microphone',
'phone',
'photos',
'reminders',
'sensors',
'sms',
'speech',
'storage',
'ignoreBatteryOptimizations',
'notification',
'access_media_location',
'activity_recognition',
'unknown',
];
@override
String toString() => 'Permission.${_names[value]}';
}
part of permission_handler_platform_interface;
enum ServiceStatus {
/// The service for the permission is disabled.
disabled,
/// The service for the permission is enabled.
enabled,
/// The permission does not have an associated service on the current
/// platform.
notApplicable,
}
extension ServiceStatusValue on ServiceStatus {
int get value {
switch (this) {
case ServiceStatus.disabled:
return 0;
case ServiceStatus.enabled:
return 1;
case ServiceStatus.notApplicable:
return 2;
default:
throw UnimplementedError();
}
}
static ServiceStatus statusByValue(int value) {
return [
ServiceStatus.disabled,
ServiceStatus.enabled,
ServiceStatus.notApplicable,
][value];
}
}
extension ServiceStatusGetters on ServiceStatus {
/// If the service for the permission is disabled.
bool get isDisabled => this == ServiceStatus.disabled;
/// If the service for the permission is enabled.
bool get isEnabled => this == ServiceStatus.enabled;
/// If the permission does not have an associated service on the current
/// platform.
bool get isNotApplicable => this == ServiceStatus.notApplicable;
}
extension FutureServiceStatusGetters on Future<ServiceStatus> {
/// If the service for the permission is disabled.
Future<bool> get isDisabled async => (await this).isDisabled;
/// If the service for the permission is enabled.
Future<bool> get isEnabled async => (await this).isEnabled;
/// If the permission does not have an associated service on the current
/// platform.
Future<bool> get isNotApplicable async => (await this).isNotApplicable;
}
...@@ -18,5 +18,5 @@ dev_dependencies: ...@@ -18,5 +18,5 @@ dev_dependencies:
effective_dart: ^1.2.1 effective_dart: ^1.2.1
environment: environment:
sdk: ">=2.0.0-dev.28.0 <3.0.0" sdk: ">=2.6.0 <3.0.0"
flutter: ">=1.9.1+hotfix.4 <2.0.0" flutter: ">=1.9.1+hotfix.4 <2.0.0"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment