Commit b97a0603 by Maurits van Beusekom

Release permission_handler 5.0.0 and

permission_handler_platform_interface 2.0.0
parents 9b12a9f8 0f4ee3ad
......@@ -2,4 +2,4 @@ include: package:effective_dart/analysis_options.1.2.0.yaml
linter:
rules:
- public_member_api_docs
\ No newline at end of file
- public_member_api_docs
## 5.0.0
* **BREAKING**: Implemented more intuitive API exposed by `permission_handler_platform_interface: 2.0.0` ([#230](https://github.com/Baseflow/flutter-permission-handler/issues/230)).
## 4.4.0+hotfix.2
* Issue #235: Solved a bug which made it impossible to request service status on Android 7;
......
# 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)
A permissions plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
## Features
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.
* Check if a permission is granted.
* Request permission for a specific feature.
* Open app settings so the user can enable a permission.
* Show a rationale for requesting permission (Android).
This plugin provides a cross-platform (iOS, Android) API to request permissions and check their status.
You can also open the device's app settings so users can grant a permission.
On Android, you can show a rationale for requesting a permission.
## 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
dependencies:
permission_handler: '^4.4.0+hotfix.2'
```
<details>
<summary>Android</summary>
> **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 TL;DR version is:
> The current version of the plugin requires AndroidX.
>
>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
>
> ...
>}
>```
>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
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:
- [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);
- [Info.plist](https://github.com/Baseflow/flutter-permission-handler/blob/develop/example/ios/Runner/Info.plist)
> 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).
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.
> 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).
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.
</details>
<details>
<summary>iOS</summary>
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.
> 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.
You can remove permissions you don't use by:
> 1. Add the following to your `Podfile` file:
>
> ```ruby
> post_install do |installer|
> installer.pods_project.targets.each do |target|
> target.build_configurations.each do |config|
> ... # 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
### Requesting permission
```dart
import 'package:permission_handler/permission_handler.dart';
Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler().requestPermissions([PermissionGroup.contacts]);
```
### Checking permission
```dart
import 'package:permission_handler/permission_handler.dart';
PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.contacts);
```
### Checking service status
```dart
import 'package:permission_handler/permission_handler.dart';
ServiceStatus serviceStatus = await PermissionHandler().checkServiceStatus(PermissionGroup.location);
```
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`.
### Open app settings
You can remove permissions you don't use:
1. Add the following to your `Podfile` file:
```ruby
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
... # 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
</details>
## How to use
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
import 'package:permission_handler/permission_handler.dart';
var status = await Permission.camera.status;
if (status.isUndetermined) {
// We didn't ask for permission yet.
}
bool isOpened = await PermissionHandler().openAppSettings();
// 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.
}
```
### Show a rationale for requesting permission (Android only)
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
import 'package:permission_handler/permission_handler.dart';
if (await Permission.contacts.request().isGranted) {
// Either the permission was already granted before or the user just granted it.
}
bool isShown = await PermissionHandler().shouldShowRequestPermissionRationale(PermissionGroup.contacts);
// You can request multiple permissions at once.
Map<Permission, PermissionStatus> statuses = await [
Permission.location,
Permission.storage,
].request();
print(statuses[Permission.location]);
```
This will always return `false` on iOS.
### List of available permissions
Defines the permission groups for which permissions can be checked or requested.
Some permissions, for example location or acceleration sensor permissions, have an associated service, which can be `enabled` or `disabled`.
```dart
enum PermissionGroup {
/// The unknown permission only used for return type, never requested
unknown,
/// 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,
if (await Permission.locationWhenInUse.serviceStatus.isEnabled) {
// Use location.
}
```
### Status of the permission
Defines the state of a permission group
You can also open the app settings:
```dart
enum PermissionStatus {
/// Permission to access the requested feature is denied by the user.
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).
restricted,
/// Permission is in an unknown state
unknown
/// Permission to access the requested feature is denied by the user and never show selected (only on Android).
neverAskAgain
if (await Permission.speech.isPermanentlyDenied) {
// The user opted to never again see the permission request dialog for this
// app. The only way to change the permission's status now is to let the
// user manually enable it in the system settings.
openAppSettings();
}
```
### Overview of possible service statuses
Defines the state of the backing service for the supplied permission group
On Android, you can show a rationale for using a permission:
```dart
/// Defines the state of a service related to the permission group
enum ServiceStatus {
/// The unknown service status indicates the state of the service could not be determined.
unknown,
/// There is no service for the supplied permission group.
notApplicable,
/// The service for the supplied permission group is disabled.
disabled,
/// The service for the supplied permission group is enabled.
enabled
}
bool isShown = await Permission.contacts.shouldShowRequestRationale;
```
## Issues
......
......@@ -5,26 +5,35 @@ import android.content.Intent;
import android.util.Log;
final class AppSettingsManager {
boolean openAppSettings(Context applicationContext) {
if (applicationContext == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context.");
return false;
@FunctionalInterface
interface OpenAppSettingsSuccessCallback {
void onSuccess(boolean appSettingsOpenedSuccessfully);
}
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 {
Intent settingsIntent = new Intent();
settingsIntent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
settingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
settingsIntent.setData(android.net.Uri.parse("package:" + applicationContext.getPackageName()));
settingsIntent.setData(android.net.Uri.parse("package:" + context.getPackageName()));
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
applicationContext.startActivity(settingsIntent);
context.startActivity(settingsIntent);
return true;
successCallback.onSuccess(true);
} 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 {
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
{
switch (call.method) {
case "checkPermissionStatus": {
case "checkServiceStatus": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
@PermissionConstants.PermissionStatus final int permissionStatus =
permissionManager.checkPermissionStatus(
permission,
applicationContext,
activity);
serviceManager.checkServiceStatus(
permission,
applicationContext,
result::success,
(String errorCode, String errorDescription) -> result.error(
errorCode,
errorDescription,
null));
result.success(permissionStatus);
break;
}
case "checkServiceStatus": {
case "checkPermissionStatus": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
@PermissionConstants.ServiceStatus final int serviceStatus =
serviceManager.checkServiceStatus(
permission,
applicationContext);
permissionManager.checkPermissionStatus(
permission,
applicationContext,
activity,
result::success,
(String errorCode, String errorDescription) -> result.error(
errorCode,
errorDescription,
null));
result.success(serviceStatus);
break;
}
case "requestPermissions":
......@@ -88,14 +94,26 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
break;
case "shouldShowRequestPermissionRationale": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
final boolean showRationale = permissionManager
.shouldShowRequestPermissionRationale(permission, activity);
result.success(showRationale);
permissionManager.shouldShowRequestPermissionRationale(
permission,
activity,
result::success,
(String errorCode, String errorDescription) -> result.error(
errorCode,
errorDescription,
null));
break;
}
case "openAppSettings":
boolean isOpen = appSettingsManager.openAppSettings(applicationContext);
result.success(isOpen);
appSettingsManager.openAppSettings(
applicationContext,
result::success,
(String errorCode, String errorDescription) -> result.error(
errorCode,
errorDescription,
null));
break;
default:
result.notImplemented();
......
......@@ -62,7 +62,7 @@ final class PermissionConstants {
static final int PERMISSION_STATUS_DENIED = 0;
static final int PERMISSION_STATUS_GRANTED = 1;
static final int PERMISSION_STATUS_RESTRICTED = 2;
static final int PERMISSION_STATUS_UNKNOWN = 3;
static final int PERMISSION_STATUS_NOT_DETERMINED = 3;
static final int PERMISSION_STATUS_NEWER_ASK_AGAIN = 4;
@Retention(RetentionPolicy.SOURCE)
......@@ -70,7 +70,7 @@ final class PermissionConstants {
PERMISSION_STATUS_DENIED,
PERMISSION_STATUS_GRANTED,
PERMISSION_STATUS_RESTRICTED,
PERMISSION_STATUS_UNKNOWN,
PERMISSION_STATUS_NOT_DETERMINED,
PERMISSION_STATUS_NEWER_ASK_AGAIN,
})
@interface PermissionStatus {
......@@ -80,14 +80,12 @@ final class PermissionConstants {
static final int SERVICE_STATUS_DISABLED = 0;
static final int SERVICE_STATUS_ENABLED = 1;
static final int SERVICE_STATUS_NOT_APPLICABLE = 2;
static final int SERVICE_STATUS_UNKNOWN = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
SERVICE_STATUS_DISABLED,
SERVICE_STATUS_ENABLED,
SERVICE_STATUS_NOT_APPLICABLE,
SERVICE_STATUS_UNKNOWN,
SERVICE_STATUS_NOT_APPLICABLE
})
@interface ServiceStatus {
}
......
......@@ -36,16 +36,16 @@ public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAwa
* <p>Calling this automatically initializes the plugin. However plugins initialized this way
* won't react to changes in activity or context, unlike {@link PermissionHandlerPlugin}.
*/
public static void registerWith(Registrar registrar) {
final PermissionHandlerPlugin permissionHandlerPlugin = new PermissionHandlerPlugin();
permissionHandlerPlugin.startListening(
registrar.context(),
registrar.activity(),
registrar.messenger(),
registrar::addActivityResultListener,
registrar::addRequestPermissionsResultListener
);
}
public static void registerWith(Registrar registrar) {
final PermissionHandlerPlugin permissionHandlerPlugin = new PermissionHandlerPlugin();
permissionHandlerPlugin.startListening(
registrar.context(),
registrar.activity(),
registrar.messenger(),
registrar::addActivityResultListener,
registrar::addRequestPermissionsResultListener
);
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
......
......@@ -23,79 +23,52 @@ import java.util.Map;
import io.flutter.plugin.common.PluginRegistry;
final class PermissionManager {
@FunctionalInterface
interface ActivityRegistry {
void addListener(PluginRegistry.ActivityResultListener handler);
}
@FunctionalInterface
interface PermissionRegistry {
void addListener(PluginRegistry.RequestPermissionsResultListener handler);
}
interface ResultCallback {
void onResult(Map<Integer, Integer> results);
@FunctionalInterface
interface RequestPermissionsSuccessCallback {
void onSuccess(Map<Integer, Integer> results);
}
interface ErrorCallback {
void onError(String errorCode, String errorDescription);
@FunctionalInterface
interface CheckPermissionsSuccessCallback {
void onSuccess(@PermissionConstants.PermissionStatus int permissionStatus);
}
@FunctionalInterface
interface ShouldShowRequestPermissionRationaleSuccessCallback {
void onSuccess(boolean shouldShowRequestPermissionRationale);
}
private boolean ongoing = false;
@PermissionConstants.PermissionStatus
int checkPermissionStatus(
void checkPermissionStatus(
@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_UNKNOWN;
}
final boolean targetsMOrHigher = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
Activity activity,
CheckPermissionsSuccessCallback successCallback,
ErrorCallback errorCallback) {
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 (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;
}
}
if(activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Activity cannot be null.");
errorCallback.onError(
"PermissionHandler.PermissionManager",
"Android activity is required to check for permissions and cannot be null.");
return;
}
return PermissionConstants.PERMISSION_STATUS_GRANTED;
successCallback.onSuccess(determinePermissionStatus(
permission,
context,
activity));
}
void requestPermissions(
......@@ -103,25 +76,28 @@ final class PermissionManager {
Activity activity,
ActivityRegistry activityRegistry,
PermissionRegistry permissionRegistry,
ResultCallback resultCallback,
RequestPermissionsSuccessCallback successCallback,
ErrorCallback errorCallback) {
if(ongoing) {
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).");
return;
}
if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity.");
errorCallback.onError("ERROR_ANDROID_ACTIVITY_MISSING", "Unable to detect current Android Activity.");
errorCallback.onError(
"PermissionHandler.PermissionManager",
"Unable to detect current Android Activity.");
return;
}
Map<Integer, Integer> requestResults = new HashMap<>();
ArrayList<String> permissionsToRequest = new ArrayList<>();
for (Integer permission : permissions) {
@PermissionConstants.PermissionStatus final int permissionStatus = checkPermissionStatus(permission, activity.getApplicationContext(), activity);
@PermissionConstants.PermissionStatus final int permissionStatus = determinePermissionStatus(permission, activity, activity);
if (permissionStatus == PermissionConstants.PERMISSION_STATUS_GRANTED) {
if (!requestResults.containsKey(permission)) {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_GRANTED);
......@@ -135,7 +111,7 @@ final class PermissionManager {
// if we can't add as unknown and continue
if (names == null || names.isEmpty()) {
if (!requestResults.containsKey(permission)) {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_UNKNOWN);
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_NOT_DETERMINED);
}
continue;
......@@ -143,7 +119,7 @@ final class PermissionManager {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
activityRegistry.addListener(
new ActivityResultListener(resultCallback)
new ActivityResultListener(successCallback)
);
String packageName = activity.getPackageName();
......@@ -164,7 +140,7 @@ final class PermissionManager {
requestResults,
(Map<Integer, Integer> results) -> {
ongoing = false;
resultCallback.onResult(results);
successCallback.onSuccess(results);
})
);
......@@ -177,15 +153,86 @@ final class PermissionManager {
} else {
ongoing = false;
if (requestResults.size() > 0) {
resultCallback.onResult(requestResults);
successCallback.onSuccess(requestResults);
}
}
}
@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;
}
boolean shouldShowRequestPermissionRationale(int permission, Activity activity) {
void shouldShowRequestPermissionRationale(
int permission,
Activity activity,
ShouldShowRequestPermissionRationaleSuccessCallback successCallback,
ErrorCallback errorCallback) {
if (activity == null) {
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);
......@@ -193,15 +240,17 @@ final class PermissionManager {
// if isn't an android specific group then go ahead and return false;
if (names == null) {
Log.d(PermissionConstants.LOG_TAG, "No android specific permissions needed for: " + permission);
return false;
successCallback.onSuccess(false);
return;
}
if (names.isEmpty()) {
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) {
......@@ -223,10 +272,10 @@ final class PermissionManager {
// call.
boolean alreadyCalled = false;
final ResultCallback callback;
final RequestPermissionsSuccessCallback callback;
@VisibleForTesting
ActivityResultListener(ResultCallback callback) {
ActivityResultListener(RequestPermissionsSuccessCallback callback) {
this.callback = callback;
}
......@@ -243,7 +292,7 @@ final class PermissionManager {
HashMap<Integer, Integer> results = new HashMap<>();
results.put(PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS, status);
callback.onResult(results);
callback.onSuccess(results);
return true;
}
}
......@@ -259,14 +308,14 @@ final class PermissionManager {
boolean alreadyCalled = false;
final Activity activity;
final ResultCallback callback;
final RequestPermissionsSuccessCallback callback;
final Map<Integer, Integer> requestResults;
@VisibleForTesting
RequestPermissionsListener(
Activity activity,
Map<Integer, Integer> requestResults,
ResultCallback callback) {
RequestPermissionsSuccessCallback callback) {
this.activity = activity;
this.callback = callback;
this.requestResults = requestResults;
......@@ -282,8 +331,10 @@ final class PermissionManager {
alreadyCalled = true;
for (int i = 0; i < permissions.length; i++) {
final String permissionName = permissions[i];
@PermissionConstants.PermissionGroup final int permission =
PermissionUtils.parseManifestName(permissions[i]);
PermissionUtils.parseManifestName(permissionName);
if (permission == PermissionConstants.PERMISSION_GROUP_UNKNOWN)
continue;
......@@ -294,23 +345,23 @@ final class PermissionManager {
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_MICROPHONE)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_MICROPHONE,
PermissionUtils.toPermissionStatus(this.activity, permission, result));
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_SPEECH)) {
requestResults.put(
PermissionConstants.PERMISSION_GROUP_SPEECH,
PermissionUtils.toPermissionStatus(this.activity, permission, result));
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS) {
@PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permission, result);
PermissionUtils.toPermissionStatus(this.activity, permissionName, result);
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) {
requestResults.put(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus);
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION) {
@PermissionConstants.PermissionStatus int permissionStatus =
PermissionUtils.toPermissionStatus(this.activity, permission, result);
PermissionUtils.toPermissionStatus(this.activity, permissionName, result);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) {
......@@ -330,13 +381,13 @@ final class PermissionManager {
} else if (!requestResults.containsKey(permission)) {
requestResults.put(
permission,
PermissionUtils.toPermissionStatus(this.activity, permission, result));
PermissionUtils.toPermissionStatus(this.activity, permissionName, result));
}
PermissionUtils.updatePermissionShouldShowStatus(this.activity, permission);
}
this.callback.onResult(requestResults);
this.callback.onSuccess(requestResults);
return true;
}
}
......
......@@ -238,9 +238,9 @@ public class PermissionUtils {
}
@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) {
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_DENIED;
}
......@@ -265,23 +265,12 @@ public class PermissionUtils {
}
@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) {
return false;
}
List<String> names = getManifestNames(activity, permission);
if (names == null || names.isEmpty()) {
return false;
}
boolean isNeverAskAgainSelected = false;
for (String name : names) {
isNeverAskAgainSelected |= PermissionUtils.neverAskAgainSelected(activity, name);
}
return isNeverAskAgainSelected;
return PermissionUtils.neverAskAgainSelected(activity, name);
}
@RequiresApi(api = Build.VERSION_CODES.M)
......@@ -298,7 +287,7 @@ public class PermissionUtils {
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);
return genPrefs.getBoolean(permission, false);
}
......
......@@ -17,34 +17,45 @@ import android.util.Log;
import java.util.List;
final class ServiceManager {
@PermissionConstants.ServiceStatus
int checkServiceStatus(
@FunctionalInterface
interface SuccessCallback {
void onSuccess(@PermissionConstants.ServiceStatus int serviceStatus);
}
void checkServiceStatus(
int permission,
Context context) {
if (context == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context.");
return PermissionConstants.SERVICE_STATUS_UNKNOWN;
Context context,
SuccessCallback successCallback,
ErrorCallback errorCallback) {
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 ||
permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS ||
permission == PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE) {
return isLocationServiceEnabled(context)
final int serviceStatus = isLocationServiceEnabled(context)
? PermissionConstants.SERVICE_STATUS_ENABLED
: PermissionConstants.SERVICE_STATUS_DISABLED;
successCallback.onSuccess(serviceStatus);
}
if (permission == PermissionConstants.PERMISSION_GROUP_PHONE) {
PackageManager pm = context.getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE);
return;
}
TelephonyManager telephonyManager = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
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);
......@@ -52,23 +63,28 @@ final class ServiceManager {
List<ResolveInfo> callAppsList = pm.queryIntentActivities(callIntent, 0);
if (callAppsList.isEmpty()) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE);
return;
}
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) {
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_NOT_APPLICABLE;
successCallback.onSuccess(serviceStatus);
return;
}
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE);
}
private boolean isLocationServiceEnabled(Context context) {
......@@ -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.
@SuppressWarnings("deprecation")
private static boolean isLocationServiceEnabledKitKat(Context context)
......@@ -109,7 +125,7 @@ final class ServiceManager {
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.
@SuppressWarnings("deprecation")
private static boolean isLocationServiceEnablePreKitKat(Context context)
......
# 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.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
......@@ -15,96 +15,70 @@ def parse_KV_file(file, separator='=')
if !File.exists? file_abs_path
return [];
end
pods_ary = []
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_ary.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_ary
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
end
generated_key_values
end
target 'Runner' do
# 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')
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
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
generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
if generated_xcode_build_settings.empty?
puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
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]))
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
}
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
# 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.map { |p|
symlink = File.join('.symlinks', 'plugins', p[:name])
File.symlink(p[:path], symlink)
pod p[:name], :path => File.join(symlink, 'ios')
}
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
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
......@@ -9,10 +9,6 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
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 */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
......@@ -28,8 +24,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
......@@ -40,7 +34,6 @@
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>"; };
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>"; };
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>"; };
......@@ -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>"; };
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>"; };
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; };
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>"; };
......@@ -64,8 +56,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
B7634AD7E1771AEDD2D1A5F7 /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
......@@ -84,9 +74,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
......@@ -232,7 +220,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
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 */ = {
isa = PBXShellScriptBuildPhase;
......@@ -241,7 +229,7 @@
);
inputPaths = (
"${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";
outputPaths = (
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
......@@ -38,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
......@@ -61,8 +59,6 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
......
......@@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
<string>Latest</string>
</dict>
</plist>
......@@ -17,33 +17,31 @@ class MyApp extends StatelessWidget {
actions: <Widget>[
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
PermissionHandler().openAppSettings().then((bool hasOpened) =>
debugPrint('App Settings opened: ' + hasOpened.toString()));
onPressed: () async {
var hasOpened = openAppSettings();
debugPrint('App Settings opened: ' + hasOpened.toString());
},
)
],
),
body: Center(
child: ListView(
children: PermissionGroup.values
.where((PermissionGroup permission) {
children: Permission.values
.where((Permission permission) {
if (Platform.isIOS) {
return permission != PermissionGroup.unknown &&
permission != PermissionGroup.sms &&
permission != PermissionGroup.storage &&
permission !=
PermissionGroup.ignoreBatteryOptimizations &&
permission != PermissionGroup.accessMediaLocation;
return permission != Permission.unknown &&
permission != Permission.sms &&
permission != Permission.storage &&
permission != Permission.ignoreBatteryOptimizations &&
permission != Permission.accessMediaLocation;
} else {
return permission != PermissionGroup.unknown &&
permission != PermissionGroup.mediaLibrary &&
permission != PermissionGroup.photos &&
permission != PermissionGroup.reminders;
return permission != Permission.unknown &&
permission != Permission.mediaLibrary &&
permission != Permission.photos &&
permission != Permission.reminders;
}
})
.map((PermissionGroup permission) =>
PermissionWidget(permission))
.map((permission) => PermissionWidget(permission))
.toList()),
),
),
......@@ -54,20 +52,20 @@ class MyApp extends StatelessWidget {
/// Permission widget which displays a permission and allows users to request
/// the permissions.
class PermissionWidget extends StatefulWidget {
/// Constructs a [PermissionWidget] for the supplied [PermissionGroup].
const PermissionWidget(this._permissionGroup);
/// Constructs a [PermissionWidget] for the supplied [Permission].
const PermissionWidget(this._permission);
final PermissionGroup _permissionGroup;
final Permission _permission;
@override
_PermissionState createState() => _PermissionState(_permissionGroup);
_PermissionState createState() => _PermissionState(_permission);
}
class _PermissionState extends State<PermissionWidget> {
_PermissionState(this._permissionGroup);
_PermissionState(this._permission);
final PermissionGroup _permissionGroup;
PermissionStatus _permissionStatus = PermissionStatus.unknown;
final Permission _permission;
PermissionStatus _permissionStatus = PermissionStatus.undetermined;
@override
void initState() {
......@@ -76,15 +74,9 @@ class _PermissionState extends State<PermissionWidget> {
_listenForPermissionStatus();
}
void _listenForPermissionStatus() {
final Future<PermissionStatus> statusFuture =
PermissionHandler().checkPermissionStatus(_permissionGroup);
statusFuture.then((PermissionStatus status) {
setState(() {
_permissionStatus = status;
});
});
void _listenForPermissionStatus() async {
final status = await _permission.status;
setState(() => _permissionStatus = status);
}
Color getPermissionColor() {
......@@ -101,7 +93,7 @@ class _PermissionState extends State<PermissionWidget> {
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(_permissionGroup.toString()),
title: Text(_permission.toString()),
subtitle: Text(
_permissionStatus.toString(),
style: TextStyle(color: getPermissionColor()),
......@@ -109,33 +101,26 @@ class _PermissionState extends State<PermissionWidget> {
trailing: IconButton(
icon: const Icon(Icons.info),
onPressed: () {
checkServiceStatus(context, _permissionGroup);
checkServiceStatus(context, _permission);
}),
onTap: () {
requestPermission(_permissionGroup);
requestPermission(_permission);
},
);
}
void checkServiceStatus(BuildContext context, PermissionGroup permission) {
PermissionHandler()
.checkServiceStatus(permission)
.then((ServiceStatus serviceStatus) {
final SnackBar snackBar =
SnackBar(content: Text(serviceStatus.toString()));
Scaffold.of(context).showSnackBar(snackBar);
});
void checkServiceStatus(BuildContext context, Permission permission) async {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text((await permission.status).toString()),
));
}
Future<void> requestPermission(PermissionGroup permission) async {
final List<PermissionGroup> permissions = <PermissionGroup>[permission];
final Map<PermissionGroup, PermissionStatus> permissionRequestResult =
await PermissionHandler().requestPermissions(permissions);
Future<void> requestPermission(Permission permission) async {
final status = await permission.request();
setState(() {
print(permissionRequestResult);
_permissionStatus = permissionRequestResult[permission];
print(status);
_permissionStatus = status;
print(_permissionStatus);
});
}
......
......@@ -16,4 +16,4 @@ dev_dependencies:
path: ../
flutter:
uses-material-design: true
\ No newline at end of file
uses-material-design: true
......@@ -107,12 +107,11 @@ typedef NS_ENUM(int, PermissionStatus) {
PermissionStatusDenied = 0,
PermissionStatusGranted,
PermissionStatusRestricted,
PermissionStatusUnknown,
PermissionStatusNotDetermined,
};
typedef NS_ENUM(int, ServiceStatus) {
ServiceStatusDisabled = 0,
ServiceStatusEnabled,
ServiceStatusNotApplicable,
ServiceStatusUnknown,
};
......@@ -23,7 +23,7 @@
return PermissionStatusUnknown;
#endif
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
......@@ -33,7 +33,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) {
if (status != PermissionStatusNotDetermined) {
completionHandler(status);
return;
}
......@@ -55,7 +55,7 @@
return;
#endif
} else {
completionHandler(PermissionStatusUnknown);
completionHandler(PermissionStatusNotDetermined);
return;
}
......@@ -73,7 +73,7 @@
switch (status) {
case AVAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case AVAuthorizationStatusRestricted:
return PermissionStatusRestricted;
case AVAuthorizationStatusDenied:
......@@ -82,7 +82,7 @@
return PermissionStatusGranted;
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
@end
......
......@@ -20,7 +20,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) {
if (status != PermissionStatusNotDetermined) {
completionHandler(status);
}
......@@ -37,7 +37,7 @@
switch (status) {
case CNAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case CNAuthorizationStatusRestricted:
return PermissionStatusRestricted;
case CNAuthorizationStatusDenied:
......@@ -51,7 +51,7 @@
switch (status) {
case kABAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case kABAuthorizationStatusRestricted:
return PermissionStatusRestricted;
case kABAuthorizationStatusDenied:
......@@ -61,7 +61,7 @@
}
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
+ (void)requestPermissionsFromContactStore:(PermissionStatusHandler)completionHandler API_AVAILABLE(ios(9)) {
......
......@@ -24,7 +24,7 @@
#endif
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
......@@ -34,7 +34,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus permissionStatus = [self checkPermissionStatus:permission];
if (permissionStatus != PermissionStatusUnknown) {
if (permissionStatus != PermissionStatusNotDetermined) {
completionHandler(permissionStatus);
return;
}
......@@ -56,7 +56,7 @@
return;
#endif
} else {
completionHandler(PermissionStatusUnknown);
completionHandler(PermissionStatusNotDetermined);
return;
}
......@@ -75,7 +75,7 @@
switch (status) {
case EKAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case EKAuthorizationStatusRestricted:
return PermissionStatusRestricted;
case EKAuthorizationStatusDenied:
......@@ -84,7 +84,7 @@
return PermissionStatusGranted;
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
@end
......
......@@ -35,7 +35,7 @@
PermissionStatus status = [self checkPermissionStatus:permission];
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse && permission == PermissionGroupLocationAlways) {
// don't do anything and continue requesting permissions
} else if (status != PermissionStatusUnknown) {
} else if (status != PermissionStatusNotDetermined) {
completionHandler(status);
}
......@@ -105,7 +105,7 @@
if (permission == PermissionGroupLocationAlways) {
switch (authorizationStatus) {
case kCLAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case kCLAuthorizationStatusRestricted:
return PermissionStatusRestricted;
case kCLAuthorizationStatusDenied:
......@@ -118,7 +118,7 @@
switch (authorizationStatus) {
case kCLAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case kCLAuthorizationStatusRestricted:
return PermissionStatusRestricted;
case kCLAuthorizationStatusDenied:
......@@ -134,7 +134,7 @@
switch (authorizationStatus) {
case kCLAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case kCLAuthorizationStatusRestricted:
return PermissionStatusRestricted;
case kCLAuthorizationStatusDenied:
......@@ -142,7 +142,7 @@
case kCLAuthorizationStatusAuthorized:
return PermissionStatusGranted;
default:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
#pragma clang diagnostic pop
......
......@@ -19,7 +19,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) {
if (status != PermissionStatusNotDetermined) {
completionHandler(status);
return;
}
......@@ -29,7 +29,7 @@
completionHandler([MediaLibraryPermissionStrategy determinePermissionStatus:status]);
}];
} else {
completionHandler(PermissionStatusUnknown);
completionHandler(PermissionStatusNotDetermined);
return;
}
}
......@@ -41,13 +41,13 @@
return [MediaLibraryPermissionStrategy determinePermissionStatus:status];
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
+ (PermissionStatus)determinePermissionStatus:(MPMediaLibraryAuthorizationStatus)authorizationStatus API_AVAILABLE(ios(9.3)){
switch (authorizationStatus) {
case MPMediaLibraryAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case MPMediaLibraryAuthorizationStatusDenied:
return PermissionStatusDenied;
case MPMediaLibraryAuthorizationStatusRestricted:
......@@ -56,7 +56,7 @@
return PermissionStatusGranted;
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
@end
......
......@@ -21,7 +21,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) {
if (status != PermissionStatusNotDetermined) {
completionHandler(status);
return;
}
......@@ -59,7 +59,7 @@
if (settings.authorizationStatus == UNAuthorizationStatusDenied) {
permissionStatus = PermissionStatusDenied;
} else if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) {
permissionStatus = PermissionStatusUnknown;
permissionStatus = PermissionStatusNotDetermined;
}
dispatch_semaphore_signal(sem);
}];
......
......@@ -13,7 +13,7 @@
@implementation PhonePermissionStrategy
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission {
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
......@@ -26,7 +26,7 @@
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
completionHandler(PermissionStatusUnknown);
completionHandler(PermissionStatusNotDetermined);
}
......
......@@ -19,7 +19,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) {
if (status != PermissionStatusNotDetermined) {
completionHandler(status);
return;
}
......@@ -38,7 +38,7 @@
+ (PermissionStatus)determinePermissionStatus:(PHAuthorizationStatus)authorizationStatus {
switch (authorizationStatus) {
case PHAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case PHAuthorizationStatusRestricted:
return PermissionStatusRestricted;
case PHAuthorizationStatusDenied:
......@@ -47,7 +47,7 @@
return PermissionStatusGranted;
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
@end
......
......@@ -19,13 +19,13 @@
: ServiceStatusDisabled;
}
return ServiceStatusUnknown;
return ServiceStatusDisabled;
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) {
if (status != PermissionStatusNotDetermined) {
completionHandler(status);
return;
}
......@@ -42,7 +42,7 @@
}
}];
} else {
completionHandler(PermissionStatusUnknown);
completionHandler(PermissionStatusNotDetermined);
}
}
......@@ -54,7 +54,7 @@
switch (status) {
case CMAuthorizationStatusNotDetermined:
permissionStatus = PermissionStatusUnknown;
permissionStatus = PermissionStatusNotDetermined;
break;
case CMAuthorizationStatusRestricted:
permissionStatus = PermissionStatusRestricted;
......@@ -72,7 +72,7 @@
return permissionStatus;
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
@end
......
......@@ -19,7 +19,7 @@
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
PermissionStatus status = [self checkPermissionStatus:permission];
if (status != PermissionStatusUnknown) {
if (status != PermissionStatusNotDetermined) {
completionHandler(status);
return;
}
......@@ -29,7 +29,7 @@
completionHandler([SpeechPermissionStrategy determinePermissionStatus:authorizationStatus]);
}];
} else {
completionHandler(PermissionStatusUnknown);
completionHandler(PermissionStatusNotDetermined);
}
}
......@@ -40,13 +40,13 @@
return [SpeechPermissionStrategy determinePermissionStatus:status];
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
+ (PermissionStatus)determinePermissionStatus:(SFSpeechRecognizerAuthorizationStatus)authorizationStatus API_AVAILABLE(ios(10.0)){
switch (authorizationStatus) {
case SFSpeechRecognizerAuthorizationStatusNotDetermined:
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
case SFSpeechRecognizerAuthorizationStatusDenied:
return PermissionStatusDenied;
case SFSpeechRecognizerAuthorizationStatusRestricted:
......@@ -55,7 +55,7 @@
return PermissionStatusGranted;
}
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
@end
......
......@@ -9,14 +9,14 @@
@implementation UnknownPermissionStrategy
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission {
return PermissionStatusUnknown;
return PermissionStatusNotDetermined;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusUnknown;
return ServiceStatusDisabled;
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
completionHandler(PermissionStatusUnknown);
completionHandler(PermissionStatusNotDetermined);
}
@end
\ No newline at end of file
@end
......@@ -3,7 +3,7 @@
#
Pod::Spec.new do |s|
s.name = 'permission_handler'
s.version = '4.4.0+hotfix.2'
s.version = '5.0.0'
s.summary = 'Permission plugin for Flutter.'
s.description = <<-DESC
Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
......
import 'dart:async';
import 'dart:io';
import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart';
export 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart'
show PermissionGroup, PermissionStatus, ServiceStatus;
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
/// the Android and iOS platforms.
class PermissionHandler extends PermissionHandlerPlatform {
@override
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) {
return PermissionHandlerPlatform.instance.checkServiceStatus(permission);
}
/// Returns [true] if the app settings page could be opened, otherwise [false].
Future<bool> openAppSettings() => _handler.openAppSettings();
@override
Future<PermissionStatus> checkPermissionStatus(PermissionGroup permission) {
return PermissionHandlerPlatform.instance.checkPermissionStatus(permission);
}
/// Actions that can be executed on a permission.
extension PermissionActions on Permission {
/// The current status of this permission.
Future<PermissionStatus> get status => _handler.checkPermissionStatus(this);
@override
Future<bool> openAppSettings() {
return PermissionHandlerPlatform.instance.openAppSettings();
/// If you should show a rationale for requesting permission.
///
/// This is only implemented on Android, calling this on iOS always returns
/// [false].
Future<bool> get shouldShowRequestRationale async {
if (!Platform.isAndroid) {
return false;
}
return _handler.shouldShowRequestPermissionRationale(this);
}
@override
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) {
return PermissionHandlerPlatform.instance.requestPermissions(permissions);
/// Request the user for access to this [Permission], if access hasn't already
/// been grant access before.
///
/// Returns the new [PermissionStatus].
Future<PermissionStatus> request() async {
return (await [this].request())[this];
}
}
@override
Future<bool> shouldShowRequestPermissionRationale(
PermissionGroup permission) {
if (!Platform.isAndroid) {
return Future.value(false);
}
/// Shortcuts for checking the [status] of a [Permission].
extension PermissionCheckShortcuts on Permission {
/// If this permission was never requested before.
Future<bool> get isUndetermined => status.isUndetermined;
return PermissionHandlerPlatform.instance
.shouldShowRequestPermissionRationale(permission);
}
/// 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);
}
name: permission_handler
description: Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
version: 4.4.0+hotfix.2
version: 5.0.0
homepage: https://github.com/baseflowit/flutter-permission-handler
flutter:
......@@ -16,12 +16,12 @@ dependencies:
flutter:
sdk: flutter
meta: ^1.1.6
permission_handler_platform_interface: ^1.0.0
permission_handler_platform_interface: ^2.0.0
dev_dependencies:
effective_dart: ^1.2.1
plugin_platform_interface: ^1.0.1
environment:
sdk: ">=2.1.0 <3.0.0"
flutter: ">=1.12.8 <2.0.0"
\ No newline at end of file
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.12.8 <2.0.0"
## 2.0.0
- **BREAKING**: Created a much more intuitive API using Dart's new extension methods ([#230](https://github.com/Baseflow/flutter-permission-handler/issues/230)). Big thank you to [@marcelgarus](https://github.com/marcelgarus) for the idea and doing all the grunt work.
## 1.0.0
- Initial open-source release.
\ No newline at end of file
- Initial open-source release.
......@@ -5,4 +5,6 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'src/method_channel/method_channel_permission_handler.dart';
part 'src/permission_handler_platform_interface.dart';
part 'src/permission_handler_enums.dart';
part 'src/permission_status.dart';
part 'src/permissions.dart';
part 'src/service_status.dart';
......@@ -8,78 +8,72 @@ import 'utils/codec.dart';
const MethodChannel _methodChannel =
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 {
/// Check current permission status.
///
/// Returns a [Future] containing the current permission status for the
/// supplied [PermissionGroup].
Future<PermissionStatus> checkPermissionStatus(
PermissionGroup permission) async {
/// Checks the current status of the given [Permission].
Future<PermissionStatus> checkPermissionStatus(Permission permission) async {
final status = await _methodChannel.invokeMethod(
'checkPermissionStatus', permission.value);
return Codec.decodePermissionStatus(status);
}
/// Check current service status.
///
/// Returns a [Future] containing the current service status for the supplied
/// [PermissionGroup].
/// Checks the current status of the service associated with the given
/// [Permission].
///
/// Notes about specific PermissionGroups:
/// - **PermissionGroup.phone**
/// Notes about specific permissions:
/// - **[Permission.phone]**
/// - Android:
/// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the device lacks the TELEPHONY feature
/// 1. TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE
/// 1. when no Intents can be resolved to handle the `tel:` scheme
/// - the 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:
/// 1. the SIM card is missing
/// - the SIM card is missing
/// - iOS:
/// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the native code can not find a handler for the `tel:` scheme
/// - the native code can not find a handler for the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when:
/// 1. the mobile network code (MNC) is either 0 or 65535. See
/// - the mobile network code (MNC) is either 0 or 65535. See
/// https://stackoverflow.com/a/11595365 for details
/// - **PLEASE NOTE that this is still not a perfect indication** of the
/// devices' capability to place & connect phone calls
/// as it also depends on the network condition.
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) async {
/// device's capability to place & connect phone calls as it also depends
/// on the network condition.
Future<ServiceStatus> checkServiceStatus(Permission permission) async {
final status = await _methodChannel.invokeMethod(
'checkServiceStatus', permission.value);
return Codec.decodeServiceStatus(status);
}
/// Open the App settings page.
/// Opens the app settings page.
///
/// Returns [true] if the app settings page could be opened,
/// otherwise [false] is returned.
/// Returns [true] if the app settings page could be opened, otherwise
/// [false].
Future<bool> openAppSettings() async {
final hasOpened = await _methodChannel.invokeMethod('openAppSettings');
return hasOpened;
final wasOpened = await _methodChannel.invokeMethod('openAppSettings');
return wasOpened;
}
/// 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.
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) async {
final data = Codec.encodePermissionGroups(permissions);
/// Returns a [Map] containing the status per requested [Permission].
Future<Map<Permission, PermissionStatus>> requestPermissions(
List<Permission> permissions) async {
final data = Codec.encodePermissions(permissions);
final status =
await _methodChannel.invokeMethod('requestPermissions', data);
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
/// returns [false].
Future<bool> shouldShowRequestPermissionRationale(
PermissionGroup permission) async {
Permission permission) async {
final shouldShowRationale = await _methodChannel.invokeMethod(
'shouldShowRequestPermissionRationale', permission.value);
......
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.
class Codec {
/// Converts the supplied integer value into a [PermissionStatus] instance.
/// Converts the given [value] into a [PermissionStatus] instance.
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) {
return ServiceStatus.values[value];
return ServiceStatusValue.statusByValue(value);
}
/// Converts the supplied [Map] of integers into a [Map] of
/// [PermissionGroup] key and [PermissionStatus] value instances.
static Map<PermissionGroup, PermissionStatus> decodePermissionRequestResult(
/// Converts the given [Map] of [int]s into a [Map] with [Permission]s as
/// keys and their respective [PermissionStatus] as value.
static Map<Permission, PermissionStatus> decodePermissionRequestResult(
Map<int, int> value) {
return value.map((key, value) =>
MapEntry<PermissionGroup, PermissionStatus>(
PermissionGroup.values[key], PermissionStatus.values[value]));
return value.map((key, value) => MapEntry<Permission, PermissionStatus>(
Permission.byValue(key), PermissionStatusValue.statusByValue(value)));
}
/// Converts the supplied [List] containing [PermissionGroup] instances into
/// a [List] containing integers which can be used to send on the Flutter
/// method channel.
static List<int> encodePermissionGroups(List<PermissionGroup> permissions) {
/// Converts the given [List] of [Permission]s into a [List] of [int]s which
/// can be sent on the Flutter method channel.
static List<int> encodePermissions(List<Permission> permissions) {
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;
/// 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
/// as `permission_handler` does not consider newly added methods to be
......@@ -21,71 +21,67 @@ abstract class PermissionHandlerPlatform extends PlatformInterface {
/// Defaults to [MethodChannelPermissionHandler].
static PermissionHandlerPlatform get instance => _instance;
/// Platform-specific plugins should set this with their own platform-specific
/// class that extends [PermissionHandlerPlatform] when they register themselves.
/// Platform-specific plugins should set this with their own
/// platform-specific class that extends
/// [PermissionHandlerPlatform] when they register themselves.
static set instance(PermissionHandlerPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// Check current permission status.
///
/// Returns a [Future] containing the current permission status for the
/// supplied [PermissionGroup].
Future<PermissionStatus> checkPermissionStatus(PermissionGroup permission) {
/// Checks the current status of the given [Permission].
Future<PermissionStatus> checkPermissionStatus(Permission permission) {
throw UnimplementedError(
'checkPermissionStatus() has not been implemented.');
}
/// Check current service status.
///
/// Returns a [Future] containing the current service status for the supplied
/// [PermissionGroup].
/// Checks the current status of the service associated with the given
/// [Permission].
///
/// Notes about specific PermissionGroups:
/// - **PermissionGroup.phone**
/// Notes about specific permissions:
/// - **[Permission.phone]**
/// - Android:
/// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the device lacks the TELEPHONY feature
/// 1. TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE
/// 1. when no Intents can be resolved to handle the `tel:` scheme
/// - the 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:
/// 1. the SIM card is missing
/// - the SIM card is missing
/// - iOS:
/// - The method will return [ServiceStatus.notApplicable] when:
/// 1. the native code can not find a handler for the `tel:` scheme
/// - the native code can not find a handler for the `tel:` scheme
/// - The method will return [ServiceStatus.disabled] when:
/// 1. the mobile network code (MNC) is either 0 or 65535. See
/// - the mobile network code (MNC) is either 0 or 65535. See
/// https://stackoverflow.com/a/11595365 for details
/// - **PLEASE NOTE that this is still not a perfect indication** of the
/// devices' capability to place & connect phone calls
/// as it also depends on the network condition.
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) {
/// device's capability to place & connect phone calls as it also depends
/// on the network condition.
Future<ServiceStatus> checkServiceStatus(Permission permission) {
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,
/// otherwise [false] is returned.
/// Returns [true] if the app settings page could be opened, otherwise
/// [false].
Future<bool> openAppSettings() {
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.
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) {
/// Returns a [Map] containing the status per requested [Permission].
Future<Map<Permission, PermissionStatus>> requestPermissions(
List<Permission> permissions) {
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
/// returns [false].
Future<bool> shouldShowRequestPermissionRationale(
PermissionGroup permission) {
Future<bool> shouldShowRequestPermissionRationale(Permission permission) {
throw UnimplementedError(
'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;
}
......@@ -3,7 +3,7 @@ description: A common platform interface for the permission_handler plugin.
homepage: https://github.com/baseflow/flutter-permission-handler/tree/master/permission_handler_platform_interface
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
version: 1.0.0
version: 2.0.0
dependencies:
flutter:
......@@ -18,5 +18,5 @@ dev_dependencies:
effective_dart: ^1.2.1
environment:
sdk: ">=2.0.0-dev.28.0 <3.0.0"
flutter: ">=1.9.1+hotfix.4 <2.0.0"
\ No newline at end of file
sdk: ">=2.6.0 <3.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