Skip to content

Commit 627daa9

Browse files
authored
Implement Permission.calendarReadOnly and Permission.calendarFullAccess (#1189)
* Clean up `determinePermissionStatus` * Implement new calendar permissions
1 parent c2f3055 commit 627daa9

File tree

6 files changed

+116
-33
lines changed

6 files changed

+116
-33
lines changed

permission_handler_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 11.1.0
2+
3+
* Implements the `Permission.calendarReadOnly` and `PermissionCalendarFullAccess` permissions.
4+
15
## 11.0.5
26

37
* Removes the obsolete `updatePermissionShouldShowStatus` method from the Java code base.

permission_handler_android/android/src/main/java/com/baseflow/permissionhandler/PermissionConstants.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ final class PermissionConstants {
1717
static final int PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY = 213;
1818
static final int PERMISSION_CODE_SCHEDULE_EXACT_ALARM = 214;
1919

20-
//PERMISSION_GROUP
20+
21+
// PERMISSION_GROUP
22+
23+
// Deprecated in favor of PERMISSION_GROUP_CALENDAR_READ_ONLY and
24+
// PERMISSION_GROUP_CALENDAR_FULL_ACCESS.
2125
static final int PERMISSION_GROUP_CALENDAR = 0;
2226
static final int PERMISSION_GROUP_CAMERA = 1;
2327
static final int PERMISSION_GROUP_CONTACTS = 2;
@@ -54,6 +58,8 @@ final class PermissionConstants {
5458
static final int PERMISSION_GROUP_AUDIO = 33;
5559
static final int PERMISSION_GROUP_SCHEDULE_EXACT_ALARM = 34;
5660
static final int PERMISSION_GROUP_SENSORS_ALWAYS = 35;
61+
static final int PERMISSION_GROUP_CALENDAR_READ_ONLY = 36;
62+
static final int PERMISSION_GROUP_CALENDAR_FULL_ACCESS = 37;
5763

5864
@Retention(RetentionPolicy.SOURCE)
5965
@IntDef({
@@ -89,7 +95,9 @@ final class PermissionConstants {
8995
PERMISSION_GROUP_NEARBY_WIFI_DEVICES,
9096
PERMISSION_GROUP_VIDEOS,
9197
PERMISSION_GROUP_AUDIO,
92-
PERMISSION_GROUP_SCHEDULE_EXACT_ALARM
98+
PERMISSION_GROUP_SCHEDULE_EXACT_ALARM,
99+
PERMISSION_GROUP_CALENDAR_READ_ONLY,
100+
PERMISSION_GROUP_CALENDAR_FULL_ACCESS
93101
})
94102
@interface PermissionGroup {
95103
}

permission_handler_android/android/src/main/java/com/baseflow/permissionhandler/PermissionManager.java

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import androidx.core.content.ContextCompat;
2525

2626
import java.util.ArrayList;
27+
import java.util.Arrays;
2728
import java.util.HashMap;
2829
import java.util.HashSet;
2930
import java.util.List;
@@ -165,9 +166,38 @@ public boolean onRequestPermissionsResult(
165166
return false;
166167
}
167168

169+
// Calendar permissions are split between READ and WRITE in Android, and split between READ
170+
// and FULL ACCESS in the plugin. We need special logic for this translation.
171+
final List<String> permissionList = Arrays.asList(permissions);
172+
final int calendarReadIndex = permissionList.indexOf(Manifest.permission.READ_CALENDAR);
173+
final int calendarWriteIndex = permissionList.indexOf(Manifest.permission.WRITE_CALENDAR);
174+
// READ -> READ.
175+
if (calendarReadIndex >= 0) {
176+
final int readGrantResult = grantResults[calendarReadIndex];
177+
final @PermissionConstants.PermissionStatus int readStatus =
178+
PermissionUtils.toPermissionStatus(this.activity, Manifest.permission.READ_CALENDAR, readGrantResult);
179+
requestResults.put(PermissionConstants.PERMISSION_GROUP_CALENDAR_READ_ONLY, readStatus);
180+
181+
// READ + WRITE -> FULL ACCESS.
182+
if (calendarWriteIndex >= 0) {
183+
final int writeGrantResult = grantResults[calendarWriteIndex];
184+
final @PermissionConstants.PermissionStatus int writeStatus =
185+
PermissionUtils.toPermissionStatus(this.activity, Manifest.permission.WRITE_CALENDAR, writeGrantResult);
186+
final @PermissionConstants.PermissionStatus int fullAccessStatus = strictestStatus(readStatus, writeStatus);
187+
requestResults.put(PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS, fullAccessStatus);
188+
// Support deprecated CALENDAR permission.
189+
requestResults.put(PermissionConstants.PERMISSION_GROUP_CALENDAR, fullAccessStatus);
190+
}
191+
}
192+
168193
for (int i = 0; i < permissions.length; i++) {
169194
final String permissionName = permissions[i];
170195

196+
// READ_CALENDAR and WRITE_CALENDAR permission results have already been handled.
197+
if (permissionName.equals(Manifest.permission.READ_CALENDAR) || permissionName.equals(Manifest.permission.WRITE_CALENDAR)) {
198+
continue;
199+
}
200+
171201
@PermissionConstants.PermissionGroup final int permission =
172202
PermissionUtils.parseManifestName(permissionName);
173203

@@ -378,6 +408,17 @@ void requestPermissions(
378408
launchSpecialPermission(
379409
Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM,
380410
PermissionConstants.PERMISSION_CODE_SCHEDULE_EXACT_ALARM);
411+
} else if (permission == PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS || permission == PermissionConstants.PERMISSION_GROUP_CALENDAR) {
412+
// Deny CALENDAR_FULL_ACCESS permission if manifest is not listing both read- and write permissions.
413+
// Otherwise, we will only ask for READ permission and think full access is granted.
414+
final boolean isValidManifest = isValidManifestForCalendarFullAccess();
415+
if (isValidManifest) {
416+
permissionsToRequest.add(Manifest.permission.READ_CALENDAR);
417+
permissionsToRequest.add(Manifest.permission.WRITE_CALENDAR);
418+
pendingRequestCount += 2;
419+
} else {
420+
requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_DENIED);
421+
}
381422
} else {
382423
permissionsToRequest.addAll(names);
383424
pendingRequestCount += names.size();
@@ -418,6 +459,13 @@ private int determinePermissionStatus(final @PermissionConstants.PermissionGroup
418459
}
419460
}
420461

462+
// Inspect the manifest for CALENDAR_FULL_ACCESS, as a misconfigured manifest will give a false positive if READ access has been provided.
463+
if (permission == PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS || permission == PermissionConstants.PERMISSION_GROUP_CALENDAR) {
464+
boolean isValidManifest = isValidManifestForCalendarFullAccess();
465+
if (!isValidManifest)
466+
return PermissionConstants.PERMISSION_STATUS_DENIED;
467+
}
468+
421469
final List<String> names = PermissionUtils.getManifestNames(context, permission);
422470

423471
if (names == null) {
@@ -451,24 +499,19 @@ private int determinePermissionStatus(final @PermissionConstants.PermissionGroup
451499
: PermissionConstants.PERMISSION_STATUS_DENIED;
452500
}
453501

454-
final boolean targetsMOrHigher = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
502+
final boolean requiresExplicitPermission = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
503+
504+
if (requiresExplicitPermission) {
505+
final Set<@PermissionConstants.PermissionStatus Integer> permissionStatuses = new HashSet<>();
455506

456-
Set<@PermissionConstants.PermissionStatus Integer> permissionStatuses = new HashSet<>();
457-
for (String name : names) {
458-
// Only handle them if the client app actually targets a API level greater than M.
459-
if (targetsMOrHigher) {
507+
for (String name : names) {
460508
if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
461509
String packageName = context.getPackageName();
462510
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
463-
// PowerManager.isIgnoringBatteryOptimizations has been included in Android M first.
464-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
465-
if (pm != null && pm.isIgnoringBatteryOptimizations(packageName)) {
466-
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_GRANTED);
467-
} else {
468-
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_DENIED);
469-
}
511+
if (pm != null && pm.isIgnoringBatteryOptimizations(packageName)) {
512+
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_GRANTED);
470513
} else {
471-
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_RESTRICTED);
514+
permissionStatuses.add(PermissionConstants.PERMISSION_STATUS_DENIED);
472515
}
473516
} else if (permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) {
474517
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
@@ -480,12 +523,10 @@ private int determinePermissionStatus(final @PermissionConstants.PermissionGroup
480523
: PermissionConstants.PERMISSION_STATUS_DENIED;
481524
permissionStatuses.add(status);
482525
} else if (permission == PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW) {
483-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
484-
int status = Settings.canDrawOverlays(context)
485-
? PermissionConstants.PERMISSION_STATUS_GRANTED
486-
: PermissionConstants.PERMISSION_STATUS_DENIED;
487-
permissionStatuses.add(status);
488-
}
526+
int status = Settings.canDrawOverlays(context)
527+
? PermissionConstants.PERMISSION_STATUS_GRANTED
528+
: PermissionConstants.PERMISSION_STATUS_DENIED;
529+
permissionStatuses.add(status);
489530
} else if (permission == PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES) {
490531
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
491532
int status = context.getPackageManager().canRequestPackageInstalls()
@@ -494,13 +535,11 @@ private int determinePermissionStatus(final @PermissionConstants.PermissionGroup
494535
permissionStatuses.add(status);
495536
}
496537
} else if (permission == PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY) {
497-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
498-
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Application.NOTIFICATION_SERVICE);
499-
int status = notificationManager.isNotificationPolicyAccessGranted()
500-
? PermissionConstants.PERMISSION_STATUS_GRANTED
501-
: PermissionConstants.PERMISSION_STATUS_DENIED;
502-
permissionStatuses.add(status);
503-
}
538+
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Application.NOTIFICATION_SERVICE);
539+
int status = notificationManager.isNotificationPolicyAccessGranted()
540+
? PermissionConstants.PERMISSION_STATUS_GRANTED
541+
: PermissionConstants.PERMISSION_STATUS_DENIED;
542+
permissionStatuses.add(status);
504543
} else if (permission == PermissionConstants.PERMISSION_GROUP_SCHEDULE_EXACT_ALARM) {
505544
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
506545
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -518,11 +557,11 @@ private int determinePermissionStatus(final @PermissionConstants.PermissionGroup
518557
}
519558
}
520559
}
560+
if (!permissionStatuses.isEmpty()) {
561+
return strictestStatus(permissionStatuses);
562+
}
521563
}
522564

