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

Commit 9195a05c authored by Bernardo Rufino's avatar Bernardo Rufino Committed by Android (Google) Code Review
Browse files

Merge "Exempt the default browser from trampoline restrictions" into sc-dev

parents 580522c0 dc3727eb
Loading
Loading
Loading
Loading
+87 −13
Original line number Diff line number Diff line
@@ -123,7 +123,7 @@ import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;

import android.Manifest;
import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -492,7 +492,7 @@ public class NotificationManagerService extends SystemService {
    private DeviceIdleManager mDeviceIdleManager;
    private IUriGrantsManager mUgm;
    private UriGrantsManagerInternal mUgmInternal;
    private RoleObserver mRoleObserver;
    private volatile RoleObserver mRoleObserver;
    private UserManager mUm;
    private IPlatformCompat mPlatformCompat;
    private ShortcutHelper mShortcutHelper;
@@ -2651,6 +2651,11 @@ public class NotificationManagerService extends SystemService {

    @Override
    public void onBootPhase(int phase) {
        onBootPhase(phase, Looper.getMainLooper());
    }

    @VisibleForTesting
    void onBootPhase(int phase, Looper mainLooper) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            // no beeping until we're basically done booting
            mSystemReady = true;
@@ -2660,9 +2665,11 @@ public class NotificationManagerService extends SystemService {
            mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
            mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
            mZenModeHelper.onSystemReady();
            mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
                    mPackageManager, getContext().getMainExecutor());
            mRoleObserver.init();
            RoleObserver roleObserver = new RoleObserver(getContext(),
                    getContext().getSystemService(RoleManager.class),
                    mPackageManager, mainLooper);
            roleObserver.init();
            mRoleObserver = roleObserver;
            LauncherApps launcherApps =
                    (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
            mShortcutHelper = new ShortcutHelper(launcherApps, mShortcutListener, getLocalService(
@@ -10688,26 +10695,40 @@ public class NotificationManagerService extends SystemService {
        // Role name : user id : list of approved packages
        private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps;

        /**
         * Writes should be pretty rare (only when default browser changes) and reads are done
         * during activity start code-path, so we're optimizing for reads. This means this set is
         * immutable once written and we'll recreate the set every time there is a role change and
         * then assign that new set to the volatile below, so reads can be done without needing to
         * hold a lock. Every write is done on the main-thread, so write atomicity is guaranteed.
         *
         * Didn't use unmodifiable set to enforce immutability to avoid iterating via iterators.
         */
        private volatile ArraySet<Integer> mTrampolineExemptUids = new ArraySet<>();

        private final RoleManager mRm;
        private final IPackageManager mPm;
        private final Executor mExecutor;
        private final Looper mMainLooper;

        RoleObserver(@NonNull RoleManager roleManager,
                @NonNull IPackageManager pkgMgr,
                @NonNull @CallbackExecutor Executor executor) {
        RoleObserver(Context context, @NonNull RoleManager roleManager,
                @NonNull IPackageManager pkgMgr, @NonNull Looper mainLooper) {
            mRm = roleManager;
            mPm = pkgMgr;
            mExecutor = executor;
            mExecutor = context.getMainExecutor();
            mMainLooper = mainLooper;
        }

        /** Should be called from the main-thread. */
        @MainThread
        public void init() {
            List<UserInfo> users = mUm.getUsers();
            List<UserHandle> users = mUm.getUserHandles(/* excludeDying */ true);
            mNonBlockableDefaultApps = new ArrayMap<>();
            for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
                final ArrayMap<Integer, ArraySet<String>> userToApprovedList = new ArrayMap<>();
                mNonBlockableDefaultApps.put(NON_BLOCKABLE_DEFAULT_ROLES[i], userToApprovedList);
                for (int j = 0; j < users.size(); j++) {
                    Integer userId = users.get(j).getUserHandle().getIdentifier();
                    Integer userId = users.get(j).getIdentifier();
                    ArraySet<String> approvedForUserId = new ArraySet<>(mRm.getRoleHoldersAsUser(
                            NON_BLOCKABLE_DEFAULT_ROLES[i], UserHandle.of(userId)));
                    ArraySet<Pair<String, Integer>> approvedAppUids = new ArraySet<>();
@@ -10718,7 +10739,7 @@ public class NotificationManagerService extends SystemService {
                    mPreferencesHelper.updateDefaultApps(userId, null, approvedAppUids);
                }
            }

            updateTrampolineExemptUidsForUsers(users.toArray(new UserHandle[0]));
            mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
        }

@@ -10727,6 +10748,11 @@ public class NotificationManagerService extends SystemService {
            return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
        }

        @VisibleForTesting
        public boolean isUidExemptFromTrampolineRestrictions(int uid) {
            return mTrampolineExemptUids.contains(uid);
        }

        /**
         * Convert the assistant-role holder into settings. The rest of the system uses the
         * settings.
@@ -10736,6 +10762,12 @@ public class NotificationManagerService extends SystemService {
         */
        @Override
        public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
            onRoleHoldersChangedForNonBlockableDefaultApps(roleName, user);
            onRoleHoldersChangedForTrampolines(roleName, user);
        }

        private void onRoleHoldersChangedForNonBlockableDefaultApps(@NonNull String roleName,
                @NonNull UserHandle user) {
            // we only care about a couple of the roles they'll tell us about
            boolean relevantChange = false;
            for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
@@ -10783,6 +10815,41 @@ public class NotificationManagerService extends SystemService {
            // write of the notification policy xml for this change
        }

        private void onRoleHoldersChangedForTrampolines(@NonNull String roleName,
                @NonNull UserHandle user) {
            if (!RoleManager.ROLE_BROWSER.equals(roleName)) {
                return;
            }
            updateTrampolineExemptUidsForUsers(user);
        }

        private void updateTrampolineExemptUidsForUsers(UserHandle... users) {
            Preconditions.checkState(mMainLooper.isCurrentThread());
            ArraySet<Integer> oldUids = mTrampolineExemptUids;
            ArraySet<Integer> newUids = new ArraySet<>();
            // Add the uids from previous set for the users that we won't update.
            for (int i = 0, n = oldUids.size(); i < n; i++) {
                int uid = oldUids.valueAt(i);
                UserHandle user = UserHandle.of(UserHandle.getUserId(uid));
                if (!ArrayUtils.contains(users, user)) {
                    newUids.add(uid);
                }
            }
            // Now lookup the new uids for the users that we want to update.
            for (int i = 0, n = users.length; i < n; i++) {
                UserHandle user = users[i];
                for (String pkg : mRm.getRoleHoldersAsUser(RoleManager.ROLE_BROWSER, user)) {
                    int uid = getUidForPackage(pkg, user.getIdentifier());
                    if (uid != -1) {
                        newUids.add(uid);
                    } else {
                        Slog.e(TAG, "Bad uid (-1) for browser package " + pkg);
                    }
                }
            }
            mTrampolineExemptUids = newUids;
        }

        private int getUidForPackage(String pkg, int userId) {
            try {
                return mPm.getPackageUid(pkg, MATCH_ALL, userId);
@@ -10947,7 +11014,7 @@ public class NotificationManagerService extends SystemService {
            }
            String logcatMessage =
                    "Indirect notification activity start (trampoline) from " + packageName;
            if (CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid)) {
            if (blockTrampoline(uid)) {
                // Post toast() call to mHandler to offload PM lookup from the activity start path
                mHandler.post(() -> toast(packageName, uid));
                Slog.e(TAG, logcatMessage + " blocked");
@@ -10958,6 +11025,13 @@ public class NotificationManagerService extends SystemService {
            }
        }

        private boolean blockTrampoline(int uid) {
            if (mRoleObserver != null && mRoleObserver.isUidExemptFromTrampolineRestrictions(uid)) {
                return false;
            }
            return CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
        }

        @Override
        public boolean canCloseSystemDialogs(Collection<IBinder> tokens, int uid) {
            // If the start is allowed via notification, we allow the app to close system dialogs
+6 −1
Original line number Diff line number Diff line
@@ -141,6 +141,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
@@ -267,6 +268,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    RankingHandler mRankingHandler;
    @Mock
    ActivityManagerInternal mAmi;
    @Mock
    private Looper mMainLooper;

    @Mock
    IIntentSender pi1;
@@ -514,7 +517,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class),
                mAmi, mToastRateLimiter);
        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
        // Return first true for RoleObserver main-thread check
        when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);

        mService.setAudioManager(mAudioManager);

+102 −34
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.notification;

import static android.app.role.RoleManager.ROLE_BROWSER;
import static android.app.role.RoleManager.ROLE_DIALER;
import static android.app.role.RoleManager.ROLE_EMERGENCY;
import static android.content.pm.PackageManager.MATCH_ALL;
@@ -23,6 +24,7 @@ import static android.content.pm.PackageManager.MATCH_ALL;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -31,6 +33,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static java.util.Arrays.asList;

import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
@@ -44,7 +48,6 @@ import android.companion.ICompanionDeviceManager;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
@@ -80,7 +83,6 @@ import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -98,13 +100,13 @@ public class RoleObserverTest extends UiServiceTestCase {
    @Mock
    private UserManager mUm;
    @Mock
    private Executor mExecutor;
    @Mock
    private RoleManager mRoleManager;
    @Mock
    private Looper mMainLooper;
    NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
    private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
            1 << 30);
    private List<UserInfo> mUsers;
    private List<UserHandle> mUsers;

    private static class TestableNotificationManagerService extends NotificationManagerService {
        TestableNotificationManagerService(Context context,
@@ -133,13 +135,15 @@ public class RoleObserverTest extends UiServiceTestCase {
        mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));

        mUsers = new ArrayList<>();
        mUsers.add(new UserInfo(0, "system", 0));
        mUsers.add(new UserInfo(10, "second", 0));
        when(mUm.getUsers()).thenReturn(mUsers);
        mUsers.add(new UserHandle(0));
        mUsers.add(new UserHandle(10));
        when(mUm.getUserHandles(anyBoolean())).thenReturn(mUsers);

        when(mMainLooper.isCurrentThread()).thenReturn(true);

        mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
                mNotificationInstanceIdSequence);
        mRoleObserver = mService.new RoleObserver(mRoleManager, mPm, mExecutor);
        mRoleObserver = mService.new RoleObserver(mContext, mRoleManager, mPm, mMainLooper);

        try {
            mService.init(mService.new WorkerHandler(mock(Looper.class)),
@@ -174,7 +178,7 @@ public class RoleObserverTest extends UiServiceTestCase {
    }

    @Test
    public void testInit() throws Exception {
    public void testInit_forNonBlockableDefaultApps() throws Exception {
        List<String> dialer0 = new ArrayList<>();
        dialer0.add("dialer");
        List<String> emer0 = new ArrayList<>();
@@ -191,29 +195,29 @@ public class RoleObserverTest extends UiServiceTestCase {

        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_DIALER,
                mUsers.get(0).getUserHandle())).
                thenReturn(dialer0);
                mUsers.get(0)))
                .thenReturn(dialer0);
        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_EMERGENCY,
                mUsers.get(0).getUserHandle())).
                thenReturn(emer0);
                mUsers.get(0)))
                .thenReturn(emer0);

        mRoleObserver.init();

        // verify internal records of current state of the world
        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
                ROLE_DIALER, dialer0.get(0), mUsers.get(0).id));
                ROLE_DIALER, dialer0.get(0), mUsers.get(0).getIdentifier()));
        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
                ROLE_DIALER, dialer0.get(0), mUsers.get(1).id));
                ROLE_DIALER, dialer0.get(0), mUsers.get(1).getIdentifier()));

        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
                ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).id));
                ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).getIdentifier()));
        assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
                ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).id));
                ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).getIdentifier()));

        // make sure we're listening to updates
        verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser(
                eq(mExecutor), any(), eq(UserHandle.ALL));
                any(), any(), eq(UserHandle.ALL));

        // make sure we told pref helper about the state of the world
        verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, dialer0Pair);
