Commit d6d3621e by Maurits van Beusekom

Release 4.3.0

parents ed5df6a5 2e98fd86
## 4.3.0
* Allow requesting location permissions when location services are disabled (on iOS this will redirect the user to the Location settings page);
* Android: Add support for requesting Activity Recognition permissions;
* Confirm to Effective Dart guidelines;
* Documented all public API members;
* Fixed several typos in the README.md.
## 4.2.0+hotfix.3
* Android: Fixes a bug which in some cases caused the permission `neverAskAgain` to be returned incorrectly.
......
# Flutter Permission handler Plugin
[![pub package](https://img.shields.io/pub/v/permission_handler.svg)](https://pub.dartlang.org/packages/permission_handler)
[![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.
Branch | Build Status
------- | ------------
develop | [![Build Status](https://app.bitrise.io/app/fa4f5d4bf452bcfb/status.svg?token=HorGpL_AOw2llYz39CjmdQ&branch=develop)](https://app.bitrise.io/app/fa4f5d4bf452bcfb)
master | [![Build Status](https://app.bitrise.io/app/fa4f5d4bf452bcfb/status.svg?token=HorGpL_AOw2llYz39CjmdQ&branch=master)](https://app.bitrise.io/app/fa4f5d4bf452bcfb)
## Features
* Check if a permission is granted.
......@@ -22,7 +17,7 @@ To use this plugin, add `permission_handler` as a [dependency in your pubspec.ya
```yaml
dependencies:
permission_handler: '^4.2.0+hotfix.3'
permission_handler: '^4.3.0'
```
> **NOTE:** As of version 3.1.0 the permission_handler plugin switched to the AndroidX version of the Android Support Libraries. This means you need to make sure your Android project is also upgraded to support AndroidX. Detailed instructions can be found [here](https://flutter.dev/docs/development/packages-and-plugins/androidx-compatibility).
......@@ -162,7 +157,7 @@ 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.locationWhenInUser`, `PermissionGroup.locationAlways` or `PermissionGroup.sensors` on iOS. All other permission groups are not backed by a separate service and will always return `ServiceStatus.notApplicable`.
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
......
include: package:pedantic/analysis_options.1.7.0.yaml
include: package:effective_dart/analysis_options.1.2.0.yaml
linter:
rules:
- public_member_api_docs
\ No newline at end of file
......@@ -65,7 +65,8 @@ public class PermissionHandlerPlugin implements MethodCallHandler {
private static final int PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS = 15;
private static final int PERMISSION_GROUP_NOTIFICATION = 16;
private static final int PERMISSION_GROUP_ACCESS_MEDIA_LOCATION = 17;
private static final int PERMISSION_GROUP_UNKNOWN = 18;
private static final int PERMISSION_GROUP_ACTIVITY_RECOGNITION = 18;
private static final int PERMISSION_GROUP_UNKNOWN = 19;
private PermissionHandlerPlugin(Registrar mRegistrar) {
this.mRegistrar = mRegistrar;
......@@ -91,6 +92,7 @@ public class PermissionHandlerPlugin implements MethodCallHandler {
PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS,
PERMISSION_GROUP_NOTIFICATION,
PERMISSION_GROUP_ACCESS_MEDIA_LOCATION,
PERMISSION_GROUP_ACTIVITY_RECOGNITION,
PERMISSION_GROUP_UNKNOWN,
})
private @interface PermissionGroup {
......@@ -98,15 +100,13 @@ public class PermissionHandlerPlugin implements MethodCallHandler {
//PERMISSION_STATUS
private static final int PERMISSION_STATUS_DENIED = 0;
private static final int PERMISSION_STATUS_DISABLED = 1;
private static final int PERMISSION_STATUS_GRANTED = 2;
private static final int PERMISSION_STATUS_RESTRICTED = 3;
private static final int PERMISSION_STATUS_UNKNOWN = 4;
private static final int PERMISSION_STATUS_NEWER_ASK_AGAIN = 5;
private static final int PERMISSION_STATUS_GRANTED = 1;
private static final int PERMISSION_STATUS_RESTRICTED = 2;
private static final int PERMISSION_STATUS_UNKNOWN = 3;
private static final int PERMISSION_STATUS_NEWER_ASK_AGAIN = 4;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PERMISSION_STATUS_DENIED,
PERMISSION_STATUS_DISABLED,
PERMISSION_STATUS_GRANTED,
PERMISSION_STATUS_RESTRICTED,
PERMISSION_STATUS_UNKNOWN,
......@@ -202,6 +202,8 @@ public class PermissionHandlerPlugin implements MethodCallHandler {
return PERMISSION_GROUP_STORAGE;
case Manifest.permission.ACCESS_MEDIA_LOCATION:
return PERMISSION_GROUP_ACCESS_MEDIA_LOCATION;
case Manifest.permission.ACTIVITY_RECOGNITION:
return PERMISSION_GROUP_ACTIVITY_RECOGNITION;
default:
return PERMISSION_GROUP_UNKNOWN;
}
......@@ -313,13 +315,7 @@ public class PermissionHandlerPlugin implements MethodCallHandler {
}
}
}
if (permission == PERMISSION_GROUP_LOCATION || permission == PERMISSION_GROUP_LOCATION_ALWAYS || permission == PERMISSION_GROUP_LOCATION_WHEN_IN_USE) {
if (!isLocationServiceEnabled(context)) {
return PERMISSION_STATUS_DISABLED;
}
}
return PERMISSION_STATUS_GRANTED;
}
......@@ -471,13 +467,13 @@ public class PermissionHandlerPlugin implements MethodCallHandler {
mRequestResults.put(PERMISSION_GROUP_SPEECH, toPermissionStatus(permission, result));
}
} else if (permission == PERMISSION_GROUP_LOCATION_ALWAYS) {
@PermissionStatus int permissionStatus = determineActualLocationStatus(permission, result);
@PermissionStatus int permissionStatus = toPermissionStatus(permission, result);
if (!mRequestResults.containsKey(PERMISSION_GROUP_LOCATION_ALWAYS)) {
mRequestResults.put(PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus);
}
} else if (permission == PERMISSION_GROUP_LOCATION) {
@PermissionStatus int permissionStatus = determineActualLocationStatus(permission, result);
@PermissionStatus int permissionStatus = toPermissionStatus(permission, result);
if (VERSION.SDK_INT < VERSION_CODES.Q) {
if (!mRequestResults.containsKey(PERMISSION_GROUP_LOCATION_ALWAYS)) {
......@@ -500,23 +496,6 @@ public class PermissionHandlerPlugin implements MethodCallHandler {
processResult();
}
/**
* Crosschecks a permission grant result with the location service availability.
*
* @param grantResult Grant Result as received from the Android system.
*/
@PermissionStatus
private int determineActualLocationStatus(@PermissionGroup int permission, int grantResult) {
final Context context =
mRegistrar.activity() == null ? mRegistrar.activeContext() : mRegistrar.activity();
final boolean isLocationServiceEnabled = context != null && isLocationServiceEnabled(context);
@PermissionStatus int permissionStatus = toPermissionStatus(permission, grantResult);
if (permissionStatus == PERMISSION_STATUS_GRANTED && !isLocationServiceEnabled) {
permissionStatus = PERMISSION_STATUS_DISABLED;
}
return permissionStatus;
}
private void handleIgnoreBatteryOptimizationsRequest(boolean granted) {
if (mResult == null) {
return;
......@@ -690,6 +669,11 @@ public class PermissionHandlerPlugin implements MethodCallHandler {
permissionNames.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
break;
case PERMISSION_GROUP_ACTIVITY_RECOGNITION:
if (VERSION.SDK_INT >= VERSION_CODES.Q && hasPermissionInManifest(Manifest.permission.ACTIVITY_RECOGNITION))
permissionNames.add(Manifest.permission.ACTIVITY_RECOGNITION);
break;
case PERMISSION_GROUP_NOTIFICATION:
case PERMISSION_GROUP_MEDIA_LIBRARY:
case PERMISSION_GROUP_PHOTOS:
......
#
# NOTE: This podspec is NOT to be published. It is only used as a local source!
#
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'High-performance, high-fidelity mobile apps.'
s.description = <<-DESC
Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
DESC
s.homepage = 'https://flutter.io'
s.license = { :type => 'MIT' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.vendored_frameworks = 'Flutter.framework'
end
......@@ -241,7 +241,7 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
"${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
......
......@@ -5,6 +5,8 @@ import 'package:permission_handler/permission_handler.dart';
void main() => runApp(MyApp());
/// Example Flutter Application demonstrating the functionality of the
/// Permission Handler plugin.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
......@@ -32,7 +34,7 @@ class MyApp extends StatelessWidget {
permission != PermissionGroup.storage &&
permission !=
PermissionGroup.ignoreBatteryOptimizations &&
permission != PermissionGroup.access_media_location;
permission != PermissionGroup.accessMediaLocation;
} else {
return permission != PermissionGroup.unknown &&
permission != PermissionGroup.mediaLibrary &&
......@@ -49,7 +51,10 @@ 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);
final PermissionGroup _permissionGroup;
......
......@@ -105,7 +105,6 @@ typedef NS_ENUM(int, PermissionGroup) {
typedef NS_ENUM(int, PermissionStatus) {
PermissionStatusDenied = 0,
PermissionStatusDisabled,
PermissionStatusGranted,
PermissionStatusRestricted,
PermissionStatusUnknown,
......
......@@ -96,11 +96,6 @@
PermissionStatus status = [LocationPermissionStrategy
determinePermissionStatus:permission authorizationStatus:authorizationStatus];
if ((status == PermissionStatusGranted || status == PermissionStatusDenied)
&& ![CLLocationManager locationServicesEnabled]) {
return PermissionStatusDisabled;
}
return status;
}
......
......@@ -69,12 +69,7 @@
permissionStatus = PermissionStatusGranted;
}
if ((permissionStatus == PermissionStatusGranted || permissionStatus == PermissionStatusDenied) && ![CMMotionActivityManager isActivityAvailable]) {
return PermissionStatusDisabled;
} else {
return permissionStatus;
}
return permissionStatus;
}
return PermissionStatusUnknown;
......
......@@ -3,7 +3,7 @@
#
Pod::Spec.new do |s|
s.name = 'permission_handler'
s.version = '4.2.0+hotfix.3'
s.version = '4.3.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.
......
......@@ -2,31 +2,30 @@
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);
/// The feature is disabled (or not available) on the device.
static const PermissionStatus disabled = PermissionStatus._(1);
/// Permission to access the requested feature is granted by the user.
static const PermissionStatus granted = PermissionStatus._(2);
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._(3);
/// 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._(4);
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._(5);
/// 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,
disabled,
granted,
restricted,
unknown,
......@@ -35,7 +34,6 @@ class PermissionStatus {
static const List<String> _names = <String>[
'denied',
'disabled',
'granted',
'restricted',
'unknown',
......@@ -50,6 +48,7 @@ class PermissionStatus {
class ServiceStatus {
const ServiceStatus._(this.value);
/// Integer representation of the [ServiceStatus].
final int value;
/// The service for the supplied permission group is disabled.
......@@ -61,9 +60,11 @@ class ServiceStatus {
/// 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.
/// 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,
......@@ -82,10 +83,12 @@ class ServiceStatus {
String toString() => 'ServiceStatus.${_names[value]}';
}
/// Defines the permission groups for which permissions can be checked or requested.
/// 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
......@@ -147,7 +150,8 @@ class PermissionGroup {
static const PermissionGroup speech = PermissionGroup._(13);
/// Android: External Storage
/// iOS: Access to folders like `Documents` or `Downloads`. Implicitly granted.
/// iOS: Access to folders like `Documents` or `Downloads`. Implicitly
/// granted.
static const PermissionGroup storage = PermissionGroup._(14);
/// Android: Ignore Battery Optimizations
......@@ -158,12 +162,19 @@ class PermissionGroup {
/// 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 access_media_location = PermissionGroup._(17);
/// 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._(18);
static const PermissionGroup unknown = PermissionGroup._(19);
/// Returns a list of all possible [PermissionGroup] values.
static const List<PermissionGroup> values = <PermissionGroup>[
calendar,
camera,
......@@ -182,7 +193,8 @@ class PermissionGroup {
storage,
ignoreBatteryOptimizations,
notification,
access_media_location,
accessMediaLocation,
activityRecognition,
unknown,
];
......@@ -205,6 +217,7 @@ class PermissionGroup {
'ignoreBatteryOptimizations',
'notification',
'access_media_location',
'activity_recognition',
'unknown',
];
......
......@@ -4,14 +4,21 @@ import 'dart:io';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'package:permission_handler/src/permission_enums.dart';
import 'package:permission_handler/src/utils/codec.dart';
import 'permission_enums.dart';
import 'utils/codec.dart';
/// Provides a cross-platform (iOS, Android) API to request and check permissions.
/// Provides a cross-platform (iOS, Android) API to request and check
/// permissions.
class PermissionHandler {
/// Constructs a singleton instance of [Geolocator].
///
/// When a second instance is created, the first instance will not be able to
/// listen to the EventChannel because it is overridden. Forcing the class to
/// be a singleton class can prevent misuse of creating a second instance
/// from a programmer.
factory PermissionHandler() {
if (_instance == null) {
const MethodChannel methodChannel =
const methodChannel =
MethodChannel('flutter.baseflow.com/permissions/methods');
_instance = PermissionHandler.private(methodChannel);
......@@ -19,6 +26,8 @@ class PermissionHandler {
return _instance;
}
/// This constructor is only used for testing and shouldn't be accessed by
/// users of the plugin.
@visibleForTesting
PermissionHandler.private(this._methodChannel);
......@@ -28,10 +37,11 @@ class PermissionHandler {
/// Check current permission status.
///
/// Returns a [Future] containing the current permission status for the supplied [PermissionGroup].
/// Returns a [Future] containing the current permission status for the
/// supplied [PermissionGroup].
Future<PermissionStatus> checkPermissionStatus(
PermissionGroup permission) async {
final int status = await _methodChannel.invokeMethod(
final status = await _methodChannel.invokeMethod(
'checkPermissionStatus', permission.value);
return Codec.decodePermissionStatus(status);
......@@ -39,7 +49,8 @@ class PermissionHandler {
/// Check current service status.
///
/// Returns a [Future] containing the current service status for the supplied [PermissionGroup].
/// Returns a [Future] containing the current service status for the supplied
/// [PermissionGroup].
///
/// Notes about specific PermissionGroups:
/// - **PermissionGroup.phone**
......@@ -60,7 +71,7 @@ class PermissionHandler {
/// devices' capability to place & connect phone calls
/// as it also depends on the network condition.
Future<ServiceStatus> checkServiceStatus(PermissionGroup permission) async {
final int status = await _methodChannel.invokeMethod(
final status = await _methodChannel.invokeMethod(
'checkServiceStatus', permission.value);
return Codec.decodeServiceStatus(status);
......@@ -68,9 +79,10 @@ class PermissionHandler {
/// Open 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] is returned.
Future<bool> openAppSettings() async {
final bool hasOpened = await _methodChannel.invokeMethod('openAppSettings');
final hasOpened = await _methodChannel.invokeMethod('openAppSettings');
return hasOpened;
}
......@@ -80,8 +92,8 @@ class PermissionHandler {
/// Returns a [Map] containing the status per requested permissiongroup.
Future<Map<PermissionGroup, PermissionStatus>> requestPermissions(
List<PermissionGroup> permissions) async {
final List<int> data = Codec.encodePermissionGroups(permissions);
final Map<dynamic, dynamic> status =
final data = Codec.encodePermissionGroups(permissions);
final status =
await _methodChannel.invokeMethod('requestPermissions', data);
return Codec.decodePermissionRequestResult(Map<int, int>.from(status));
......@@ -97,7 +109,7 @@ class PermissionHandler {
return false;
}
final bool shouldShowRationale = await _methodChannel.invokeMethod(
final shouldShowRationale = await _methodChannel.invokeMethod(
'shouldShowRequestPermissionRationale', permission.value);
return shouldShowRationale;
......
import 'package:permission_handler/src/permission_enums.dart';
import '../permission_enums.dart';
/// Provides utility methods for encoding messages that are send on the Flutter
/// message channel.
class Codec {
/// Converts the supplied integer value into a [PermissionStatus] instance.
static PermissionStatus decodePermissionStatus(int value) {
return PermissionStatus.values[value];
}
/// Converts the supplied integer value into a [ServiceStatus] instance.
static ServiceStatus decodeServiceStatus(int value) {
return ServiceStatus.values[value];
}
/// Converts the supplied [Map] of integers into a [Map] of
/// [PermissionGroup] key and [PermissionStatus] value instances.
static Map<PermissionGroup, PermissionStatus> decodePermissionRequestResult(
Map<int, int> value) {
return value.map((int key, int value) =>
return value.map((key, value) =>
MapEntry<PermissionGroup, PermissionStatus>(
PermissionGroup.values[key], PermissionStatus.values[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) {
return permissions.map((PermissionGroup it) => it.value).toList();
return permissions.map((it) => it.value).toList();
}
}
name: permission_handler
description: Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
version: 4.2.0+hotfix.3
version: 4.3.0
homepage: https://github.com/baseflowit/flutter-permission-handler
environment:
......@@ -13,7 +13,7 @@ dependencies:
meta: ^1.1.6
dev_dependencies:
pedantic: ^1.7.0
effective_dart: ^1.2.1
flutter:
plugin:
......
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