523-
if (!permissionStatuses.isEmpty()) {
524-
return strictestStatus(permissionStatuses);
525-
}
526565
return PermissionConstants.PERMISSION_STATUS_GRANTED;
527566
}
528567

@@ -609,4 +648,23 @@ private int checkBluetoothPermissionStatus() {
609648
}
610649
return PermissionConstants.PERMISSION_STATUS_GRANTED;
611650
}
651+
652+
/**
653+
* Checks if the manifest contains both {@link Manifest.permission#READ_CALENDAR} and
654+
* {@link Manifest.permission#WRITE_CALENDAR} permission declarations.
655+
*/
656+
private boolean isValidManifestForCalendarFullAccess() {
657+
List<String> names = PermissionUtils.getManifestNames(context, PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS);
658+
final boolean readInManifest = names != null && names.contains(Manifest.permission.READ_CALENDAR);
659+
final boolean writeInManifest = names != null && names.contains(Manifest.permission.WRITE_CALENDAR);
660+
final boolean validManifest = readInManifest && writeInManifest;
661+
if (!validManifest) {
662+
if (!readInManifest)
663+
Log.d(PermissionConstants.LOG_TAG, Manifest.permission.READ_CALENDAR + " missing in manifest");
664+
if (!writeInManifest)
665+
Log.d(PermissionConstants.LOG_TAG, Manifest.permission.WRITE_CALENDAR + " missing in manifest");
666+
return false;
667+
}
668+
return true;
669+
}
612670
}

