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

Commit e3874038 authored by Bernardo Rufino's avatar Bernardo Rufino
Browse files

Block non-activity notification trampolines for apps w/ targetSdk S+

Block activity starts coming from broadcast receivers and services in
response to notification and notification action clicks for apps that
target SDK S+.

Note that for those cases (the ones that are being blocked), I'm always
showing the toast.

Also move SAW check to before the process flag check so we don't show
toast/log false positives (apparently I missed this).

Bug: 167676448
Test: atest NotificationManagerTest
Change-Id: I3d8124ca66718a9726121ff65e519cdcf349b214
parent f62f7c84
Loading
Loading
Loading
Loading
+29 −14
Original line number Diff line number Diff line
@@ -139,6 +139,7 @@ import android.app.StatusBarManager;
import android.app.UriGrantsManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.backup.BackupManager;
import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.app.usage.UsageEvents;
@@ -407,6 +408,15 @@ public class NotificationManagerService extends SystemService {
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
    private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L;

    /**
     * Activity starts coming from broadcast receivers or services in response to notification and
     * notification action clicks will be blocked for UX and performance reasons. Instead start the
     * activity directly from the PendingIntent.
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
    private static final long NOTIFICATION_TRAMPOLINE_BLOCK = 167676448L;

    private IActivityManager mAm;
    private ActivityTaskManagerInternal mAtm;
    private ActivityManager mActivityManager;
@@ -10005,7 +10015,7 @@ public class NotificationManagerService extends SystemService {
     * TODO(b/161957908): Remove dogfooder toast.
     */
    private class NotificationTrampolineCallback implements BackgroundActivityStartCallback {
        private Set<String> mPackagesShown = new ArraySet<>();
        private final Set<String> mPackagesShown = new ArraySet<>();

        @Override
        public IBinder getToken() {
@@ -10013,13 +10023,9 @@ public class NotificationManagerService extends SystemService {
        }

        @Override
        public void onExclusiveTokenActivityStart(String packageName) {
            Slog.w(TAG, "Indirect notification activity start from " + packageName);
            boolean isFirstOccurrence = mPackagesShown.add(packageName);
            if (!isFirstOccurrence) {
                return;
            }

        public boolean isActivityStartAllowed(int uid, String packageName) {
            boolean block = CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
            if (block || mPackagesShown.add(packageName)) {
                mUiHandler.post(() ->
                        Toast.makeText(getUiContext(),
                                "Indirect activity start from "
@@ -10028,5 +10034,14 @@ public class NotificationManagerService extends SystemService {
                                        + "See go/s-trampolines.",
                                Toast.LENGTH_LONG).show());
            }
            String message =
                    "Indirect notification activity start (trampoline) from " + packageName;
            if (block) {
                Slog.e(TAG, message + " blocked");
                return false;
            }
            Slog.w(TAG, message + ", this should be avoided for performance reasons");
            return true;
        }
    }
}
+6 −6
Original line number Diff line number Diff line
@@ -1358,6 +1358,12 @@ class ActivityStarter {
            }
            return false;
        }
        // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
        if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
            Slog.w(TAG, "Background activity start for " + callingPackage
                    + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
            return false;
        }
        // If we don't have callerApp at this point, no caller was provided to startActivity().
        // That's the case for PendingIntent-based starts, since the creator's process might not be
        // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
@@ -1394,12 +1400,6 @@ class ActivityStarter {
                }
            }
        }
        // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
        if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
            Slog.w(TAG, "Background activity start for " + callingPackage
                    + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
            return false;
        }
        // anything that has fallen through would currently be aborted
        Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                + "; callingUid: " + callingUid
+7 −4
Original line number Diff line number Diff line
@@ -24,8 +24,8 @@ import android.os.IBinder;
 */
public interface BackgroundActivityStartCallback {
    /**
     * The token that allowed the activity start that triggered {@link
     * #onExclusiveTokenActivityStart()}.
     * The token for which this callback is responsible for deciding whether the app can start
     * background activities or not.
     *
     * Ideally this should just return a final variable, don't do anything costly here (don't hold
     * any locks).
@@ -33,7 +33,10 @@ public interface BackgroundActivityStartCallback {
    IBinder getToken();

    /**
     * Called when the background activity start happens.
     * Returns true if the background activity start due to originating token in {@link #getToken()}
     * should be allowed or not.
     *
     * This will be called holding the WM lock, don't do anything costly here.
     */
    void onExclusiveTokenActivityStart(String packageName);
    boolean isActivityStartAllowed(int uid, String packageName);
}
+18 −8
Original line number Diff line number Diff line
@@ -223,7 +223,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
    private boolean mRunningRemoteAnimation;

    @Nullable
    private BackgroundActivityStartCallback mBackgroundActivityStartCallback;
    private final BackgroundActivityStartCallback mBackgroundActivityStartCallback;

    /** The state for oom-adjustment calculation. */
    private final OomScoreReferenceState mOomRefState;
@@ -555,8 +555,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
            return true;
        }
        // allow if the flag was explicitly set
        if (!mBackgroundActivityStartTokens.isEmpty()) {
            onBackgroundStartAllowedByToken();
        if (isBackgroundStartAllowedByToken()) {
            if (DEBUG_ACTIVITY_STARTS) {
                Slog.d(TAG, "[WindowProcessController(" + mPid
                        + ")] Activity start allowed: process allowed by token");
@@ -566,18 +565,29 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
        return false;
    }

    private void onBackgroundStartAllowedByToken() {
    /**
     * If there are no tokens, we don't allow *by token*. If there are tokens, we need to check if
     * the callback handles all the tokens, if so we ask the callback if the activity should be
     * started, otherwise we allow.
     */
    private boolean isBackgroundStartAllowedByToken() {
        if (mBackgroundActivityStartTokens.isEmpty()) {
            return false;
        }
        if (mBackgroundActivityStartCallback == null) {
            return;
            // We have tokens but no callback to decide => allow
            return true;
        }
        IBinder callbackToken = mBackgroundActivityStartCallback.getToken();
        for (IBinder token : mBackgroundActivityStartTokens.values()) {
            if (token != callbackToken) {
                return;
                // The callback doesn't handle all the tokens => allow
                return true;
            }
        }
        mAtm.mH.post(() ->
                mBackgroundActivityStartCallback.onExclusiveTokenActivityStart(mInfo.packageName));
        // The callback handles all the tokens => callback decides
        return mBackgroundActivityStartCallback.isActivityStartAllowed(mInfo.uid,
                mInfo.packageName);
    }

    private boolean isBoundByForegroundUid() {