@@ -221,14 +225,31 @@ public class RoleObserverTest extends UiServiceTestCase {
    }

    @Test
    public void testSwapDefault() throws Exception {
    public void testInit_forTrampolines() throws Exception {
        when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_BROWSER,
                mUsers.get(0)))
                .thenReturn(asList("com.browser"));

        mRoleObserver.init();

        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));

        // make sure we're listening to updates
        verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser(any(), any(),
                eq(UserHandle.ALL));
    }

    @Test
    public void testSwapDefault_forNonBlockableDefaultApps() throws Exception {
        List<String> dialer0 = new ArrayList<>();
        dialer0.add("dialer");

        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_DIALER,
                mUsers.get(0).getUserHandle())).
                thenReturn(dialer0);
                mUsers.get(0)))
                .thenReturn(dialer0);

        mRoleObserver.init();

@@ -241,8 +262,8 @@ public class RoleObserverTest extends UiServiceTestCase {

        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_DIALER,
                mUsers.get(0).getUserHandle())).
                thenReturn(newDefault);
                mUsers.get(0)))
                .thenReturn(newDefault);

        mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0));

@@ -251,15 +272,39 @@ public class RoleObserverTest extends UiServiceTestCase {
    }

    @Test
    public void testSwapDefault_multipleOverlappingApps() throws Exception {
    public void testSwapDefault_forTrampolines() throws Exception {
        List<String> dialer0 = new ArrayList<>();
        when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
        when(mPm.getPackageUid("com.browser2", MATCH_ALL, 0)).thenReturn(31);
        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_BROWSER,
                mUsers.get(0)))
                .thenReturn(asList("com.browser"));
        mRoleObserver.init();
        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
        assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(31));
        // Default changed
        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_BROWSER,
                mUsers.get(0)))
                .thenReturn(asList("com.browser2"));
        mRoleObserver.onRoleHoldersChanged(ROLE_BROWSER, UserHandle.of(0));

        assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(31));
    }

    @Test
    public void testSwapDefault_multipleOverlappingApps_forNonBlockableDefaultApps()
            throws Exception {
        List<String> dialer0 = new ArrayList<>();
        dialer0.add("dialer");
        dialer0.add("phone");

        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_DIALER,
                mUsers.get(0).getUserHandle())).
                thenReturn(dialer0);
                mUsers.get(0)))
                .thenReturn(dialer0);

        mRoleObserver.init();

