From b17596b05127cae7a15d6d127c42bb4501b2c731 Mon Sep 17 00:00:00 2001 From: Nate Myren Date: Fri, 15 Apr 2022 11:26:02 -0700 Subject: [PATCH] Show Notification Permission dialog on (some) task trampolines If an app creates its first notification channel, and the current task was trampolined from another by the same app, then show the notification prompt. If an app starts, and is a trampoline, check if there are any other running, focused tasks which were a launcher start. If so, show the notification prompt Test: manual Fixes: 229282386 Change-Id: I2c2b96b1597d7f09a44668423308ae76d1f9699c --- .../policy/PermissionPolicyInternal.java | 5 +- .../policy/PermissionPolicyService.java | 67 ++++++++++++++++--- .../com/android/server/wm/AppTaskImpl.java | 16 +++-- .../server/wm/RootWindowContainer.java | 2 +- 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java index 92b9944b74cf8..77885c7ab8ba4 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java @@ -74,10 +74,13 @@ public abstract class PermissionPolicyInternal { * * @param taskInfo The task to be checked * @param currPkg The package of the current top visible activity + * @param callingPkg The package that started the top visible activity * @param intent The intent of the current top visible activity + * @param activityName The name of the current top visible activity */ public abstract boolean shouldShowNotificationDialogForTask(@Nullable TaskInfo taskInfo, - @Nullable String currPkg, @Nullable Intent intent); + @Nullable String currPkg, @Nullable String callingPkg, @Nullable Intent intent, + @NonNull String activityName); /** * @return true if an intent will resolve to a permission request dialog activity diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 89ac9e7739065..aa51021a5c842 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -35,6 +35,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppOpsManager; @@ -1067,7 +1068,8 @@ public final class PermissionPolicyService extends SystemService { ActivityInterceptorInfo info) { super.onActivityLaunched(taskInfo, activityInfo, info); if (!shouldShowNotificationDialogOrClearFlags(taskInfo, - activityInfo.packageName, info.intent, info.checkedOptions, true) + activityInfo.packageName, info.callingPackage, info.intent, + info.checkedOptions, activityInfo.name, true) || isNoDisplayActivity(activityInfo)) { return; } @@ -1138,9 +1140,9 @@ public final class PermissionPolicyService extends SystemService { @Override public boolean shouldShowNotificationDialogForTask(TaskInfo taskInfo, String currPkg, - Intent intent) { - return shouldShowNotificationDialogOrClearFlags( - taskInfo, currPkg, intent, null, false); + String callingPkg, Intent intent, String activityName) { + return shouldShowNotificationDialogOrClearFlags(taskInfo, currPkg, callingPkg, intent, + null, activityName, false); } private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo) { @@ -1166,23 +1168,61 @@ public final class PermissionPolicyService extends SystemService { * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or * 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 + * originally, and the task was started with a launcher intent, or + * 4. The activity is the first activity in a new task, and was started by the app the + * activity belongs to, and that app has another task that is currently focused, which was + * started with a launcher intent. This case seeks to identify cases where an app launches, + * then immediately trampolines to a new activity and task. * @param taskInfo The task to be checked * @param currPkg The package of the current top visible activity + * @param callingPkg The package that initiated this dialog action * @param intent The intent of the current top visible activity + * @param options The ActivityOptions of the newly started activity, if this is called due + * to an activity start + * @param startedActivity The ActivityInfo of the newly started activity, if this is called + * due to an activity start */ private boolean shouldShowNotificationDialogOrClearFlags(TaskInfo taskInfo, String currPkg, - Intent intent, ActivityOptions options, boolean activityStart) { - if (intent == null || currPkg == null || taskInfo == null + String callingPkg, Intent intent, ActivityOptions options, + String topActivityName, boolean startedActivity) { + if (intent == null || currPkg == null || taskInfo == null || topActivityName == null || (!(taskInfo.isFocused && taskInfo.isVisible && taskInfo.isRunning) - && !activityStart)) { + && !startedActivity)) { return false; } - return isLauncherIntent(intent) || (options != null && options.isEligibleForLegacyPermissionPrompt()) - || (currPkg.equals(taskInfo.baseActivity.getPackageName()) - && isLauncherIntent(taskInfo.baseIntent)); + || isTaskStartedFromLauncher(currPkg, taskInfo) + || (isTaskPotentialTrampoline(topActivityName, currPkg, callingPkg, taskInfo, + intent) + && (!startedActivity || pkgHasRunningLauncherTask(currPkg, taskInfo))); + } + + private boolean isTaskPotentialTrampoline(String activityName, String currPkg, + String callingPkg, TaskInfo taskInfo, Intent intent) { + return currPkg.equals(callingPkg) && taskInfo.baseIntent.filterEquals(intent) + && taskInfo.numActivities == 1 + && activityName.equals(taskInfo.topActivityInfo.name); + } + + private boolean pkgHasRunningLauncherTask(String currPkg, TaskInfo taskInfo) { + ActivityTaskManagerInternal m = + LocalServices.getService(ActivityTaskManagerInternal.class); + try { + // TODO(b/230616478) Investigate alternatives like ActivityMetricsLaunchObserver + List tasks = + m.getAppTasks(currPkg, mPackageManager.getPackageUid(currPkg, 0)); + for (int i = 0; i < tasks.size(); i++) { + TaskInfo other = tasks.get(i).getTaskInfo(); + if (other.taskId != taskInfo.taskId && other.isFocused && other.isRunning + && isTaskStartedFromLauncher(currPkg, other)) { + return true; + } + } + } catch (PackageManager.NameNotFoundException e) { + // Fall through + } + return false; } private boolean isLauncherIntent(Intent intent) { @@ -1193,6 +1233,11 @@ public final class PermissionPolicyService extends SystemService { || intent.getCategories().contains(Intent.CATEGORY_CAR_LAUNCHER)); } + private boolean isTaskStartedFromLauncher(String currPkg, TaskInfo taskInfo) { + return currPkg.equals(taskInfo.baseActivity.getPackageName()) + && isLauncherIntent(taskInfo.baseIntent); + } + private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) { if ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user) & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 6e46fa6b67d0d..e80c2607a0ade 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -48,8 +49,9 @@ class AppTaskImpl extends IAppTask.Stub { mCallingUid = callingUid; } - private void checkCaller() { - if (mCallingUid != Binder.getCallingUid()) { + private void checkCallerOrSystemOrRoot() { + if (mCallingUid != Binder.getCallingUid() && Process.SYSTEM_UID != Binder.getCallingUid() + && Process.ROOT_UID != Binder.getCallingUid()) { throw new SecurityException("Caller " + mCallingUid + " does not match caller of getAppTasks(): " + Binder.getCallingUid()); } @@ -67,7 +69,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void finishAndRemoveTask() { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); @@ -85,7 +87,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public ActivityManager.RecentTaskInfo getTaskInfo() { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); @@ -105,7 +107,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void moveToFront(IApplicationThread appThread, String callingPackage) { - checkCaller(); + checkCallerOrSystemOrRoot(); // Will bring task to front if it already has a root activity. final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -136,7 +138,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public int startActivity(IBinder whoThread, String callingPackage, String callingFeatureId, Intent intent, String resolvedType, Bundle bOptions) { - checkCaller(); + checkCallerOrSystemOrRoot(); mService.assertPackageMatchesCallingUid(callingPackage); int callingUser = UserHandle.getCallingUserId(); @@ -167,7 +169,7 @@ class AppTaskImpl extends IAppTask.Stub { @Override public void setExcludeFromRecents(boolean exclude) { - checkCaller(); + checkCallerOrSystemOrRoot(); synchronized (mService.mGlobalLock) { final long origId = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 22714c6f55937..a483a05d49c9c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3378,7 +3378,7 @@ class RootWindowContainer extends WindowContainer if (record != null && record.isUid(uid) && Objects.equals(pkgName, record.packageName) && pPi.shouldShowNotificationDialogForTask(record.getTask().getTaskInfo(), - pkgName, record.intent)) { + pkgName, record.launchedFromPackage, record.intent, record.getName())) { validTaskId[0] = record.getTask().mTaskId; return true; }