Commit 627daa91 by Jeroen Weener Committed by GitHub

Implement `Permission.calendarReadOnly` and `Permission.calendarFullAccess` (#1189)

* Clean up `determinePermissionStatus`

* Implement new calendar permissions
parent c2f30555
## 11.1.0
* Implements the `Permission.calendarReadOnly` and `PermissionCalendarFullAccess` permissions.
## 11.0.5
* Removes the obsolete `updatePermissionShouldShowStatus` method from the Java code base.
......
......@@ -17,7 +17,11 @@ final class PermissionConstants {
static final int PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY = 213;
static final int PERMISSION_CODE_SCHEDULE_EXACT_ALARM = 214;
//PERMISSION_GROUP
// PERMISSION_GROUP
// Deprecated in favor of PERMISSION_GROUP_CALENDAR_READ_ONLY and
// PERMISSION_GROUP_CALENDAR_FULL_ACCESS.
static final int PERMISSION_GROUP_CALENDAR = 0;
static final int PERMISSION_GROUP_CAMERA = 1;
static final int PERMISSION_GROUP_CONTACTS = 2;
......@@ -54,6 +58,8 @@ final class PermissionConstants {
static final int PERMISSION_GROUP_AUDIO = 33;
static final int PERMISSION_GROUP_SCHEDULE_EXACT_ALARM = 34;
static final int PERMISSION_GROUP_SENSORS_ALWAYS = 35;
static final int PERMISSION_GROUP_CALENDAR_READ_ONLY = 36;
static final int PERMISSION_GROUP_CALENDAR_FULL_ACCESS = 37;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
......@@ -89,7 +95,9 @@ final class PermissionConstants {
PERMISSION_GROUP_NEARBY_WIFI_DEVICES,
PERMISSION_GROUP_VIDEOS,
PERMISSION_GROUP_AUDIO,
PERMISSION_GROUP_SCHEDULE_EXACT_ALARM
PERMISSION_GROUP_SCHEDULE_EXACT_ALARM,
PERMISSION_GROUP_CALENDAR_READ_ONLY,
PERMISSION_GROUP_CALENDAR_FULL_ACCESS
})
@interface PermissionGroup {
}
......
......@@ -24,6 +24,7 @@ import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
......@@ -165,9 +166,38 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
return false;
}
// Calendar permissions are split between READ and WRITE in Android, and split between READ
// and FULL ACCESS in the plugin. We need special logic for this translation.
final List<String> permissionList = Arrays.asList(permissions);
final int calendarReadIndex = permissionList.indexOf(Manifest.permission.READ_CALENDAR);
final int calendarWriteIndex = permissionList.indexOf(Manifest.permission.WRITE_CALENDAR);
// READ -> READ.
if (calendarReadIndex >= 0) {
final int readGrantResult = grantResults[calendarReadIndex];
final @PermissionConstants.PermissionStatus int readStatus =
PermissionUtils.toPermissionStatus(this.activity, Manifest.permission.READ_CALENDAR, readGrantResult);
requestResults.put(PermissionConstants.PERMISSION_GROUP_CALENDAR_READ_ONLY, readStatus);
// READ + WRITE -> FULL ACCESS.
if (calendarWriteIndex >= 0) {
final int writeGrantResult = grantResults[calendarWriteIndex];
final @PermissionConstants.PermissionStatus int writeStatus =
PermissionUtils.toPermissionStatus(this.activity, Manifest.permission.WRITE_CALENDAR, writeGrantResult);
final @PermissionConstants.PermissionStatus int fullAccessStatus = strictestStatus(readStatus, writeStatus);
requestResults.put(PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS, fullAccessStatus);
// Support deprecated CALENDAR permission.
requestResults.put(PermissionConstants.PERMISSION_GROUP_CALENDAR, fullAccessStatus);
}
}
for (int i = 0; i < permissions.length; i++) {
final String permissionName = permissions[i];
// READ_CALENDAR and WRITE_CALENDAR permission results have already been handled.
if (permissionName.equals(Manifest.permission.READ_CALENDAR) || permissionName.equals(Manifest.permission.WRITE_CALENDAR)) {
continue;
}
@PermissionConstants.PermissionGroup final int permission =
PermissionUtils.parseManifestName(permissionName);
......@@ -378,6 +408,17 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
launchSpecialPermission(
Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM,
PermissionConstants.PERMISSION_CODE_SCHEDULE_EXACT_ALARM);
} else if (permission == PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS || permission == PermissionConstants.PERMISSION_GROUP_CALENDAR) {
// Deny CALENDAR_FULL_ACCESS permission if manifest is not listing both read- and write permissions.
// Otherwise, we will only ask for READ permission and think full access is granted.
final boolean isValidManifest = isValidManifestForCalendarFullAccess();
if (isValidManifest) {
permissionsToRequest.add(Manifest.permission.READ_CALENDAR);
permissionsToRequest.add(Manifest.permission.WRITE_CALENDAR);
pendingRequestCount += 2;
} else {
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_DENIED);
}
} else {
permissionsToRequest.addAll(names);
pendingRequestCount += names.size();
......@@ -418,6 +459,13 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
}
}
// Inspect the manifest for CALENDAR_FULL_ACCESS, as a misconfigured manifest will give a false positive if READ access has been provided.
if (permission == PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS || permission == PermissionConstants.PERMISSION_GROUP_CALENDAR) {
boolean isValidManifest = isValidManifestForCalendarFullAccess();
if (!isValidManifest)
return PermissionConstants.PERMISSION_STATUS_DENIED;
}
final List<String> names = PermissionUtils.getManifestNames(context, permission);
if (names == null) {
......@@ -451,24 +499,19 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
: PermissionConstants.PERMISSION_STATUS_DENIED;
}
final boolean targetsMOrHigher = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
final boolean requiresExplicitPermission = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
if (requiresExplicitPermission) {
final Set<@PermissionConstants.PermissionStatus Integer> permissionStatuses = new HashSet<>();
Set<@PermissionConstants.PermissionStatus Integer> permissionStatuses = new HashSet<>();
for (String name : names) {
// Only handle them if the client app actually targets a API level greater than M.
if (targetsMOrHigher) {
for (String name : names) {
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)) {
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_GRANTED);
} else {
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_DENIED);
}
if (pm != null && pm.isIgnoringBatteryOptimizations(packageName)) {
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_GRANTED);
} else {
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_RESTRICTED);
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_DENIED);
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
......@@ -480,12 +523,10 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
: PermissionConstants.PERMISSION_STATUS_DENIED;
permissionStatuses.add(status);
} else if (permission == PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int status = Settings.canDrawOverlays(context)
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permissionStatuses.add(status);
}
int status = Settings.canDrawOverlays(context)
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permissionStatuses.add(status);
} else if (permission == PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
int status = context.getPackageManager().canRequestPackageInstalls()
......@@ -494,13 +535,11 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
permissionStatuses.add(status);
}
} else if (permission == PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Application.NOTIFICATION_SERVICE);
int status = notificationManager.isNotificationPolicyAccessGranted()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permissionStatuses.add(status);
}
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Application.NOTIFICATION_SERVICE);
int status = notificationManager.isNotificationPolicyAccessGranted()
? PermissionConstants.PERMISSION_STATUS_GRANTED
: PermissionConstants.PERMISSION_STATUS_DENIED;
permissionStatuses.add(status);
} else if (permission == PermissionConstants.PERMISSION_GROUP_SCHEDULE_EXACT_ALARM) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
......@@ -518,11 +557,11 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
}
}
}
if (!permissionStatuses.isEmpty()) {
return strictestStatus(permissionStatuses);
}
}
if (!permissionStatuses.isEmpty()) {
return strictestStatus(permissionStatuses);
}
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
......@@ -609,4 +648,23 @@ final class PermissionManager implements PluginRegistry.ActivityResultListener,
}
return PermissionConstants.PERMISSION_STATUS_GRANTED;
}
/**
* Checks if the manifest contains both {@link Manifest.permission#READ_CALENDAR} and
* {@link Manifest.permission#WRITE_CALENDAR} permission declarations.
*/
private boolean isValidManifestForCalendarFullAccess() {
List<String> names = PermissionUtils.getManifestNames(context, PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS);
final boolean readInManifest = names != null && names.contains(Manifest.permission.READ_CALENDAR);
final boolean writeInManifest = names != null && names.contains(Manifest.permission.WRITE_CALENDAR);
final boolean validManifest = readInManifest && writeInManifest;
if (!validManifest) {
if (!readInManifest)
Log.d(PermissionConstants.LOG_TAG, Manifest.permission.READ_CALENDAR + " missing in manifest");
if (!writeInManifest)
Log.d(PermissionConstants.LOG_TAG, Manifest.permission.WRITE_CALENDAR + " missing in manifest");
return false;
}
return true;
}
}
......@@ -103,6 +103,12 @@ public class PermissionUtils {
final ArrayList<String> permissionNames = new ArrayList<>();
switch (permission) {
case PermissionConstants.PERMISSION_GROUP_CALENDAR_READ_ONLY:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CALENDAR))
permissionNames.add(Manifest.permission.READ_CALENDAR);
break;
case PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS:
case PermissionConstants.PERMISSION_GROUP_CALENDAR:
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CALENDAR))
permissionNames.add(Manifest.permission.READ_CALENDAR);
......
......@@ -2,6 +2,13 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.baseflow.permissionhandler.example">
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<!--
Internet permissions do not affect the `permission_handler` plugin, but are required if your app needs access to
the internet.
......
name: permission_handler_android
description: Permission plugin for Flutter. This plugin provides the Android API to request and check permissions.
homepage: https://github.com/baseflow/flutter-permission-handler
version: 11.0.5
version: 11.1.0
environment:
sdk: ">=2.15.0 <4.0.0"
......@@ -18,7 +18,7 @@ flutter:
dependencies:
flutter:
sdk: flutter
permission_handler_platform_interface: ^3.11.2
permission_handler_platform_interface: ^3.12.0
dev_dependencies:
flutter_lints: ^1.0.4
......
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