@@ -273,8 +318,8 @@ public class RoleObserverTest extends UiServiceTestCase {

        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_DIALER,
                mUsers.get(0).getUserHandle())).
                thenReturn(newDefault);
                mUsers.get(0)))
                .thenReturn(newDefault);

        ArraySet<String> expectedRemove = new ArraySet<>();
        expectedRemove.add("dialer");
@@ -294,14 +339,14 @@ public class RoleObserverTest extends UiServiceTestCase {
    }

    @Test
    public void testSwapDefault_newUser() throws Exception {
    public void testSwapDefault_newUser_forNonBlockableDefaultApps() throws Exception {
        List<String> dialer0 = new ArrayList<>();
        dialer0.add("dialer");

        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_DIALER,
                mUsers.get(0).getUserHandle())).
                thenReturn(dialer0);
                mUsers.get(0)))
                .thenReturn(dialer0);

        mRoleObserver.init();

@@ -310,8 +355,8 @@ public class RoleObserverTest extends UiServiceTestCase {

        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_DIALER,
                mUsers.get(1).getUserHandle())).
                thenReturn(dialer10);
                mUsers.get(1)))
                .thenReturn(dialer10);

        ArraySet<Pair<String, Integer>> expectedAddPair = new ArraySet<>();
        expectedAddPair.add(new Pair("phone", 30));
@@ -329,4 +374,27 @@ public class RoleObserverTest extends UiServiceTestCase {
        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 10));
        assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
    }

    @Test
    public void testSwapDefault_newUser_forTrampolines() throws Exception {
        List<String> dialer0 = new ArrayList<>();
        when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
        when(mPm.getPackageUid("com.browser2", MATCH_ALL, 10)).thenReturn(1031);
        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_BROWSER,
                mUsers.get(0)))
                .thenReturn(asList("com.browser"));
        mRoleObserver.init();
        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
        assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(1031));
        // New user
        when(mRoleManager.getRoleHoldersAsUser(
                ROLE_BROWSER,
                mUsers.get(1)))
                .thenReturn(asList("com.browser2"));
        mRoleObserver.onRoleHoldersChanged(ROLE_BROWSER, UserHandle.of(10));

        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(1031));
    }
}