Loading services/core/java/com/android/server/notification/NotificationManagerService.java +44 −28 Original line number Original line Diff line number Diff line Loading @@ -2013,34 +2013,39 @@ public class NotificationManagerService extends SystemService { return (haystack & needle) != 0; return (haystack & needle) != 0; } } public boolean isInLockDownMode() { // Return whether the user is in lockdown mode. return mIsInLockDownMode; // If the flag is not set, we assume the user is not in lockdown. public boolean isInLockDownMode(int userId) { return mUserInLockDownMode.get(userId, false); } } @Override @Override public synchronized void onStrongAuthRequiredChanged(int userId) { public synchronized void onStrongAuthRequiredChanged(int userId) { boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId), boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId), STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mUserInLockDownMode.put(userId, userInLockDownModeNext); boolean isInLockDownModeNext = mUserInLockDownMode.indexOfValue(true) != -1; if (mIsInLockDownMode == isInLockDownModeNext) { // Nothing happens if the lockdown mode of userId keeps the same. if (userInLockDownModeNext == isInLockDownMode(userId)) { return; return; } } if (isInLockDownModeNext) { // When the lockdown mode is changed, we perform the following steps. cancelNotificationsWhenEnterLockDownMode(); // If the userInLockDownModeNext is true, all the function calls to // notifyPostedLocked and notifyRemovedLocked will not be executed. // The cancelNotificationsWhenEnterLockDownMode calls notifyRemovedLocked // and postNotificationsWhenExitLockDownMode calls notifyPostedLocked. // So we shall call cancelNotificationsWhenEnterLockDownMode before // we set mUserInLockDownMode as true. // On the other hand, if the userInLockDownModeNext is false, we shall call // postNotificationsWhenExitLockDownMode after we put false into mUserInLockDownMode if (userInLockDownModeNext) { cancelNotificationsWhenEnterLockDownMode(userId); } } // When the mIsInLockDownMode is true, both notifyPostedLocked and mUserInLockDownMode.put(userId, userInLockDownModeNext); // 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) { if (!userInLockDownModeNext) { postNotificationsWhenExitLockDownMode(); postNotificationsWhenExitLockDownMode(userId); } } } } } } Loading Loading @@ -9299,11 +9304,14 @@ public class NotificationManagerService extends SystemService { } } } } private void cancelNotificationsWhenEnterLockDownMode() { private void cancelNotificationsWhenEnterLockDownMode(int userId) { synchronized (mNotificationLock) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); NotificationRecord rec = mNotificationList.get(i); if (rec.getUser().getIdentifier() != userId) { continue; } mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL, mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL, rec.getStats()); rec.getStats()); } } Loading @@ -9311,14 +9319,23 @@ public class NotificationManagerService extends SystemService { } } } } private void postNotificationsWhenExitLockDownMode() { private void postNotificationsWhenExitLockDownMode(int userId) { synchronized (mNotificationLock) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); int numNotifications = mNotificationList.size(); // Set the delay to spread out the burst of notifications. long delay = 0; for (int i = 0; i < numNotifications; i++) { for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); NotificationRecord rec = mNotificationList.get(i); if (rec.getUser().getIdentifier() != userId) { continue; } mHandler.postDelayed(() -> { synchronized (mNotificationLock) { mListeners.notifyPostedLocked(rec, rec); mListeners.notifyPostedLocked(rec, rec); } } }, delay); delay += 20; } } } } } Loading Loading @@ -9510,12 +9527,15 @@ public class NotificationManagerService extends SystemService { * notifications visible to the given listener. * notifications visible to the given listener. */ */ @GuardedBy("mNotificationLock") @GuardedBy("mNotificationLock") private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { final int N = mNotificationList.size(); final int N = mNotificationList.size(); final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>(); final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>(); for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); NotificationRecord record = mNotificationList.get(i); if (isInLockDownMode(record.getUser().getIdentifier())) { continue; } if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) { if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) { continue; continue; } } Loading Loading @@ -9557,8 +9577,8 @@ public class NotificationManagerService extends SystemService { rankings.toArray(new NotificationListenerService.Ranking[0])); rankings.toArray(new NotificationListenerService.Ranking[0])); } } boolean isInLockDownMode() { boolean isInLockDownMode(int userId) { return mStrongAuthTracker.isInLockDownMode(); return mStrongAuthTracker.isInLockDownMode(userId); } } boolean hasCompanionDevice(ManagedServiceInfo info) { boolean hasCompanionDevice(ManagedServiceInfo info) { Loading Loading @@ -10614,7 +10634,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") @GuardedBy("mNotificationLock") void notifyPostedLocked(NotificationRecord r, NotificationRecord old, void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { boolean notifyAllListeners) { if (isInLockDownMode()) { if (isInLockDownMode(r.getUser().getIdentifier())) { return; return; } } Loading Loading @@ -10715,7 +10735,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") @GuardedBy("mNotificationLock") public void notifyRemovedLocked(NotificationRecord r, int reason, public void notifyRemovedLocked(NotificationRecord r, int reason, NotificationStats notificationStats) { NotificationStats notificationStats) { if (isInLockDownMode()) { if (isInLockDownMode(r.getUser().getIdentifier())) { return; return; } } Loading Loading @@ -10764,10 +10784,6 @@ public class NotificationManagerService extends SystemService { */ */ @GuardedBy("mNotificationLock") @GuardedBy("mNotificationLock") public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) { public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) { if (isInLockDownMode()) { return; } boolean isHiddenRankingUpdate = changedHiddenNotifications != null boolean isHiddenRankingUpdate = changedHiddenNotifications != null && changedHiddenNotifications.size() > 0; && changedHiddenNotifications.size() > 0; // TODO (b/73052211): if the ranking update changed the notification type, // TODO (b/73052211): if the ranking update changed the notification type, Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +99 −50 Original line number Original line Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo; import android.content.pm.VersionedPackage; import android.content.pm.VersionedPackage; import android.os.Bundle; import android.os.Bundle; import android.os.UserHandle; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.NotificationStats; Loading @@ -61,7 +62,6 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream; import java.util.List; public class NotificationListenersTest extends UiServiceTestCase { public class NotificationListenersTest extends UiServiceTestCase { Loading Loading @@ -362,63 +362,112 @@ public class NotificationListenersTest extends UiServiceTestCase { @Test @Test public void testNotifyPostedLockedInLockdownMode() { public void testNotifyPostedLockedInLockdownMode() { NotificationRecord r = mock(NotificationRecord.class); NotificationRecord r0 = mock(NotificationRecord.class); NotificationRecord old = mock(NotificationRecord.class); NotificationRecord old0 = mock(NotificationRecord.class); UserHandle uh0 = mock(UserHandle.class); // before the lockdown mode when(mNm.isInLockDownMode()).thenReturn(false); NotificationRecord r1 = mock(NotificationRecord.class); mListeners.notifyPostedLocked(r, old, true); NotificationRecord old1 = mock(NotificationRecord.class); mListeners.notifyPostedLocked(r, old, false); UserHandle uh1 = mock(UserHandle.class); verify(r, atLeast(2)).getSbn(); // Neither user0 and user1 is in the lockdown mode // in the lockdown mode when(r0.getUser()).thenReturn(uh0); reset(r); when(uh0.getIdentifier()).thenReturn(0); reset(old); when(mNm.isInLockDownMode(0)).thenReturn(false); when(mNm.isInLockDownMode()).thenReturn(true); mListeners.notifyPostedLocked(r, old, true); when(r1.getUser()).thenReturn(uh1); mListeners.notifyPostedLocked(r, old, false); when(uh1.getIdentifier()).thenReturn(1); verify(r, never()).getSbn(); when(mNm.isInLockDownMode(1)).thenReturn(false); } mListeners.notifyPostedLocked(r0, old0, true); @Test mListeners.notifyPostedLocked(r0, old0, false); public void testnotifyRankingUpdateLockedInLockdownMode() { verify(r0, atLeast(2)).getSbn(); List chn = mock(List.class); mListeners.notifyPostedLocked(r1, old1, true); // before the lockdown mode mListeners.notifyPostedLocked(r1, old1, false); when(mNm.isInLockDownMode()).thenReturn(false); verify(r1, atLeast(2)).getSbn(); mListeners.notifyRankingUpdateLocked(chn); verify(chn, atLeast(1)).size(); // Reset reset(r0); // in the lockdown mode reset(old0); reset(chn); reset(r1); when(mNm.isInLockDownMode()).thenReturn(true); reset(old1); mListeners.notifyRankingUpdateLocked(chn); verify(chn, never()).size(); // Only user 0 is in the lockdown mode when(r0.getUser()).thenReturn(uh0); when(uh0.getIdentifier()).thenReturn(0); when(mNm.isInLockDownMode(0)).thenReturn(true); when(r1.getUser()).thenReturn(uh1); when(uh1.getIdentifier()).thenReturn(1); when(mNm.isInLockDownMode(1)).thenReturn(false); mListeners.notifyPostedLocked(r0, old0, true); mListeners.notifyPostedLocked(r0, old0, false); verify(r0, never()).getSbn(); mListeners.notifyPostedLocked(r1, old1, true); mListeners.notifyPostedLocked(r1, old1, false); verify(r1, atLeast(2)).getSbn(); } } @Test @Test public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { NotificationRecord r = mock(NotificationRecord.class); NotificationRecord r0 = mock(NotificationRecord.class); NotificationStats rs = mock(NotificationStats.class); NotificationStats rs0 = mock(NotificationStats.class); UserHandle uh0 = mock(UserHandle.class); NotificationRecord r1 = mock(NotificationRecord.class); NotificationStats rs1 = mock(NotificationStats.class); UserHandle uh1 = mock(UserHandle.class); StatusBarNotification sbn = mock(StatusBarNotification.class); StatusBarNotification sbn = mock(StatusBarNotification.class); FieldSetter.setField(mNm, FieldSetter.setField(mNm, NotificationManagerService.class.getDeclaredField("mHandler"), NotificationManagerService.class.getDeclaredField("mHandler"), mock(NotificationManagerService.WorkerHandler.class)); mock(NotificationManagerService.WorkerHandler.class)); // before the lockdown mode // Neither user0 and user1 is in the lockdown mode when(mNm.isInLockDownMode()).thenReturn(false); when(r0.getUser()).thenReturn(uh0); when(r.getSbn()).thenReturn(sbn); when(uh0.getIdentifier()).thenReturn(0); mListeners.notifyRemovedLocked(r, 0, rs); when(mNm.isInLockDownMode(0)).thenReturn(false); mListeners.notifyRemovedLocked(r, 0, rs); when(r0.getSbn()).thenReturn(sbn); verify(r, atLeast(2)).getSbn(); when(r1.getUser()).thenReturn(uh1); // in the lockdown mode when(uh1.getIdentifier()).thenReturn(1); reset(r); when(mNm.isInLockDownMode(1)).thenReturn(false); reset(rs); when(r1.getSbn()).thenReturn(sbn); when(mNm.isInLockDownMode()).thenReturn(true); when(r.getSbn()).thenReturn(sbn); mListeners.notifyRemovedLocked(r0, 0, rs0); mListeners.notifyRemovedLocked(r, 0, rs); mListeners.notifyRemovedLocked(r0, 0, rs0); mListeners.notifyRemovedLocked(r, 0, rs); verify(r0, atLeast(2)).getSbn(); verify(r, never()).getSbn(); mListeners.notifyRemovedLocked(r1, 0, rs1); mListeners.notifyRemovedLocked(r1, 0, rs1); verify(r1, atLeast(2)).getSbn(); // Reset reset(r0); reset(rs0); reset(r1); reset(rs1); // Only user 0 is in the lockdown mode when(r0.getUser()).thenReturn(uh0); when(uh0.getIdentifier()).thenReturn(0); when(mNm.isInLockDownMode(0)).thenReturn(true); when(r0.getSbn()).thenReturn(sbn); when(r1.getUser()).thenReturn(uh1); when(uh1.getIdentifier()).thenReturn(1); when(mNm.isInLockDownMode(1)).thenReturn(false); when(r1.getSbn()).thenReturn(sbn); mListeners.notifyRemovedLocked(r0, 0, rs0); mListeners.notifyRemovedLocked(r0, 0, rs0); verify(r0, never()).getSbn(); mListeners.notifyRemovedLocked(r1, 0, rs1); mListeners.notifyRemovedLocked(r1, 0, rs1); verify(r1, atLeast(2)).getSbn(); } } } } services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +61 −7 Original line number Original line Diff line number Diff line Loading @@ -160,6 +160,7 @@ import android.service.notification.Adjustment; import android.service.notification.ConversationChannelWrapper; import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationStats; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification; import android.service.notification.ZenPolicy; import android.service.notification.ZenPolicy; Loading Loading @@ -195,6 +196,7 @@ import com.android.server.SystemService.TargetUser; import com.android.server.UiServiceTestCase; import com.android.server.UiServiceTestCase; import com.android.server.lights.LightsManager; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.lights.LogicalLight; import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.pm.PackageManagerService; import com.android.server.pm.PackageManagerService; Loading Loading @@ -345,6 +347,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Nullable @Nullable NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; @Nullable Boolean mIsVisibleToListenerReturnValue = null; TestableNotificationManagerService(Context context, NotificationRecordLogger logger, TestableNotificationManagerService(Context context, NotificationRecordLogger logger, InstanceIdSequence notificationInstanceIdSequence) { InstanceIdSequence notificationInstanceIdSequence) { super(context, logger, notificationInstanceIdSequence); super(context, logger, notificationInstanceIdSequence); Loading Loading @@ -413,6 +418,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet); void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet); } } protected void setIsVisibleToListenerReturnValue(boolean value) { mIsVisibleToListenerReturnValue = value; } @Override boolean isVisibleToListener(StatusBarNotification sbn, int notificationType, ManagedServiceInfo listener) { if (mIsVisibleToListenerReturnValue != null) { return mIsVisibleToListenerReturnValue; } return super.isVisibleToListener(sbn, notificationType, listener); } class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { private int mGetStrongAuthForUserReturnValue = 0; private int mGetStrongAuthForUserReturnValue = 0; StrongAuthTrackerFake(Context context) { StrongAuthTrackerFake(Context context) { Loading Loading @@ -8549,10 +8567,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mStrongAuthTracker.setGetStrongAuthForUserReturnValue( mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); assertTrue(mStrongAuthTracker.isInLockDownMode()); assertTrue(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); mStrongAuthTracker.setGetStrongAuthForUserReturnValue(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); assertFalse(mStrongAuthTracker.isInLockDownMode()); assertFalse(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); } } @Test @Test Loading @@ -8568,8 +8586,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // when entering the lockdown mode, cancel the 2 notifications. // when entering the lockdown mode, cancel the 2 notifications. mStrongAuthTracker.setGetStrongAuthForUserReturnValue( mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(0); assertTrue(mStrongAuthTracker.isInLockDownMode()); assertTrue(mStrongAuthTracker.isInLockDownMode(0)); // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN. // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN. ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class); Loading @@ -8578,9 +8596,45 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // exit lockdown mode. // exit lockdown mode. mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(0); assertFalse(mStrongAuthTracker.isInLockDownMode(0)); // the notifyPostedLocked function is called twice. // the notifyPostedLocked function is called twice. verify(mListeners, times(2)).notifyPostedLocked(any(), any()); verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong()); //verify(mListeners, times(2)).notifyPostedLocked(any(), any()); } @Test public void testMakeRankingUpdateLockedInLockDownMode() { // post 2 notifications from a same package NotificationRecord pkgA = new NotificationRecord(mContext, generateSbn("a", 1000, 9, 0), mTestNotificationChannel); mService.addNotification(pkgA); NotificationRecord pkgB = new NotificationRecord(mContext, generateSbn("a", 1000, 9, 1), mTestNotificationChannel); mService.addNotification(pkgB); mService.setIsVisibleToListenerReturnValue(true); NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(null); assertEquals(2, nru.getRankingMap().getOrderedKeys().length); // when only user 0 entering the lockdown mode, its notification will be suppressed. mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(0); assertTrue(mStrongAuthTracker.isInLockDownMode(0)); assertFalse(mStrongAuthTracker.isInLockDownMode(1)); nru = mService.makeRankingUpdateLocked(null); assertEquals(1, nru.getRankingMap().getOrderedKeys().length); // User 0 exits lockdown mode. Its notification will be resumed. mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); mStrongAuthTracker.onStrongAuthRequiredChanged(0); assertFalse(mStrongAuthTracker.isInLockDownMode(0)); assertFalse(mStrongAuthTracker.isInLockDownMode(1)); nru = mService.makeRankingUpdateLocked(null); assertEquals(2, nru.getRankingMap().getOrderedKeys().length); } } } } Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +44 −28 Original line number Original line Diff line number Diff line Loading @@ -2013,34 +2013,39 @@ public class NotificationManagerService extends SystemService { return (haystack & needle) != 0; return (haystack & needle) != 0; } } public boolean isInLockDownMode() { // Return whether the user is in lockdown mode. return mIsInLockDownMode; // If the flag is not set, we assume the user is not in lockdown. public boolean isInLockDownMode(int userId) { return mUserInLockDownMode.get(userId, false); } } @Override @Override public synchronized void onStrongAuthRequiredChanged(int userId) { public synchronized void onStrongAuthRequiredChanged(int userId) { boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId), boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId), STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mUserInLockDownMode.put(userId, userInLockDownModeNext); boolean isInLockDownModeNext = mUserInLockDownMode.indexOfValue(true) != -1; if (mIsInLockDownMode == isInLockDownModeNext) { // Nothing happens if the lockdown mode of userId keeps the same. if (userInLockDownModeNext == isInLockDownMode(userId)) { return; return; } } if (isInLockDownModeNext) { // When the lockdown mode is changed, we perform the following steps. cancelNotificationsWhenEnterLockDownMode(); // If the userInLockDownModeNext is true, all the function calls to // notifyPostedLocked and notifyRemovedLocked will not be executed. // The cancelNotificationsWhenEnterLockDownMode calls notifyRemovedLocked // and postNotificationsWhenExitLockDownMode calls notifyPostedLocked. // So we shall call cancelNotificationsWhenEnterLockDownMode before // we set mUserInLockDownMode as true. // On the other hand, if the userInLockDownModeNext is false, we shall call // postNotificationsWhenExitLockDownMode after we put false into mUserInLockDownMode if (userInLockDownModeNext) { cancelNotificationsWhenEnterLockDownMode(userId); } } // When the mIsInLockDownMode is true, both notifyPostedLocked and mUserInLockDownMode.put(userId, userInLockDownModeNext); // 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) { if (!userInLockDownModeNext) { postNotificationsWhenExitLockDownMode(); postNotificationsWhenExitLockDownMode(userId); } } } } } } Loading Loading @@ -9299,11 +9304,14 @@ public class NotificationManagerService extends SystemService { } } } } private void cancelNotificationsWhenEnterLockDownMode() { private void cancelNotificationsWhenEnterLockDownMode(int userId) { synchronized (mNotificationLock) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); NotificationRecord rec = mNotificationList.get(i); if (rec.getUser().getIdentifier() != userId) { continue; } mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL, mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL, rec.getStats()); rec.getStats()); } } Loading @@ -9311,14 +9319,23 @@ public class NotificationManagerService extends SystemService { } } } } private void postNotificationsWhenExitLockDownMode() { private void postNotificationsWhenExitLockDownMode(int userId) { synchronized (mNotificationLock) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); int numNotifications = mNotificationList.size(); // Set the delay to spread out the burst of notifications. long delay = 0; for (int i = 0; i < numNotifications; i++) { for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); NotificationRecord rec = mNotificationList.get(i); if (rec.getUser().getIdentifier() != userId) { continue; } mHandler.postDelayed(() -> { synchronized (mNotificationLock) { mListeners.notifyPostedLocked(rec, rec); mListeners.notifyPostedLocked(rec, rec); } } }, delay); delay += 20; } } } } } Loading Loading @@ -9510,12 +9527,15 @@ public class NotificationManagerService extends SystemService { * notifications visible to the given listener. * notifications visible to the given listener. */ */ @GuardedBy("mNotificationLock") @GuardedBy("mNotificationLock") private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { final int N = mNotificationList.size(); final int N = mNotificationList.size(); final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>(); final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>(); for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); NotificationRecord record = mNotificationList.get(i); if (isInLockDownMode(record.getUser().getIdentifier())) { continue; } if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) { if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) { continue; continue; } } Loading Loading @@ -9557,8 +9577,8 @@ public class NotificationManagerService extends SystemService { rankings.toArray(new NotificationListenerService.Ranking[0])); rankings.toArray(new NotificationListenerService.Ranking[0])); } } boolean isInLockDownMode() { boolean isInLockDownMode(int userId) { return mStrongAuthTracker.isInLockDownMode(); return mStrongAuthTracker.isInLockDownMode(userId); } } boolean hasCompanionDevice(ManagedServiceInfo info) { boolean hasCompanionDevice(ManagedServiceInfo info) { Loading Loading @@ -10614,7 +10634,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") @GuardedBy("mNotificationLock") void notifyPostedLocked(NotificationRecord r, NotificationRecord old, void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { boolean notifyAllListeners) { if (isInLockDownMode()) { if (isInLockDownMode(r.getUser().getIdentifier())) { return; return; } } Loading Loading @@ -10715,7 +10735,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") @GuardedBy("mNotificationLock") public void notifyRemovedLocked(NotificationRecord r, int reason, public void notifyRemovedLocked(NotificationRecord r, int reason, NotificationStats notificationStats) { NotificationStats notificationStats) { if (isInLockDownMode()) { if (isInLockDownMode(r.getUser().getIdentifier())) { return; return; } } Loading Loading @@ -10764,10 +10784,6 @@ public class NotificationManagerService extends SystemService { */ */ @GuardedBy("mNotificationLock") @GuardedBy("mNotificationLock") public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) { public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) { if (isInLockDownMode()) { return; } boolean isHiddenRankingUpdate = changedHiddenNotifications != null boolean isHiddenRankingUpdate = changedHiddenNotifications != null && changedHiddenNotifications.size() > 0; && changedHiddenNotifications.size() > 0; // TODO (b/73052211): if the ranking update changed the notification type, // TODO (b/73052211): if the ranking update changed the notification type, Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +99 −50 Original line number Original line Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo; import android.content.pm.VersionedPackage; import android.content.pm.VersionedPackage; import android.os.Bundle; import android.os.Bundle; import android.os.UserHandle; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.NotificationStats; Loading @@ -61,7 +62,6 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream; import java.util.List; public class NotificationListenersTest extends UiServiceTestCase { public class NotificationListenersTest extends UiServiceTestCase { Loading Loading @@ -362,63 +362,112 @@ public class NotificationListenersTest extends UiServiceTestCase { @Test @Test public void testNotifyPostedLockedInLockdownMode() { public void testNotifyPostedLockedInLockdownMode() { NotificationRecord r = mock(NotificationRecord.class); NotificationRecord r0 = mock(NotificationRecord.class); NotificationRecord old = mock(NotificationRecord.class); NotificationRecord old0 = mock(NotificationRecord.class); UserHandle uh0 = mock(UserHandle.class); // before the lockdown mode when(mNm.isInLockDownMode()).thenReturn(false); NotificationRecord r1 = mock(NotificationRecord.class); mListeners.notifyPostedLocked(r, old, true); NotificationRecord old1 = mock(NotificationRecord.class); mListeners.notifyPostedLocked(r, old, false); UserHandle uh1 = mock(UserHandle.class); verify(r, atLeast(2)).getSbn(); // Neither user0 and user1 is in the lockdown mode // in the lockdown mode when(r0.getUser()).thenReturn(uh0); reset(r); when(uh0.getIdentifier()).thenReturn(0); reset(old); when(mNm.isInLockDownMode(0)).thenReturn(false); when(mNm.isInLockDownMode()).thenReturn(true); mListeners.notifyPostedLocked(r, old, true); when(r1.getUser()).thenReturn(uh1); mListeners.notifyPostedLocked(r, old, false); when(uh1.getIdentifier()).thenReturn(1); verify(r, never()).getSbn(); when(mNm.isInLockDownMode(1)).thenReturn(false); } mListeners.notifyPostedLocked(r0, old0, true); @Test mListeners.notifyPostedLocked(r0, old0, false); public void testnotifyRankingUpdateLockedInLockdownMode() { verify(r0, atLeast(2)).getSbn(); List chn = mock(List.class); mListeners.notifyPostedLocked(r1, old1, true); // before the lockdown mode mListeners.notifyPostedLocked(r1, old1, false); when(mNm.isInLockDownMode()).thenReturn(false); verify(r1, atLeast(2)).getSbn(); mListeners.notifyRankingUpdateLocked(chn); verify(chn, atLeast(1)).size(); // Reset reset(r0); // in the lockdown mode reset(old0); reset(chn); reset(r1); when(mNm.isInLockDownMode()).thenReturn(true); reset(old1); mListeners.notifyRankingUpdateLocked(chn); verify(chn, never()).size(); // Only user 0 is in the lockdown mode when(r0.getUser()).thenReturn(uh0); when(uh0.getIdentifier()).thenReturn(0); when(mNm.isInLockDownMode(0)).thenReturn(true); when(r1.getUser()).thenReturn(uh1); when(uh1.getIdentifier()).thenReturn(1); when(mNm.isInLockDownMode(1)).thenReturn(false); mListeners.notifyPostedLocked(r0, old0, true); mListeners.notifyPostedLocked(r0, old0, false); verify(r0, never()).getSbn(); mListeners.notifyPostedLocked(r1, old1, true); mListeners.notifyPostedLocked(r1, old1, false); verify(r1, atLeast(2)).getSbn(); } } @Test @Test public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { NotificationRecord r = mock(NotificationRecord.class); NotificationRecord r0 = mock(NotificationRecord.class); NotificationStats rs = mock(NotificationStats.class); NotificationStats rs0 = mock(NotificationStats.class); UserHandle uh0 = mock(UserHandle.class); NotificationRecord r1 = mock(NotificationRecord.class); NotificationStats rs1 = mock(NotificationStats.class); UserHandle uh1 = mock(UserHandle.class); StatusBarNotification sbn = mock(StatusBarNotification.class); StatusBarNotification sbn = mock(StatusBarNotification.class); FieldSetter.setField(mNm, FieldSetter.setField(mNm, NotificationManagerService.class.getDeclaredField("mHandler"), NotificationManagerService.class.getDeclaredField("mHandler"), mock(NotificationManagerService.WorkerHandler.class)); mock(NotificationManagerService.WorkerHandler.class)); // before the lockdown mode // Neither user0 and user1 is in the lockdown mode when(mNm.isInLockDownMode()).thenReturn(false); when(r0.getUser()).thenReturn(uh0); when(r.getSbn()).thenReturn(sbn); when(uh0.getIdentifier()).thenReturn(0); mListeners.notifyRemovedLocked(r, 0, rs); when(mNm.isInLockDownMode(0)).thenReturn(false); mListeners.notifyRemovedLocked(r, 0, rs); when(r0.getSbn()).thenReturn(sbn); verify(r, atLeast(2)).getSbn(); when(r1.getUser()).thenReturn(uh1); // in the lockdown mode when(uh1.getIdentifier()).thenReturn(1); reset(r); when(mNm.isInLockDownMode(1)).thenReturn(false); reset(rs); when(r1.getSbn()).thenReturn(sbn); when(mNm.isInLockDownMode()).thenReturn(true); when(r.getSbn()).thenReturn(sbn); mListeners.notifyRemovedLocked(r0, 0, rs0); mListeners.notifyRemovedLocked(r, 0, rs); mListeners.notifyRemovedLocked(r0, 0, rs0); mListeners.notifyRemovedLocked(r, 0, rs); verify(r0, atLeast(2)).getSbn(); verify(r, never()).getSbn(); mListeners.notifyRemovedLocked(r1, 0, rs1); mListeners.notifyRemovedLocked(r1, 0, rs1); verify(r1, atLeast(2)).getSbn(); // Reset reset(r0); reset(rs0); reset(r1); reset(rs1); // Only user 0 is in the lockdown mode when(r0.getUser()).thenReturn(uh0); when(uh0.getIdentifier()).thenReturn(0); when(mNm.isInLockDownMode(0)).thenReturn(true); when(r0.getSbn()).thenReturn(sbn); when(r1.getUser()).thenReturn(uh1); when(uh1.getIdentifier()).thenReturn(1); when(mNm.isInLockDownMode(1)).thenReturn(false); when(r1.getSbn()).thenReturn(sbn); mListeners.notifyRemovedLocked(r0, 0, rs0); mListeners.notifyRemovedLocked(r0, 0, rs0); verify(r0, never()).getSbn(); mListeners.notifyRemovedLocked(r1, 0, rs1); mListeners.notifyRemovedLocked(r1, 0, rs1); verify(r1, atLeast(2)).getSbn(); } } } }
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +61 −7 Original line number Original line Diff line number Diff line Loading @@ -160,6 +160,7 @@ import android.service.notification.Adjustment; import android.service.notification.ConversationChannelWrapper; import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationStats; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification; import android.service.notification.ZenPolicy; import android.service.notification.ZenPolicy; Loading Loading @@ -195,6 +196,7 @@ import com.android.server.SystemService.TargetUser; import com.android.server.UiServiceTestCase; import com.android.server.UiServiceTestCase; import com.android.server.lights.LightsManager; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.lights.LogicalLight; import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.pm.PackageManagerService; import com.android.server.pm.PackageManagerService; Loading Loading @@ -345,6 +347,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Nullable @Nullable NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; @Nullable Boolean mIsVisibleToListenerReturnValue = null; TestableNotificationManagerService(Context context, NotificationRecordLogger logger, TestableNotificationManagerService(Context context, NotificationRecordLogger logger, InstanceIdSequence notificationInstanceIdSequence) { InstanceIdSequence notificationInstanceIdSequence) { super(context, logger, notificationInstanceIdSequence); super(context, logger, notificationInstanceIdSequence); Loading Loading @@ -413,6 +418,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet); void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet); } } protected void setIsVisibleToListenerReturnValue(boolean value) { mIsVisibleToListenerReturnValue = value; } @Override boolean isVisibleToListener(StatusBarNotification sbn, int notificationType, ManagedServiceInfo listener) { if (mIsVisibleToListenerReturnValue != null) { return mIsVisibleToListenerReturnValue; } return super.isVisibleToListener(sbn, notificationType, listener); } class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { private int mGetStrongAuthForUserReturnValue = 0; private int mGetStrongAuthForUserReturnValue = 0; StrongAuthTrackerFake(Context context) { StrongAuthTrackerFake(Context context) { Loading Loading @@ -8549,10 +8567,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mStrongAuthTracker.setGetStrongAuthForUserReturnValue( mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); assertTrue(mStrongAuthTracker.isInLockDownMode()); assertTrue(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); mStrongAuthTracker.setGetStrongAuthForUserReturnValue(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); assertFalse(mStrongAuthTracker.isInLockDownMode()); assertFalse(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); } } @Test @Test Loading @@ -8568,8 +8586,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // when entering the lockdown mode, cancel the 2 notifications. // when entering the lockdown mode, cancel the 2 notifications. mStrongAuthTracker.setGetStrongAuthForUserReturnValue( mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(0); assertTrue(mStrongAuthTracker.isInLockDownMode()); assertTrue(mStrongAuthTracker.isInLockDownMode(0)); // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN. // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN. ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class); Loading @@ -8578,9 +8596,45 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // exit lockdown mode. // exit lockdown mode. mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(0); assertFalse(mStrongAuthTracker.isInLockDownMode(0)); // the notifyPostedLocked function is called twice. // the notifyPostedLocked function is called twice. verify(mListeners, times(2)).notifyPostedLocked(any(), any()); verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong()); //verify(mListeners, times(2)).notifyPostedLocked(any(), any()); } @Test public void testMakeRankingUpdateLockedInLockDownMode() { // post 2 notifications from a same package NotificationRecord pkgA = new NotificationRecord(mContext, generateSbn("a", 1000, 9, 0), mTestNotificationChannel); mService.addNotification(pkgA); NotificationRecord pkgB = new NotificationRecord(mContext, generateSbn("a", 1000, 9, 1), mTestNotificationChannel); mService.addNotification(pkgB); mService.setIsVisibleToListenerReturnValue(true); NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(null); assertEquals(2, nru.getRankingMap().getOrderedKeys().length); // when only user 0 entering the lockdown mode, its notification will be suppressed. mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(0); assertTrue(mStrongAuthTracker.isInLockDownMode(0)); assertFalse(mStrongAuthTracker.isInLockDownMode(1)); nru = mService.makeRankingUpdateLocked(null); assertEquals(1, nru.getRankingMap().getOrderedKeys().length); // User 0 exits lockdown mode. Its notification will be resumed. mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); mStrongAuthTracker.onStrongAuthRequiredChanged(0); assertFalse(mStrongAuthTracker.isInLockDownMode(0)); assertFalse(mStrongAuthTracker.isInLockDownMode(1)); nru = mService.makeRankingUpdateLocked(null); assertEquals(2, nru.getRankingMap().getOrderedKeys().length); } } } }