permission_handler_android/android/src/main/java/com/baseflow/permissionhandler/PermissionUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ static List<String> getManifestNames(Context context, @PermissionConstants.Permi
103103
final ArrayList<String> permissionNames = new ArrayList<>();
104104

105105
switch (permission) {
106+
case PermissionConstants.PERMISSION_GROUP_CALENDAR_READ_ONLY:
107+
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CALENDAR))
108+
permissionNames.add(Manifest.permission.READ_CALENDAR);
109+
break;
110+
111+
case PermissionConstants.PERMISSION_GROUP_CALENDAR_FULL_ACCESS:
106112
case PermissionConstants.PERMISSION_GROUP_CALENDAR:
107113
if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CALENDAR))
108114
permissionNames.add(Manifest.permission.READ_CALENDAR);

permission_handler_android/example/android/app/src/main/AndroidManifest.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
xmlns:tools="http://schemas.android.com/tools"
33
package="com.baseflow.permissionhandler.example">
44

5+
<uses-feature
6+
android:name="android.hardware.telephony"
7+
android:required="false" />
8+
<uses-feature
9+
android:name="android.hardware.camera"
10+
android:required="false" />
11+
512
<!--
613
Internet permissions do not affect the `permission_handler` plugin, but are required if your app needs access to
714
the internet.

permission_handler_android/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: permission_handler_android
22
description: Permission plugin for Flutter. This plugin provides the Android API to request and check permissions.
33
homepage: https://github.com/baseflow/flutter-permission-handler
4-
version: 11.0.5
4+
version: 11.1.0
55

66
environment:
77
sdk: ">=2.15.0 <4.0.0"
@@ -18,7 +18,7 @@ flutter:
1818
dependencies:
1919
flutter:
2020
sdk: flutter
21-
permission_handler_platform_interface: ^3.11.2
21+
permission_handler_platform_interface: ^3.12.0
2222

2323
dev_dependencies:
2424
flutter_lints: ^1.0.4

0 commit comments

Comments
 (0)