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

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

Restrict what activity launches show permission prompts

Only show a permission prompt if the activity is a launcher, the calling
app is the same as the opened intent app, or the
isEligibleForLegacyPermissionPrompt ActivityOption is set. Also prevents
the dialog from showing while the keyguard is locked.

Test: atest NotificationPermissionTest
Bug: 194833441
Fixes: 220891438
Change-Id: Ie5381af045d538a5b822bb1ccdf62b1dd916114f
parent 09076f0a
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -155,8 +155,10 @@ package android.app {

  public class ActivityOptions {
    method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle);
    method public boolean isEligibleForLegacyPermissionPrompt();
    method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
    method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
    method public void setEligibleForLegacyPermissionPrompt(boolean);
    method public static void setExitTransitionTimeout(long);
    method public void setLaunchActivityType(int);
    method public void setLaunchWindowingMode(int);
+33 −0
Original line number Diff line number Diff line
@@ -173,6 +173,13 @@ public class ActivityOptions extends ComponentOptions {
     */
    public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";

    /**
     * Indicates that this activity launch is eligible to show a legacy permission prompt
     * @hide
     */
    public static final String KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE =
            "android:activity.legacyPermissionPromptEligible";

    /**
     * Callback for when the last frame of the animation is played.
     * @hide
@@ -445,6 +452,7 @@ public class ActivityOptions extends ComponentOptions {
    private String mSplashScreenThemeResName;
    @SplashScreen.SplashScreenStyle
    private int mSplashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
    private boolean mIsEligibleForLegacyPermissionPrompt;
    private boolean mRemoveWithTaskOrganizer;
    private boolean mLaunchedFromBubble;
    private boolean mTransientLaunch;
@@ -1243,6 +1251,8 @@ public class ActivityOptions extends ComponentOptions {
        mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
        mSplashScreenStyle = opts.getInt(KEY_SPLASH_SCREEN_STYLE);
        mLaunchIntoPipParams = opts.getParcelable(KEY_LAUNCH_INTO_PIP_PARAMS);
        mIsEligibleForLegacyPermissionPrompt =
                opts.getBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE);
    }

    /**
@@ -1473,6 +1483,24 @@ public class ActivityOptions extends ComponentOptions {
        return this;
    }

    /**
     * Whether the activity is eligible to show a legacy permission prompt
     * @hide
     */
    @TestApi
    public boolean isEligibleForLegacyPermissionPrompt() {
        return mIsEligibleForLegacyPermissionPrompt;
    }

    /**
     * Sets whether the activity is eligible to show a legacy permission prompt
     * @hide
     */
    @TestApi
    public void setEligibleForLegacyPermissionPrompt(boolean eligible) {
        mIsEligibleForLegacyPermissionPrompt = eligible;
    }

    /**
     * Sets whether the activity is to be launched into LockTask mode.
     *
@@ -1909,6 +1937,7 @@ public class ActivityOptions extends ComponentOptions {
        mSpecsFuture = otherOptions.mSpecsFuture;
        mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
        mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams;
        mIsEligibleForLegacyPermissionPrompt = otherOptions.mIsEligibleForLegacyPermissionPrompt;
    }

    /**
@@ -2084,6 +2113,10 @@ public class ActivityOptions extends ComponentOptions {
        if (mLaunchIntoPipParams != null) {
            b.putParcelable(KEY_LAUNCH_INTO_PIP_PARAMS, mLaunchIntoPipParams);
        }
        if (mIsEligibleForLegacyPermissionPrompt) {
            b.putBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE,
                    mIsEligibleForLegacyPermissionPrompt);
        }
        return b;
    }

+8 −2
Original line number Diff line number Diff line
@@ -3972,8 +3972,14 @@ public class CentralSurfaces extends CoreStartable implements

                mActivityLaunchAnimator.startPendingIntentWithAnimation(
                        controller, animate, intent.getCreatorPackage(),
                        (animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
                                null, getActivityOptions(mDisplayId, animationAdapter)));
                        (animationAdapter) -> {
                            ActivityOptions options = new ActivityOptions(
                                    getActivityOptions(mDisplayId, animationAdapter));
                            // TODO b/221255671: restrict this to only be set for notifications
                            options.setEligibleForLegacyPermissionPrompt(true);
                            return intent.sendAndReturnResult(null, 0, null, null, null,
                                    null, options.toBundle());
                        });
            } catch (PendingIntent.CanceledException e) {
                // the stack trace isn't very helpful here.
                // Just log the exception message.
+42 −12
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -150,6 +151,7 @@ public final class PermissionPolicyService extends SystemService {
    private Context mContext;
    private PackageManagerInternal mPackageManagerInternal;
    private NotificationManagerInternal mNotificationManager;
    private final KeyguardManager mKeyguardManager;
    private final PackageManager mPackageManager;

    public PermissionPolicyService(@NonNull Context context) {
@@ -157,6 +159,7 @@ public final class PermissionPolicyService extends SystemService {

        mContext = context;
        mPackageManager = context.getPackageManager();
        mKeyguardManager = context.getSystemService(KeyguardManager.class);
        LocalServices.addService(PermissionPolicyInternal.class, new Internal());
    }

@@ -1046,13 +1049,22 @@ public final class PermissionPolicyService extends SystemService {
                    }

                    @Override
                    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
                        super.onActivityLaunched(taskInfo, activityInfo);
                        clearNotificationReviewFlagsIfNeeded(activityInfo.packageName,
                                UserHandle.of(taskInfo.userId));
                    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
                            ActivityInterceptorInfo info) {
                        super.onActivityLaunched(taskInfo, activityInfo, info);
                        if (!shouldShowNotificationDialogOrClearFlags(info.intent,
                                info.checkedOptions)) {
                            return;
                        }
                        UserHandle user = UserHandle.of(taskInfo.userId);
                        if (CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
                                activityInfo.packageName, user)) {
                            clearNotificationReviewFlagsIfNeeded(activityInfo.packageName, user);
                        } else {
                            showNotificationPromptIfNeeded(activityInfo.packageName,
                                    taskInfo.userId, taskInfo.taskId);
                        }
                    }
                };

        private void onActivityManagerReady() {
@@ -1092,10 +1104,28 @@ public final class PermissionPolicyService extends SystemService {
            launchNotificationPermissionRequestDialog(packageName, user, taskId);
        }

        /**
         * 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:
         * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or
         * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER)
         */
        private boolean shouldShowNotificationDialogOrClearFlags(Intent intent,
                ActivityOptions options) {
            if ((options != null && options.isEligibleForLegacyPermissionPrompt())) {
                return true;
            }

            return Intent.ACTION_MAIN.equals(intent.getAction())
                    && intent.getCategories() != null
                    && (intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)
                    || intent.getCategories().contains(Intent.CATEGORY_LEANBACK_LAUNCHER)
                    || intent.getCategories().contains(Intent.CATEGORY_CAR_LAUNCHER));
        }

        private void clearNotificationReviewFlagsIfNeeded(String packageName, UserHandle user) {
            if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, packageName, user)
                    || ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user)
                    & FLAG_PERMISSION_REVIEW_REQUIRED) == 0)) {
            if ((mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, packageName, user)
                    & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
                return;
            }
            try {
@@ -1210,8 +1240,8 @@ public final class PermissionPolicyService extends SystemService {
            }

            if (!pkg.getRequestedPermissions().contains(POST_NOTIFICATIONS)
                    || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
                    pkg.getPackageName(), user)) {
                    || CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, pkgName, user)
                    || mKeyguardManager.isKeyguardLocked()) {
                return false;
            }

@@ -1220,7 +1250,7 @@ public final class PermissionPolicyService extends SystemService {
                mNotificationManager = LocalServices.getService(NotificationManagerInternal.class);
            }
            boolean hasCreatedNotificationChannels = mNotificationManager
                    .getNumNotificationChannelsForPackage(pkg.getPackageName(), uid, true) > 0;
                    .getNumNotificationChannelsForPackage(pkgName, uid, true) > 0;
            int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
            boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
            boolean needsReview = (flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
+6 −2
Original line number Diff line number Diff line
@@ -43,9 +43,13 @@ public abstract class ActivityInterceptorCallback {
    public abstract @Nullable ActivityInterceptResult intercept(ActivityInterceptorInfo info);

    /**
     * Called when an activity is successfully launched.
     * Called when an activity is successfully launched. The intent included in the
     * ActivityInterceptorInfo may have changed from the one sent in
     * {@link #intercept(ActivityInterceptorInfo)}, due to the return from
     * {@link #intercept(ActivityInterceptorInfo)}.
     */
    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo) {
    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
            ActivityInterceptorInfo info) {
    }

    /**
Loading