Merge "Intercept activity start to check for notification permissions"
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5d42429..9185340 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3023,6 +3023,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
+ field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 819cbb0..3a41f3d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4221,6 +4221,17 @@
"android.content.pm.action.REQUEST_PERMISSIONS";
/**
+ * The action used to request that the user approve a permission request
+ * from the application. Sent from an application other than the one whose permissions
+ * will be granted. Can only be used by the system server.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER =
+ "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+
+ /**
* The names of the requested permissions.
* <p>
* <strong>Type:</strong> String[]
@@ -4328,8 +4339,9 @@
public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 1 << 5;
/**
- * Permission flag: The permission has to be reviewed before any of
- * the app components can run.
+ * Permission flag: If app targetSDK < M, then the permission has to be reviewed before any of
+ * the app components can run. If app targetSDK >= M, then the system might need to show a
+ * request dialog for this permission on behalf of an app.
*
* @hide
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a97d460..0c8afa3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9949,6 +9949,7 @@
*
* @hide
*/
+ @Readable
public static final String NOTIFICATION_PERMISSION_ENABLED =
"notification_permission_enabled";
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 0528b95..5455738 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -36,4 +36,7 @@
void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
void onConversationRemoved(String pkg, int uid, Set<String> shortcuts);
+
+ /** Get the number of notification channels for a given package */
+ int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 48fdc09..2554796 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -133,6 +133,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
+import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -296,6 +297,7 @@
import com.android.server.notification.toast.ToastRecord;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
@@ -480,6 +482,7 @@
private IPackageManager mPackageManager;
private PackageManager mPackageManagerClient;
private PackageManagerInternal mPackageManagerInternal;
+ private PermissionPolicyInternal mPermissionPolicyInternal;
AudioManager mAudioManager;
AudioManagerInternal mAudioManagerInternal;
// Can be null for wear
@@ -2106,6 +2109,7 @@
mPackageManager = packageManager;
mPackageManagerClient = packageManagerClient;
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
mAppOps = appOps;
mAppOpsService = iAppOps;
try {
@@ -3674,9 +3678,19 @@
private void createNotificationChannelsImpl(String pkg, int uid,
ParceledListSlice channelsList) {
+ createNotificationChannelsImpl(pkg, uid, channelsList,
+ ActivityTaskManager.INVALID_TASK_ID);
+ }
+
+ private void createNotificationChannelsImpl(String pkg, int uid,
+ ParceledListSlice channelsList, int startingTaskId) {
List<NotificationChannel> channels = channelsList.getList();
final int channelsSize = channels.size();
+ ParceledListSlice<NotificationChannel> oldChannels =
+ mPreferencesHelper.getNotificationChannels(pkg, uid, true);
+ final boolean hadChannel = oldChannels != null && !oldChannels.getList().isEmpty();
boolean needsPolicyFileChange = false;
+ boolean hasRequestedNotificationPermission = false;
for (int i = 0; i < channelsSize; i++) {
final NotificationChannel channel = channels.get(i);
Objects.requireNonNull(channel, "channel in list is null");
@@ -3690,6 +3704,19 @@
mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(),
false),
NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+ boolean hasChannel = hadChannel || hasRequestedNotificationPermission;
+ if (!hasChannel) {
+ ParceledListSlice<NotificationChannel> currChannels =
+ mPreferencesHelper.getNotificationChannels(pkg, uid, true);
+ hasChannel = currChannels != null && !currChannels.getList().isEmpty();
+ }
+ if (!hadChannel && hasChannel && !hasRequestedNotificationPermission
+ && startingTaskId != ActivityTaskManager.INVALID_TASK_ID) {
+ hasRequestedNotificationPermission = true;
+ mHandler.post(new ShowNotificationPermissionPromptRunnable(pkg,
+ UserHandle.getUserId(uid), startingTaskId,
+ mPermissionPolicyInternal));
+ }
}
}
if (needsPolicyFileChange) {
@@ -3698,10 +3725,29 @@
}
@Override
- public void createNotificationChannels(String pkg,
- ParceledListSlice channelsList) {
+ public void createNotificationChannels(String pkg, ParceledListSlice channelsList) {
checkCallerIsSystemOrSameApp(pkg);
- createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
+ int taskId = ActivityTaskManager.INVALID_TASK_ID;
+ try {
+ int uid = mPackageManager.getPackageUid(pkg, 0,
+ UserHandle.getUserId(Binder.getCallingUid()));
+ List<ActivityManager.AppTask> tasks = mAtm.getAppTasks(pkg, uid);
+ for (int i = 0; i < tasks.size(); i++) {
+ ActivityManager.RecentTaskInfo task = tasks.get(i).getTaskInfo();
+ if (mPermissionPolicyInternal == null) {
+ mPermissionPolicyInternal =
+ LocalServices.getService(PermissionPolicyInternal.class);
+ }
+ if (mPermissionPolicyInternal != null
+ && mPermissionPolicyInternal.canShowPermissionPromptForTask(task)) {
+ taskId = task.taskId;
+ break;
+ }
+ }
+ } catch (RemoteException e) {
+ // Do nothing
+ }
+ createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId);
}
@Override
@@ -3885,8 +3931,8 @@
public int getNumNotificationChannelsForPackage(String pkg, int uid,
boolean includeDeleted) {
enforceSystemOrSystemUI("getNumNotificationChannelsForPackage");
- return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted)
- .getList().size();
+ return NotificationManagerService.this
+ .getNumNotificationChannelsForPackage(pkg, uid, includeDeleted);
}
@Override
@@ -6151,8 +6197,20 @@
// initially *and* force remove FLAG_FOREGROUND_SERVICE.
sbn.getNotification().flags = (r.mOriginalFlags & ~FLAG_FOREGROUND_SERVICE);
}
+
+ @Override
+ public int getNumNotificationChannelsForPackage(String pkg, int uid,
+ boolean includeDeleted) {
+ return NotificationManagerService.this
+ .getNumNotificationChannelsForPackage(pkg, uid, includeDeleted);
+ }
};
+ int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
+ return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted).getList()
+ .size();
+ }
+
void cancelNotificationInternal(String pkg, String opPkg, int callingUid, int callingPid,
String tag, int id, int userId) {
userId = ActivityManager.handleIncomingUser(callingPid,
@@ -6974,6 +7032,44 @@
}
}
+ protected static class ShowNotificationPermissionPromptRunnable implements Runnable {
+ private final String mPkgName;
+ private final int mUserId;
+ private final int mTaskId;
+ private final PermissionPolicyInternal mPpi;
+
+ ShowNotificationPermissionPromptRunnable(String pkg, int user, int task,
+ PermissionPolicyInternal pPi) {
+ mPkgName = pkg;
+ mUserId = user;
+ mTaskId = task;
+ mPpi = pPi;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ShowNotificationPermissionPromptRunnable)) {
+ return false;
+ }
+
+ ShowNotificationPermissionPromptRunnable other =
+ (ShowNotificationPermissionPromptRunnable) o;
+
+ return Objects.equals(mPkgName, other.mPkgName) && mUserId == other.mUserId
+ && mTaskId == other.mTaskId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPkgName, mUserId, mTaskId);
+ }
+
+ @Override
+ public void run() {
+ mPpi.showNotificationPromptIfNeeded(mPkgName, mUserId, mTaskId);
+ }
+ }
+
protected class EnqueueNotificationRunnable implements Runnable {
private final NotificationRecord r;
private final int userId;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 24008d0..0cbdbc1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.permission.PermissionManager.PERMISSION_GRANTED;
@@ -159,13 +160,21 @@
}
/**
+ * @see setNotificationPermission(String, int, boolean, boolean, boolean)
+ */
+ public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
+ boolean userSet) {
+ setNotificationPermission(packageName, userId, grant, userSet, false);
+ }
+
+ /**
* Grants or revokes the notification permission for a given package/user. UserSet should
* only be true if this method is being called to migrate existing user choice, because it
* can prevent the user from seeing the in app permission dialog. Must not be called
* with a lock held.
*/
public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
- boolean userSet) {
+ boolean userSet, boolean reviewRequired) {
assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
@@ -177,7 +186,12 @@
}
if (userSet) {
mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
- FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, userId);
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_REVIEW_REQUIRED,
+ FLAG_PERMISSION_USER_SET, true, userId);
+ } else if (reviewRequired) {
+ mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
+ FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true,
+ userId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Could not reach system server", e);
@@ -186,15 +200,17 @@
}
}
+ /**
+ * Set the notification permission state upon phone version upgrade from S- to T+, or upon
+ * restoring a pre-T backup on a T+ device
+ */
public void setNotificationPermission(PackagePermission pkgPerm) {
assertFlag();
- final long callingId = Binder.clearCallingIdentity();
- try {
- setNotificationPermission(
- pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet);
- } finally {
- Binder.restoreCallingIdentity(callingId);
+ if (pkgPerm == null || pkgPerm.packageName == null) {
+ return;
}
+ setNotificationPermission(pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted,
+ pkgPerm.userSet, !pkgPerm.userSet);
}
public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index af524db..60d2fc1 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1378,7 +1378,9 @@
pm.grantPermission(permission, pkg, user);
}
- pm.updatePermissionFlags(permission, pkg, newFlags, newFlags, user);
+ // clear the REVIEW_REQUIRED flag, if set
+ int flagMask = newFlags | PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+ pm.updatePermissionFlags(permission, pkg, flagMask, newFlags, user);
}
// If a component gets a permission for being the default handler A
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index c9fd122..1b08d77 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -198,14 +198,19 @@
/** All nearby devices permissions */
private static final List<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>();
- // TODO: This is a placeholder. Replace with actual implementation
- private static final List<String> NOTIFICATION_PERMISSIONS = new ArrayList<>();
-
/**
- * All permissions that should be granted with the REVOKE_WHEN_REQUESTED flag, if they are
- * implicitly added to a package
+ * All notification permissions.
+ * Notification permission state is treated differently from other permissions. Notification
+ * permission get the REVIEW_REQUIRED flag set for S- apps, or for T+ apps on updating to T or
+ * restoring a pre-T backup. The permission and app op remain denied. The flag will be read by
+ * the notification system, and allow apps to send notifications, until cleared.
+ * The flag is cleared for S- apps by the system showing a permission request prompt, and the
+ * user clicking "allow" or "deny" in the dialog. For T+ apps, the flag is cleared upon the
+ * first activity launch.
+ *
+ * @see PermissionPolicyInternal#showNotificationPromptIfNeeded(String, int, int)
*/
- private static final List<String> IMPLICIT_GRANTED_PERMISSIONS = new ArrayList<>();
+ private static final List<String> NOTIFICATION_PERMISSIONS = new ArrayList<>();
/** If the permission of the value is granted, so is the key */
private static final Map<String, String> FULLER_PERMISSION_MAP = new HashMap<>();
@@ -221,7 +226,7 @@
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
- IMPLICIT_GRANTED_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS);
+ NOTIFICATION_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS);
}
/** Set of source package names for Privileged Permission Allowlist */
@@ -2594,9 +2599,12 @@
// Cache newImplicitPermissions before modifing permissionsState as for the
// shared uids the original and new state are the same object
+ // TODO(205888750): remove the line for LEGACY_REVIEW once propagated through
+ // droidfood
if (!origState.hasPermissionState(permName)
&& (pkg.getImplicitPermissions().contains(permName)
- || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) {
+ || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))
+ || NOTIFICATION_PERMISSIONS.contains(permName)) {
if (pkg.getImplicitPermissions().contains(permName)) {
// If permName is an implicit permission, try to auto-grant
newImplicitPermissions.add(permName);
@@ -2754,9 +2762,11 @@
}
// Remove review flag as it is not necessary anymore
- if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
- wasChanged = true;
+ if (!NOTIFICATION_PERMISSIONS.contains(perm)) {
+ if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
+ wasChanged = true;
+ }
}
if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0
@@ -3117,28 +3127,37 @@
inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, newPerm, ps,
pkg);
}
- } else if (IMPLICIT_GRANTED_PERMISSIONS.contains(newPerm)
- && !origPs.hasPermissionState(newPerm)) {
+ } else if (NOTIFICATION_PERMISSIONS.contains(newPerm)) {
+ //&& (origPs.getPermissionState(newPerm) == null) {
+ // TODO(b/205888750): add back line about origPs once propagated through droidfood
Permission bp = mRegistry.getPermission(newPerm);
if (bp == null) {
throw new IllegalStateException("Unknown new permission " + newPerm);
}
- if ((ps.getPermissionState(newPerm).getFlags()
- & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- // No need to grant if review is required
- continue;
+ // TODO(b/205888750): remove the line for REVOKE_WHEN_REQUESTED once propagated
+ // through droidfood
+ if (!isUserSetOrPregrantedOrFixed(ps.getPermissionFlags(newPerm))) {
+ updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ ps.updatePermissionFlags(bp, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED);
+ // TODO(b/205888750): remove revoke once propagated through droidfood
+ if (ps.isPermissionGranted(newPerm)) {
+ ps.revokePermission(bp);
+ }
}
- updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
- ps.updatePermissionFlags(bp,
- FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
- FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
- ps.grantPermission(bp);
}
}
return updatedUserIds;
}
+ private boolean isUserSetOrPregrantedOrFixed(int flags) {
+ return (flags & (FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_POLICY_FIXED | FLAG_PERMISSION_SYSTEM_FIXED
+ | FLAG_PERMISSION_GRANTED_BY_DEFAULT | FLAG_PERMISSION_GRANTED_BY_ROLE)) != 0;
+ }
+
@NonNull
@Override
public List<SplitPermissionInfoParcelable> getSplitPermissions() {
@@ -4323,9 +4342,9 @@
updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false)
);
- mSystemReady = true;
-
synchronized (mLock) {
+ mSystemReady = true;
+
if (mPrivappPermissionsViolations != null) {
throw new IllegalStateException("Signature|privileged permissions not in "
+ "privapp-permissions allowlist: " + mPrivappPermissionsViolations);
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java
index 6084c67..20b7ccd 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.TaskInfo;
import android.content.Intent;
/**
@@ -52,6 +53,25 @@
@Nullable String callingPackage);
/**
+ * Check whether a notification permission prompt should be shown for the given package. A
+ * prompt should be shown if the app targets S-, is currently running in a visible, focused
+ * task, has the REVIEW_REQUIRED flag set on its implicit notification permission, and has
+ * created at least one notification channel (even if it has since been deleted).
+ * @param packageName The package whose permission is being checked
+ * @param userId The user for whom the package is being started
+ * @param taskId The task the notification prompt should be attached to
+ */
+ public abstract void showNotificationPromptIfNeeded(@NonNull String packageName, int userId,
+ int taskId);
+
+ /**
+ * Determine if a particular task is in the proper state to show a system-triggered permission
+ * prompt. A prompt can be shown if the task is focused, visible, and running.
+ * @param taskInfo The task to be checked
+ */
+ public abstract boolean canShowPermissionPromptForTask(@Nullable TaskInfo taskInfo);
+
+ /**
* @return Whether the policy is initialized for a user.
*/
public abstract boolean isInitialized(@UserIdInt int userId);
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index ad43514..10d9bbf 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -16,6 +16,7 @@
package com.android.server.policy;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
@@ -25,16 +26,24 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
+import android.app.TaskInfo;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -43,6 +52,7 @@
import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PermissionInfo;
import android.os.Build;
+import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -53,6 +63,7 @@
import android.telecom.TelecomManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.util.LongSparseLongArray;
import android.util.Pair;
import android.util.Slog;
@@ -67,17 +78,21 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.notification.NotificationManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.wm.ActivityInterceptorCallback;
+import com.android.server.wm.ActivityTaskManagerInternal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ExecutionException;
/**
@@ -89,11 +104,15 @@
*/
public final class PermissionPolicyService extends SystemService {
private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName();
+ private static final String SYSTEM_PKG = "android";
private static final boolean DEBUG = false;
private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 60000;
private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private boolean mBootCompleted = false;
+
private IAppOpsCallback mAppOpsCallback;
/** Whether the user is started but not yet stopped */
@@ -118,24 +137,39 @@
@GuardedBy("mLock")
private final SparseBooleanArray mIsUidSyncScheduled = new SparseBooleanArray();
+ /**
+ * This change reflects the presence of the new Notification Permission
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ private static final long NOTIFICATION_PERM_CHANGE_ID = 194833441L;
+
private List<String> mAppOpPermissions;
+ private Context mContext;
+ private PackageManagerInternal mPackageManagerInternal;
+ private NotificationManagerInternal mNotificationManager;
+ private PermissionManagerServiceInternal mPermissionManagerService;
+ private final PackageManager mPackageManager;
+
public PermissionPolicyService(@NonNull Context context) {
super(context);
+ mContext = context;
+ mPackageManager = context.getPackageManager();
LocalServices.addService(PermissionPolicyInternal.class, new Internal());
}
@Override
public void onStart() {
- final PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ mPackageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
- final PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
+ PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
PermissionManagerServiceInternal.class);
final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
- packageManagerInternal.getPackageList(new PackageListObserver() {
+ mPackageManagerInternal.getPackageList(new PackageListObserver() {
@Override
public void onPackageAdded(String packageName, int uid) {
final int userId = UserHandle.getUserId(uid);
@@ -207,10 +241,10 @@
final PermissionInfo appOpPermissionInfo = appOpPermissionInfos.get(i);
switch (appOpPermissionInfo.name) {
- case android.Manifest.permission.ACCESS_NOTIFICATIONS:
- case android.Manifest.permission.MANAGE_IPSEC_TUNNELS:
+ case Manifest.permission.ACCESS_NOTIFICATIONS:
+ case Manifest.permission.MANAGE_IPSEC_TUNNELS:
continue;
- case android.Manifest.permission.REQUEST_INSTALL_PACKAGES:
+ case Manifest.permission.REQUEST_INSTALL_PACKAGES:
// Settings allows the user to control the app op if it's not in the default
// mode, regardless of whether the app has requested the permission, so we
// should not reset it.
@@ -251,7 +285,7 @@
}
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
// If there is no valid package for the given UID, return immediately
- if (packageManagerInternal.getPackage(uid) == null) {
+ if (mPackageManagerInternal.getPackage(uid) == null) {
return;
}
@@ -343,6 +377,18 @@
}
}
}
+
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ ((Internal) LocalServices.getService(PermissionPolicyInternal.class))
+ .onActivityManagerReady();
+ }
+
+ if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+ synchronized (mLock) {
+ mBootCompleted = true;
+ }
+ }
+
}
/**
@@ -748,10 +794,12 @@
String permissionName = permissionInfo.name;
String packageName = packageInfo.packageName;
+ UserHandle user = UserHandle.getUserHandleForUid(packageInfo.applicationInfo.uid);
int permissionFlags = mPackageManager.getPermissionFlags(permissionName,
packageName, mContext.getUser());
boolean isReviewRequired = (permissionFlags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
- if (isReviewRequired) {
+ if (isReviewRequired && !CompatChanges.isChangeEnabled(
+ NOTIFICATION_PERM_CHANGE_ID, packageName, user)) {
return;
}
@@ -953,6 +1001,33 @@
private class Internal extends PermissionPolicyInternal {
+ private ActivityInterceptorCallback mActivityInterceptorCallback =
+ new ActivityInterceptorCallback() {
+ @Nullable
+ @Override
+ public ActivityInterceptorCallback.ActivityInterceptResult intercept(
+ ActivityInterceptorInfo info) {
+ return null;
+ }
+
+ @Override
+ public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
+ super.onActivityLaunched(taskInfo, activityInfo);
+ clearNotificationReviewFlagsIfNeeded(activityInfo.packageName,
+ UserHandle.of(taskInfo.userId));
+ showNotificationPromptIfNeeded(activityInfo.packageName,
+ taskInfo.userId, taskInfo.taskId);
+ }
+ };
+
+ private void onActivityManagerReady() {
+ ActivityTaskManagerInternal atm =
+ LocalServices.getService(ActivityTaskManagerInternal.class);
+ atm.registerActivityStartInterceptor(
+ ActivityInterceptorCallback.PERMISSION_POLICY_ORDERED_ID,
+ mActivityInterceptorCallback);
+ }
+
@Override
public boolean checkStartActivity(@NonNull Intent intent, int callingUid,
@Nullable String callingPackage) {
@@ -962,9 +1037,53 @@
+ callingPackage + " (uid=" + callingUid + ")");
return false;
}
+
+ if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(intent.getAction())
+ && (callingUid != Process.SYSTEM_UID || !SYSTEM_PKG.equals(callingPackage))) {
+ return false;
+ }
+
return true;
}
+ public void showNotificationPromptIfNeeded(@NonNull String packageName, int userId,
+ int taskId) {
+ UserHandle user = UserHandle.of(userId);
+ if (packageName == null || taskId == ActivityTaskManager.INVALID_TASK_ID
+ || !shouldForceShowNotificationPermissionRequest(packageName, user)) {
+ return;
+ }
+
+ launchNotificationPermissionRequestDialog(packageName, user, taskId);
+ }
+
+ private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle userId) {
+ if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, userId)) {
+ return;
+ }
+ mPackageManager.updatePermissionFlags(POST_NOTIFICATIONS, packageName,
+ FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId);
+ }
+
+ private void launchNotificationPermissionRequestDialog(String pkgName, UserHandle user,
+ int taskId) {
+ Intent grantPermission = mPackageManager
+ .buildRequestPermissionsIntent(new String[] { POST_NOTIFICATIONS });
+ grantPermission.setAction(
+ PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER);
+ grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, pkgName);
+
+ ActivityOptions options = new ActivityOptions(new Bundle());
+ options.setTaskOverlay(true, false);
+ options.setLaunchTaskId(taskId);
+ try {
+ mContext.startActivityAsUser(grantPermission, options.toBundle(), user);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "couldn't start grant permission dialog"
+ + "for other package " + pkgName, e);
+ }
+ }
+
@Override
public boolean isInitialized(int userId) {
return isStarted(userId);
@@ -977,6 +1096,12 @@
}
}
+ @Override
+ public boolean canShowPermissionPromptForTask(@Nullable TaskInfo taskInfo) {
+ return taskInfo != null && taskInfo.isFocused && taskInfo.isVisible
+ && taskInfo.isRunning;
+ }
+
/**
* Check if the intent action is removed for the calling package (often based on target SDK
* version). If the action is removed, we'll silently cancel the activity launch.
@@ -1010,5 +1135,51 @@
return false;
}
}
+
+ private boolean shouldForceShowNotificationPermissionRequest(@NonNull String pkgName,
+ @NonNull UserHandle user) {
+ AndroidPackage pkg = mPackageManagerInternal.getPackage(pkgName);
+ // TODO(b/205888750): Remove platform key and permissionController lines after pregrants
+ // are in place
+ if (pkg == null || pkg.getPackageName() == null || pkg.isSignedWithPlatformKey()
+ || pkg.getPackageName().contains("nexuslauncher")
+ || Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName())
+ || pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
+ // TODO(b/205888750) add warning logs when pregrants in place
+ return false;
+ }
+
+ synchronized (mLock) {
+ if (!mBootCompleted) {
+ return false;
+ }
+ }
+
+ try {
+ if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, UserHandle.USER_SYSTEM)
+ == 0) {
+ return false;
+ }
+ } catch (Settings.SettingNotFoundException e) {
+ return false;
+ }
+
+ if (!pkg.getRequestedPermissions().contains(POST_NOTIFICATIONS)
+ || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
+ pkg.getPackageName(), user)) {
+ return false;
+ }
+
+ int uid = user.getUid(pkg.getUid());
+ if (mNotificationManager == null) {
+ mNotificationManager = LocalServices.getService(NotificationManagerInternal.class);
+ }
+ boolean hasCreatedNotificationChannels = mNotificationManager
+ .getNumNotificationChannelsForPackage(pkg.getPackageName(), uid, true) > 0;
+ boolean needsReview = (mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName,
+ user) & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
+ return hasCreatedNotificationChannels && needsReview;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 30cd3c4..1bb9ca7 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -54,6 +54,7 @@
@IntDef(suffix = { "_ORDERED_ID" }, value = {
FIRST_ORDERED_ID,
COMMUNAL_MODE_ORDERED_ID,
+ PERMISSION_POLICY_ORDERED_ID,
LAST_ORDERED_ID // Update this when adding new ids
})
@Retention(RetentionPolicy.SOURCE)
@@ -70,10 +71,15 @@
public static final int COMMUNAL_MODE_ORDERED_ID = 1;
/**
+ * The identifier for {@link com.android.server.policy.PermissionPolicyService} interceptor
+ */
+ public static final int PERMISSION_POLICY_ORDERED_ID = 2;
+
+ /**
* The final id, used by the framework to determine the valid range of ids. Update this when
* adding new ids.
*/
- static final int LAST_ORDERED_ID = COMMUNAL_MODE_ORDERED_ID;
+ static final int LAST_ORDERED_ID = PERMISSION_POLICY_ORDERED_ID;
/**
* Data class for storing the various arguments needed for activity interception.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index a9142ef..23508d9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -687,4 +687,7 @@
/** Get the most recent task excluding the first running task (the one on the front most). */
public abstract ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground();
+
+ /** Get the app tasks for a package */
+ public abstract List<ActivityManager.AppTask> getAppTasks(String pkgName, int uid);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 15ebe28..1bafd49 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -135,6 +135,7 @@
import android.app.IActivityClientController;
import android.app.IActivityController;
import android.app.IActivityTaskManager;
+import android.app.IAppTask;
import android.app.IApplicationThread;
import android.app.IAssistDataReceiver;
import android.app.INotificationManager;
@@ -2558,12 +2559,15 @@
@Override
public List<IBinder> getAppTasks(String callingPackage) {
- int callingUid = Binder.getCallingUid();
assertPackageMatchesCallingUid(callingPackage);
+ return getAppTasks(callingPackage, Binder.getCallingUid());
+ }
+
+ private List<IBinder> getAppTasks(String pkgName, int uid) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- return mRecentTasks.getAppTasksList(callingUid, callingPackage);
+ return mRecentTasks.getAppTasksList(uid, pkgName);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -6668,5 +6672,16 @@
}
return targetTask;
}
+
+ @Override
+ public List<ActivityManager.AppTask> getAppTasks(String pkgName, int uid) {
+ ArrayList<ActivityManager.AppTask> tasks = new ArrayList<>();
+ List<IBinder> appTasks = ActivityTaskManagerService.this.getAppTasks(pkgName, uid);
+ int numAppTasks = appTasks.size();
+ for (int i = 0; i < numAppTasks; i++) {
+ tasks.add(new ActivityManager.AppTask(IAppTask.Stub.asInterface(appTasks.get(i))));
+ }
+ return tasks;
+ }
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 7fc907a..11777ef 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -205,6 +205,7 @@
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.pm.PackageManagerService;
+import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
@@ -244,6 +245,8 @@
@RunWithLooper
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
+ private static final String PKG_NO_CHANNELS = "com.example.no.channels";
+ private static final int TEST_TASK_ID = 1;
private static final int UID_HEADLESS = 1000000;
private final int mUid = Binder.getCallingUid();
@@ -258,6 +261,8 @@
@Mock
private PackageManagerInternal mPackageManagerInternal;
@Mock
+ private PermissionPolicyInternal mPermissionPolicyInternal;
+ @Mock
private WindowManagerInternal mWindowManagerInternal;
@Mock
private PermissionHelper mPermissionHelper;
@@ -386,6 +391,8 @@
LocalServices.addService(ActivityManagerInternal.class, mAmi);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+ LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
+ LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
@@ -416,8 +423,18 @@
when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
when(mPackageManager.getPackagesForUid(mUid)).thenReturn(new String[]{PKG});
when(mPackageManagerClient.getPackagesForUid(anyInt())).thenReturn(new String[]{PKG});
+ when(mPermissionPolicyInternal.canShowPermissionPromptForTask(
+ any(ActivityManager.RecentTaskInfo.class))).thenReturn(false);
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
+ ActivityManager.AppTask task = mock(ActivityManager.AppTask.class);
+ List<ActivityManager.AppTask> taskList = new ArrayList<>();
+ ActivityManager.RecentTaskInfo taskInfo = new ActivityManager.RecentTaskInfo();
+ taskInfo.taskId = TEST_TASK_ID;
+ when(task.getTaskInfo()).thenReturn(taskInfo);
+ taskList.add(task);
+ when(mAtm.getAppTasks(anyString(), anyInt())).thenReturn(taskList);
+
// write to a test file; the system file isn't readable from tests
mFile = new File(mContext.getCacheDir(), "test.xml");
mFile.createNewFile();
@@ -942,6 +959,51 @@
}
@Test
+ public void testCreateNotificationChannels_FirstChannelWithFgndTaskStartsPermDialog()
+ throws Exception {
+ when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any(
+ ActivityManager.RecentTaskInfo.class))).thenReturn(true);
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mBinderService.createNotificationChannels(PKG_NO_CHANNELS,
+ new ParceledListSlice(Arrays.asList(channel)));
+ verify(mWorkerHandler).post(eq(new NotificationManagerService
+ .ShowNotificationPermissionPromptRunnable(PKG_NO_CHANNELS,
+ UserHandle.getUserId(mUid), TEST_TASK_ID, mPermissionPolicyInternal)));
+ }
+
+ @Test
+ public void testCreateNotificationChannels_SecondChannelWithFgndTaskDoesntStartPermDialog()
+ throws Exception {
+ when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any(
+ ActivityManager.RecentTaskInfo.class))).thenReturn(true);
+ assertTrue(mBinderService.getNumNotificationChannelsForPackage(PKG, mUid, true) > 0);
+
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(channel)));
+ verify(mWorkerHandler, never()).post(any(
+ NotificationManagerService.ShowNotificationPermissionPromptRunnable.class));
+ }
+
+ @Test
+ public void testCreateNotificationChannels_FirstChannelWithBgndTaskDoesntStartPermDialog()
+ throws Exception {
+ reset(mPermissionPolicyInternal);
+ when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any(
+ ActivityManager.RecentTaskInfo.class))).thenReturn(false);
+
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(channel)));
+
+ verify(mWorkerHandler, never()).post(any(
+ NotificationManagerService.ShowNotificationPermissionPromptRunnable.class));
+ }
+
+ @Test
public void testCreateNotificationChannels_TwoChannels() throws Exception {
final NotificationChannel channel1 =
new NotificationChannel("id1", "name", IMPORTANCE_DEFAULT);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index bd3ba04..fa294dd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
@@ -224,7 +225,30 @@
verify(mPermManager).grantRuntimePermission(
"pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
- FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_REVIEW_REQUIRED,
+ FLAG_PERMISSION_USER_SET, true, 10);
+ }
+
+ @Test
+ public void testSetNotificationPermission_grantReviewRequired() throws Exception {
+ mPermissionHelper.setNotificationPermission("pkg", 10, true, false, true);
+
+ verify(mPermManager).grantRuntimePermission(
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
+ FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10);
+ }
+
+ @Test
+ public void testSetNotificationPermission_pkgPerm_grantReviewRequired() throws Exception {
+ PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission(
+ "pkg", 10, true, false);
+ mPermissionHelper.setNotificationPermission(pkgPerm);
+
+ verify(mPermManager).grantRuntimePermission(
+ "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+ verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
+ FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED, true, 10);
}
@Test
@@ -234,7 +258,8 @@
verify(mPermManager).revokeRuntimePermission(
eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
- FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_REVIEW_REQUIRED,
+ FLAG_PERMISSION_USER_SET, true, 10);
}
@Test