diff options
4 files changed, 303 insertions, 6 deletions
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1f943acb78fd..0dbc49e5907e 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -202,6 +202,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; @@ -227,6 +228,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; @@ -1460,6 +1462,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) { this(context, new InjectableSystemClockImpl()); } @@ -1480,6 +1530,11 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting + void setStrongAuthTracker(StrongAuthTracker strongAuthTracker) { + mStrongAuthTracker = strongAuthTracker; + } + + @VisibleForTesting void setKeyguardManager(KeyguardManager keyguardManager) { mKeyguardManager = keyguardManager; } @@ -1654,6 +1709,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); @@ -1801,7 +1858,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()), @@ -1944,6 +2002,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. @@ -7310,6 +7369,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(); @@ -7519,6 +7601,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(); @@ -8026,9 +8112,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 @@ -8137,8 +8223,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; @@ -8201,8 +8291,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 @@ -8253,6 +8346,10 @@ public class NotificationManagerService extends SystemService { */ @GuardedBy("mNotificationLock") public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) { + if (isInLockDownMode()) { + return; + } + boolean isHiddenRankingUpdate = changedHiddenNotifications != null && changedHiddenNotifications.size() > 0; diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index 7453c489ecc8..5b926e041b42 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -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" /> diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java new file mode 100644 index 000000000000..793739bfe8f5 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -0,0 +1,135 @@ +/* + * 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(); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index a3f0f3ee0391..200e83da3800 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -45,9 +45,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; @@ -343,8 +346,26 @@ 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 public void show(IBinder windowToken) { @@ -433,6 +454,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(); @@ -5384,4 +5408,44 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } } } + + @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()); + } } |