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