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

Commit 1ca6f8dd authored by Wenhao Wang's avatar Wenhao Wang
Browse files

[conflict] DO NOT MERGE Suppress notifications when device enter lockdown am: 85c00b98

parents c601e32e 85c00b98
Loading
Loading
Loading
Loading
+108 −6
Original line number Diff line number Diff line
@@ -204,6 +204,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.StatsLog;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -229,6 +230,7 @@ import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.DeviceIdleController;
import com.android.server.EventLogTags;
import com.android.server.IoThread;
@@ -1491,6 +1493,54 @@ public class NotificationManagerService extends SystemService {
        return out;
    }

    protected class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {

        SparseBooleanArray mUserInLockDownMode = new SparseBooleanArray();
        boolean mIsInLockDownMode = false;

        StrongAuthTracker(Context context) {
            super(context);
        }

        private boolean containsFlag(int haystack, int needle) {
            return (haystack & needle) != 0;
        }

        public boolean isInLockDownMode() {
            return mIsInLockDownMode;
        }

        @Override
        public synchronized void onStrongAuthRequiredChanged(int userId) {
            boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId),
                    STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
            mUserInLockDownMode.put(userId, userInLockDownModeNext);
            boolean isInLockDownModeNext = mUserInLockDownMode.indexOfValue(true) != -1;

            if (mIsInLockDownMode == isInLockDownModeNext) {
                return;
            }

            if (isInLockDownModeNext) {
                cancelNotificationsWhenEnterLockDownMode();
            }

            // When the mIsInLockDownMode is true, both notifyPostedLocked and
            // notifyRemovedLocked will be dismissed. So we shall call
            // cancelNotificationsWhenEnterLockDownMode before we set mIsInLockDownMode
            // as true and call postNotificationsWhenExitLockDownMode after we set
            // mIsInLockDownMode as false.
            mIsInLockDownMode = isInLockDownModeNext;

            if (!isInLockDownModeNext) {
                postNotificationsWhenExitLockDownMode();
            }
        }
    }

    private LockPatternUtils mLockPatternUtils;
    private StrongAuthTracker mStrongAuthTracker;

    public NotificationManagerService(Context context) {
        super(context);
        Notification.processWhitelistToken = WHITELIST_TOKEN;
@@ -1502,6 +1552,16 @@ public class NotificationManagerService extends SystemService {
        mAudioManager = audioMananger;
    }

    @VisibleForTesting
    void setStrongAuthTracker(StrongAuthTracker strongAuthTracker) {
        mStrongAuthTracker = strongAuthTracker;
    }

    @VisibleForTesting
    void setKeyguardManager(KeyguardManager keyguardManager) {
        mKeyguardManager = keyguardManager;
    }

    @VisibleForTesting
    void setHints(int hints) {
        mListenerHints = hints;
@@ -1672,6 +1732,8 @@ public class NotificationManagerService extends SystemService {

        mHandler = new WorkerHandler(looper);
        mRankingThread.start();
        mLockPatternUtils = new LockPatternUtils(getContext());
        mStrongAuthTracker = new StrongAuthTracker(getContext());
        String[] extractorNames;
        try {
            extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
@@ -1814,7 +1876,8 @@ public class NotificationManagerService extends SystemService {
        init(Looper.myLooper(),
                AppGlobals.getPackageManager(), getContext().getPackageManager(),
                getLocalService(LightsManager.class),
                new NotificationListeners(AppGlobals.getPackageManager()),
                new NotificationListeners(getContext(), mNotificationLock, mUserProfiles,
                        AppGlobals.getPackageManager()),
                new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles,
                        AppGlobals.getPackageManager()),
                new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
@@ -1957,6 +2020,7 @@ public class NotificationManagerService extends SystemService {
            mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
                    mPackageManager, getContext().getMainExecutor());
            mRoleObserver.init();
            mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
        } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
            // This observer will force an update when observe is called, causing us to
            // bind to listener services.
@@ -7350,6 +7414,29 @@ public class NotificationManagerService extends SystemService {
        }
    }

    private void cancelNotificationsWhenEnterLockDownMode() {
        synchronized (mNotificationLock) {
            int numNotifications = mNotificationList.size();
            for (int i = 0; i < numNotifications; i++) {
                NotificationRecord rec = mNotificationList.get(i);
                mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL,
                        rec.getStats());
            }

        }
    }

    private void postNotificationsWhenExitLockDownMode() {
        synchronized (mNotificationLock) {
            int numNotifications = mNotificationList.size();
            for (int i = 0; i < numNotifications; i++) {
                NotificationRecord rec = mNotificationList.get(i);
                mListeners.notifyPostedLocked(rec, rec);
            }

        }
    }

    private void updateNotificationPulse() {
        synchronized (mNotificationLock) {
            updateLightsLocked();
@@ -7560,6 +7647,10 @@ public class NotificationManagerService extends SystemService {
                rankings.toArray(new NotificationListenerService.Ranking[0]));
    }

    boolean isInLockDownMode() {
        return mStrongAuthTracker.isInLockDownMode();
    }

    boolean hasCompanionDevice(ManagedServiceInfo info) {
        if (mCompanionManager == null) {
            mCompanionManager = getCompanionManager();
@@ -8079,9 +8170,9 @@ public class NotificationManagerService extends SystemService {

        private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();

        public NotificationListeners(IPackageManager pm) {
            super(getContext(), mNotificationLock, mUserProfiles, pm);

        public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
                IPackageManager pm) {
            super(context, lock, userProfiles, pm);
        }

        @Override
@@ -8190,8 +8281,12 @@ public class NotificationManagerService extends SystemService {
         *                           targetting <= O_MR1
         */
        @GuardedBy("mNotificationLock")
        private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
        void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                boolean notifyAllListeners) {
            if (isInLockDownMode()) {
                return;
            }

            // Lazily initialized snapshots of the notification.
            StatusBarNotification sbn = r.sbn;
            StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
@@ -8254,8 +8349,11 @@ public class NotificationManagerService extends SystemService {
        @GuardedBy("mNotificationLock")
        public void notifyRemovedLocked(NotificationRecord r, int reason,
                NotificationStats notificationStats) {
            final StatusBarNotification sbn = r.sbn;
            if (isInLockDownMode()) {
                return;
            }

            final StatusBarNotification sbn = r.sbn;
            // make a copy in case changes are made to the underlying Notification object
            // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
            // notification
@@ -8306,6 +8404,10 @@ public class NotificationManagerService extends SystemService {
         */
        @GuardedBy("mNotificationLock")
        public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
            if (isInLockDownMode()) {
                return;
            }

            boolean isHiddenRankingUpdate = changedHiddenNotifications != null
                    && changedHiddenNotifications.size() > 0;

+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
    <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />

    <application android:debuggable="true">
        <uses-library android:name="android.test.runner" />
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.notification;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.INotificationManager;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.testing.TestableContext;

import com.android.server.UiServiceTestCase;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.util.reflection.FieldSetter;

import java.util.List;

public class NotificationListenersTest extends UiServiceTestCase {

    @Mock
    private PackageManager mPm;
    @Mock
    private IPackageManager miPm;

    @Mock
    NotificationManagerService mNm;
    @Mock
    private INotificationManager mINm;
    private TestableContext mContext = spy(getContext());

    NotificationManagerService.NotificationListeners mListeners;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        getContext().setMockPackageManager(mPm);
        doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());

        mListeners = spy(mNm.new NotificationListeners(
                mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm));
        when(mNm.getBinderService()).thenReturn(mINm);
    }

    @Test
    public void testNotifyPostedLockedInLockdownMode() {
        NotificationRecord r = mock(NotificationRecord.class);
        NotificationRecord old = mock(NotificationRecord.class);

        // before the lockdown mode
        when(mNm.isInLockDownMode()).thenReturn(false);
        mListeners.notifyPostedLocked(r, old, true);
        mListeners.notifyPostedLocked(r, old, false);
        verify(mListeners, times(2)).getServices();

        // in the lockdown mode
        reset(r);
        reset(old);
        reset(mListeners);
        when(mNm.isInLockDownMode()).thenReturn(true);
        mListeners.notifyPostedLocked(r, old, true);
        mListeners.notifyPostedLocked(r, old, false);
        verify(mListeners, never()).getServices();
    }

    @Test
    public void testnotifyRankingUpdateLockedInLockdownMode() {
        List chn = mock(List.class);

        // before the lockdown mode
        when(mNm.isInLockDownMode()).thenReturn(false);
        mListeners.notifyRankingUpdateLocked(chn);
        verify(chn, times(1)).size();

        // in the lockdown mode
        reset(chn);
        when(mNm.isInLockDownMode()).thenReturn(true);
        mListeners.notifyRankingUpdateLocked(chn);
        verify(chn, never()).size();
    }

    @Test
    public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException {
        StatusBarNotification sbn = mock(StatusBarNotification.class);
        NotificationRecord r = mock(NotificationRecord.class);
        NotificationStats rs = mock(NotificationStats.class);
        FieldSetter.setField(r,
                NotificationRecord.class.getDeclaredField("sbn"),
                sbn);
        FieldSetter.setField(mNm,
                NotificationManagerService.class.getDeclaredField("mHandler"),
                mock(NotificationManagerService.WorkerHandler.class));

        // before the lockdown mode
        when(mNm.isInLockDownMode()).thenReturn(false);
        mListeners.notifyRemovedLocked(r, 0, rs);
        mListeners.notifyRemovedLocked(r, 0, rs);
        verify(sbn, times(2)).cloneLight();

        // in the lockdown mode
        reset(sbn);
        reset(r);
        reset(rs);
        when(mNm.isInLockDownMode()).thenReturn(true);
        mListeners.notifyRemovedLocked(r, 0, rs);
        mListeners.notifyRemovedLocked(r, 0, rs);
        verify(sbn, never()).cloneLight();
    }
}
+64 −0
Original line number Diff line number Diff line
@@ -46,9 +46,12 @@ import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;

import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -317,7 +320,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
            // Tests for this not being true are in CTS NotificationManagerTest
            return true;
        }

        class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
            private int mGetStrongAuthForUserReturnValue = 0;
            StrongAuthTrackerFake(Context context) {
                super(context);
            }

            public void setGetStrongAuthForUserReturnValue(int val) {
                mGetStrongAuthForUserReturnValue = val;
            }

            @Override
            public int getStrongAuthForUser(int userId) {
                return mGetStrongAuthForUserReturnValue;
            }
        }
    }

    TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;

    private class TestableToastCallback extends ITransientNotification.Stub {
        @Override
@@ -405,6 +426,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {

        mService.setAudioManager(mAudioManager);

        mStrongAuthTracker = mService.new StrongAuthTrackerFake(mContext);
        mService.setStrongAuthTracker(mStrongAuthTracker);

        // Tests call directly into the Binder.
        mBinderService = mService.getBinderService();
        mInternalService = mService.getInternalService();
@@ -5372,4 +5396,44 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
        assertEquals(1, notifsAfter.length);
    }

    @Test
    public void testStrongAuthTracker_isInLockDownMode() {
        mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
        mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
        assertTrue(mStrongAuthTracker.isInLockDownMode());
        mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
        mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
        assertFalse(mStrongAuthTracker.isInLockDownMode());
    }

    @Test
    public void testCancelAndPostNotificationsWhenEnterAndExitLockDownMode() {
        // post 2 notifications from 2 packages
        NotificationRecord pkgA = new NotificationRecord(mContext,
                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
        mService.addNotification(pkgA);
        NotificationRecord pkgB = new NotificationRecord(mContext,
                generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
        mService.addNotification(pkgB);

        // when entering the lockdown mode, cancel the 2 notifications.
        mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
        mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
        assertTrue(mStrongAuthTracker.isInLockDownMode());

        // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN.
        ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
        verify(mListeners, times(2)).notifyRemovedLocked(any(), captor.capture(), any());
        assertEquals(REASON_CANCEL_ALL, captor.getValue().intValue());

        // exit lockdown mode.
        mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
        mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());

        // the notifyPostedLocked function is called twice.
        verify(mListeners, times(2)).notifyPostedLocked(any(), any());
    }
}