diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c4731aa7b522a51bf09394188239d7d8f6ddc8ba..cb3f17dd89b2e10345ecd04a90f8cc2c84327360 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3746,6 +3746,10 @@ public class NotificationManagerService extends SystemService { if (!hadChannel && hasChannel && !hasRequestedNotificationPermission && startingTaskId != ActivityTaskManager.INVALID_TASK_ID) { hasRequestedNotificationPermission = true; + if (mPermissionPolicyInternal == null) { + mPermissionPolicyInternal = + LocalServices.getService(PermissionPolicyInternal.class); + } mHandler.post(new ShowNotificationPermissionPromptRunnable(pkg, UserHandle.getUserId(uid), startingTaskId, mPermissionPolicyInternal)); @@ -3764,19 +3768,7 @@ public class NotificationManagerService extends SystemService { try { int uid = mPackageManager.getPackageUid(pkg, 0, UserHandle.getUserId(Binder.getCallingUid())); - List 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; - } - } + taskId = mAtm.getTaskToShowPermissionDialogOn(pkg, uid); } catch (RemoteException e) { // Do nothing } diff --git a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java index 20b7ccd39287c919098637dfcb8e45f14d010594..92b9944b74cf817a6d7b5cd43254215484e0f5d6 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java @@ -57,6 +57,7 @@ public abstract class PermissionPolicyInternal { * 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 @@ -66,10 +67,22 @@ public abstract class PermissionPolicyInternal { /** * 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. + * prompt. A prompt can be shown if the task is focused, visible, and running and + * 1. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER), or + * 2. The activity belongs to the same package as the one which launched the task originally, + * and the task was started with a launcher intent + * * @param taskInfo The task to be checked + * @param currPkg The package of the current top visible activity + * @param intent The intent of the current top visible activity + */ + public abstract boolean shouldShowNotificationDialogForTask(@Nullable TaskInfo taskInfo, + @Nullable String currPkg, @Nullable Intent intent); + + /** + * @return true if an intent will resolve to a permission request dialog activity */ - public abstract boolean canShowPermissionPromptForTask(@Nullable TaskInfo taskInfo); + public abstract boolean isIntentToPermissionDialog(@NonNull Intent intent); /** * @return Whether the policy is initialized for a user. diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index f2ce0d4c49d3b53d70a54cf63d9664bce017a175..9328dd1a5f1959fd39e29329a66b9794c9ea727c 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -21,6 +21,8 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_NONE; +import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS; +import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; @@ -1016,7 +1018,7 @@ public final class PermissionPolicyService extends SystemService { ActivityInterceptorInfo info) { String action = info.intent.getAction(); ActivityInterceptResult result = null; - if (!PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action) + if (!ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action) && !PackageManager.ACTION_REQUEST_PERMISSIONS.equals(action)) { return null; } @@ -1033,7 +1035,7 @@ public final class PermissionPolicyService extends SystemService { && !mContinueNotifGrantMessageUids.contains(info.realCallingUid)) { return result; } - if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)) { + if (ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)) { String otherPkg = info.intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); if (otherPkg == null || (mPackageManager.getPermissionFlags( POST_NOTIFICATIONS, otherPkg, UserHandle.of(info.userId)) @@ -1052,8 +1054,8 @@ public final class PermissionPolicyService extends SystemService { public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo, ActivityInterceptorInfo info) { super.onActivityLaunched(taskInfo, activityInfo, info); - if (!shouldShowNotificationDialogOrClearFlags(info.intent, - info.checkedOptions)) { + if (!shouldShowNotificationDialogOrClearFlags(taskInfo, + activityInfo.packageName, info.intent, info.checkedOptions, true)) { return; } UserHandle user = UserHandle.of(taskInfo.userId); @@ -1085,7 +1087,7 @@ public final class PermissionPolicyService extends SystemService { return false; } - if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(intent.getAction()) + if (ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(intent.getAction()) && (callingUid != Process.SYSTEM_UID || !SYSTEM_PKG.equals(callingPackage))) { return false; } @@ -1104,18 +1106,48 @@ public final class PermissionPolicyService extends SystemService { launchNotificationPermissionRequestDialog(packageName, user, taskId); } + @Override + public boolean isIntentToPermissionDialog(@NonNull Intent intent) { + return Objects.equals(intent.getPackage(), + mPackageManager.getPermissionControllerPackageName()) + && (Objects.equals(intent.getAction(), ACTION_REQUEST_PERMISSIONS_FOR_OTHER) + || Objects.equals(intent.getAction(), ACTION_REQUEST_PERMISSIONS)); + } + + @Override + public boolean shouldShowNotificationDialogForTask(TaskInfo taskInfo, String currPkg, + Intent intent) { + return shouldShowNotificationDialogOrClearFlags( + taskInfo, currPkg, intent, null, false); + } + /** - * Determine if we should show a notification dialog, or clear the REVIEW_REQUIRED flag, - * from a particular package for a particular intent. Returns true if: + * 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 just starting, or the task is + * currently focused, visible, and running, and, * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or - * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER) + * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER), or + * 3. The activity belongs to the same package as the one which launched the task + * originally, and the task was started with a launcher intent + * @param taskInfo The task to be checked + * @param currPkg The package of the current top visible activity + * @param intent The intent of the current top visible activity */ - private boolean shouldShowNotificationDialogOrClearFlags(Intent intent, - ActivityOptions options) { - if ((options != null && options.isEligibleForLegacyPermissionPrompt())) { - return true; + private boolean shouldShowNotificationDialogOrClearFlags(TaskInfo taskInfo, String currPkg, + Intent intent, ActivityOptions options, boolean activityStart) { + if (intent == null || currPkg == null || taskInfo == null + || (!(taskInfo.isFocused && taskInfo.isVisible && taskInfo.isRunning) + && !activityStart)) { + return false; } + return isLauncherIntent(intent) + || (options != null && options.isEligibleForLegacyPermissionPrompt()) + || (currPkg.equals(taskInfo.baseActivity.getPackageName()) + && isLauncherIntent(taskInfo.baseIntent)); + } + + private boolean isLauncherIntent(Intent intent) { return Intent.ACTION_MAIN.equals(intent.getAction()) && intent.getCategories() != null && (intent.getCategories().contains(Intent.CATEGORY_LAUNCHER) @@ -1144,7 +1176,7 @@ public final class PermissionPolicyService extends SystemService { Intent grantPermission = mPackageManager .buildRequestPermissionsIntent(new String[] { POST_NOTIFICATIONS }); grantPermission.setAction( - PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER); + ACTION_REQUEST_PERMISSIONS_FOR_OTHER); grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, pkgName); ActivityOptions options = new ActivityOptions(new Bundle()); @@ -1170,12 +1202,6 @@ public final class PermissionPolicyService extends SystemService { } } - @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. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index cecfccd1f836b269944619a7a027fccdf1ef03e1..01dfb91d12be656357e6999923bbc196bd9471e3 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -680,4 +680,15 @@ public abstract class ActivityTaskManagerInternal { /** Get the app tasks for a package */ public abstract List getAppTasks(String pkgName, int uid); + + /** + * Determine if there exists a task which meets the criteria set by the PermissionPolicyService + * to show a system-owned permission dialog over, for a given package + * @see PermissionPolicyInternal.shouldShowNotificationDialogForTask + * + * @param pkgName The package whose activity must be top + * @param uid The uid that must have a top activity + * @return a task ID if a valid task ID is found. Otherwise, return INVALID_TASK_ID + */ + public abstract int getTaskToShowPermissionDialogOn(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 bfccdf97c680379c152dbbb695a4698a7c40395f..6d8b3b1d63dda7d47bb36faaea2383f59b256e63 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6754,5 +6754,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } return tasks; } + + @Override + public int getTaskToShowPermissionDialogOn(String pkgName, int uid) { + synchronized (ActivityTaskManagerService.this.mGlobalLock) { + return ActivityTaskManagerService.this.mRootWindowContainer + .getTaskToShowPermissionDialogOn(pkgName, uid); + } + } } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index b9cd657da0e2786511187c1f666f87434e402e19..56234eeb025288108a155b7eaa0b332d69cac6d4 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -149,6 +149,7 @@ import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; import com.android.server.am.AppTimeTracker; import com.android.server.am.UserState; +import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.WindowManagerPolicy; import java.io.FileDescriptor; @@ -3308,6 +3309,36 @@ class RootWindowContainer extends WindowContainer mService.startLaunchPowerMode(reason); } + /** + * Iterate over all task fragments, to see if there exists one that meets the + * PermissionPolicyService's criteria to show a permission dialog. + */ + public int getTaskToShowPermissionDialogOn(String pkgName, int uid) { + PermissionPolicyInternal pPi = mService.getPermissionPolicyInternal(); + if (pPi == null) { + return INVALID_TASK_ID; + } + + final int[] validTaskId = {INVALID_TASK_ID}; + forAllLeafTaskFragments(fragment -> { + ActivityRecord record = fragment.getActivity((r) -> { + // skip hidden (or about to hide) apps, or the permission dialog + return r.canBeTopRunning() && r.isVisibleRequested() + && !pPi.isIntentToPermissionDialog(r.intent); + }); + if (record != null && record.isUid(uid) + && Objects.equals(pkgName, record.packageName) + && pPi.shouldShowNotificationDialogForTask(record.getTask().getTaskInfo(), + pkgName, record.intent)) { + validTaskId[0] = record.getTask().mTaskId; + return true; + } + return false; + }); + + return validTaskId[0]; + } + /** * Dumps the activities matching the given {@param name} in the either the focused root task * or all visible root tasks if {@param dumpVisibleRootTasksOnly} is true. 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 71f8b8de032b64f123cc4fd2940af5b00a627b32..d752322f567a7cba6acad5be2edd02717fd5afcf 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.Notification.FLAG_AUTO_CANCEL; @@ -432,8 +433,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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); + when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())) + .thenReturn(INVALID_TASK_ID); mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class)); when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0}); @@ -971,8 +972,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCreateNotificationChannels_FirstChannelWithFgndTaskStartsPermDialog() throws Exception { - when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any( - ActivityManager.RecentTaskInfo.class))).thenReturn(true); + when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())).thenReturn(TEST_TASK_ID); final NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); mBinderService.createNotificationChannels(PKG_NO_CHANNELS, @@ -985,8 +985,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCreateNotificationChannels_SecondChannelWithFgndTaskDoesntStartPermDialog() throws Exception { - when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any( - ActivityManager.RecentTaskInfo.class))).thenReturn(true); + when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())).thenReturn(TEST_TASK_ID); assertTrue(mBinderService.getNumNotificationChannelsForPackage(PKG, mUid, true) > 0); final NotificationChannel channel = @@ -1001,8 +1000,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCreateNotificationChannels_FirstChannelWithBgndTaskDoesntStartPermDialog() throws Exception { reset(mPermissionPolicyInternal); - when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any( - ActivityManager.RecentTaskInfo.class))).thenReturn(false); + when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())).thenReturn(TEST_TASK_ID); final NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);