Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b17596b0 authored by Nate Myren's avatar Nate Myren
Browse files

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
parent e9a0acdf
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -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
+56 −11
Original line number Diff line number Diff line
@@ -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<ActivityManager.AppTask> 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) {
+9 −7
Original line number Diff line number Diff line
@@ -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();
+1 −1
Original line number Diff line number Diff line
@@ -3378,7 +3378,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
            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;
            }