Merge "Enable user graularity for lockdown mode" into qt-dev

This commit is contained in:
TreeHugger Robot
2022-11-01 21:54:57 +00:00
committed by Android (Google) Code Review
3 changed files with 217 additions and 84 deletions

View File

@@ -1475,34 +1475,39 @@ public class NotificationManagerService extends SystemService {
return (haystack & needle) != 0;
}
public boolean isInLockDownMode() {
return mIsInLockDownMode;
// Return whether the user is in lockdown mode.
// 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
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) {
// Nothing happens if the lockdown mode of userId keeps the same.
if (userInLockDownModeNext == isInLockDownMode(userId)) {
return;
}
if (isInLockDownModeNext) {
cancelNotificationsWhenEnterLockDownMode();
// When the lockdown mode is changed, we perform the following steps.
// 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
// 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;
mUserInLockDownMode.put(userId, userInLockDownModeNext);
if (!isInLockDownModeNext) {
postNotificationsWhenExitLockDownMode();
if (!userInLockDownModeNext) {
postNotificationsWhenExitLockDownMode(userId);
}
}
}
@@ -7398,11 +7403,14 @@ public class NotificationManagerService extends SystemService {
}
}
private void cancelNotificationsWhenEnterLockDownMode() {
private void cancelNotificationsWhenEnterLockDownMode(int userId) {
synchronized (mNotificationLock) {
int numNotifications = mNotificationList.size();
for (int i = 0; i < numNotifications; i++) {
NotificationRecord rec = mNotificationList.get(i);
if (rec.getUser().getIdentifier() != userId) {
continue;
}
mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL,
rec.getStats());
}
@@ -7410,14 +7418,23 @@ public class NotificationManagerService extends SystemService {
}
}
private void postNotificationsWhenExitLockDownMode() {
private void postNotificationsWhenExitLockDownMode(int userId) {
synchronized (mNotificationLock) {
int numNotifications = mNotificationList.size();
// Set the delay to spread out the burst of notifications.
long delay = 0;
for (int i = 0; i < numNotifications; i++) {
NotificationRecord rec = mNotificationList.get(i);
mListeners.notifyPostedLocked(rec, rec);
if (rec.getUser().getIdentifier() != userId) {
continue;
}
mHandler.postDelayed(() -> {
synchronized (mNotificationLock) {
mListeners.notifyPostedLocked(rec, rec);
}
}, delay);
delay += 20;
}
}
}
@@ -7590,12 +7607,15 @@ public class NotificationManagerService extends SystemService {
* notifications visible to the given listener.
*/
@GuardedBy("mNotificationLock")
private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) {
NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) {
final int N = mNotificationList.size();
final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (isInLockDownMode(record.getUser().getIdentifier())) {
continue;
}
if (!isVisibleToListener(record.sbn, info)) {
continue;
}
@@ -7630,8 +7650,8 @@ public class NotificationManagerService extends SystemService {
rankings.toArray(new NotificationListenerService.Ranking[0]));
}
boolean isInLockDownMode() {
return mStrongAuthTracker.isInLockDownMode();
boolean isInLockDownMode(int userId) {
return mStrongAuthTracker.isInLockDownMode(userId);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
@@ -7666,7 +7686,8 @@ public class NotificationManagerService extends SystemService {
ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
}
private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
@VisibleForTesting
boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
if (!listener.enabledAndUserMatches(sbn.getUserId())) {
return false;
}
@@ -8254,7 +8275,7 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
boolean notifyAllListeners) {
if (isInLockDownMode()) {
if (isInLockDownMode(r.getUser().getIdentifier())) {
return;
}
@@ -8320,7 +8341,7 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
public void notifyRemovedLocked(NotificationRecord r, int reason,
NotificationStats notificationStats) {
if (isInLockDownMode()) {
if (isInLockDownMode(r.getUser().getIdentifier())) {
return;
}
@@ -8375,10 +8396,6 @@ public class NotificationManagerService extends SystemService {
*/
@GuardedBy("mNotificationLock")
public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
if (isInLockDownMode()) {
return;
}
boolean isHiddenRankingUpdate = changedHiddenNotifications != null
&& changedHiddenNotifications.size() > 0;

View File

@@ -28,6 +28,7 @@ import static org.mockito.Mockito.when;
import android.app.INotificationManager;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.testing.TestableContext;
@@ -40,8 +41,6 @@ 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
@@ -70,66 +69,127 @@ public class NotificationListenersTest extends UiServiceTestCase {
@Test
public void testNotifyPostedLockedInLockdownMode() {
NotificationRecord r = mock(NotificationRecord.class);
NotificationRecord old = mock(NotificationRecord.class);
NotificationRecord r0 = mock(NotificationRecord.class);
NotificationRecord old0 = mock(NotificationRecord.class);
UserHandle uh0 = mock(UserHandle.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();
NotificationRecord r1 = mock(NotificationRecord.class);
NotificationRecord old1 = mock(NotificationRecord.class);
UserHandle uh1 = mock(UserHandle.class);
// in the lockdown mode
reset(r);
reset(old);
// Neither user0 and user1 is in the lockdown mode
when(r0.getUser()).thenReturn(uh0);
when(uh0.getIdentifier()).thenReturn(0);
when(mNm.isInLockDownMode(0)).thenReturn(false);
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);
mListeners.notifyPostedLocked(r1, old1, true);
mListeners.notifyPostedLocked(r1, old1, false);
verify(mListeners, times(4)).getServices();
// Reset
reset(r0);
reset(old0);
reset(r1);
reset(old1);
reset(mListeners);
when(mNm.isInLockDownMode()).thenReturn(true);
mListeners.notifyPostedLocked(r, old, true);
mListeners.notifyPostedLocked(r, old, false);
// 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(mListeners, never()).getServices();
}
@Test
public void testnotifyRankingUpdateLockedInLockdownMode() {
List chn = mock(List.class);
mListeners.notifyPostedLocked(r1, old1, true);
mListeners.notifyPostedLocked(r1, old1, false);
// 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();
verify(mListeners, times(2)).getServices();
}
@Test
public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException {
StatusBarNotification sbn = mock(StatusBarNotification.class);
NotificationRecord r = mock(NotificationRecord.class);
NotificationStats rs = mock(NotificationStats.class);
FieldSetter.setField(r,
StatusBarNotification sbn0 = mock(StatusBarNotification.class);
NotificationRecord r0 = mock(NotificationRecord.class);
NotificationStats rs0 = mock(NotificationStats.class);
UserHandle uh0 = mock(UserHandle.class);
FieldSetter.setField(r0,
NotificationRecord.class.getDeclaredField("sbn"),
sbn);
sbn0);
StatusBarNotification sbn1 = mock(StatusBarNotification.class);
NotificationRecord r1 = mock(NotificationRecord.class);
NotificationStats rs1 = mock(NotificationStats.class);
UserHandle uh1 = mock(UserHandle.class);
FieldSetter.setField(r1,
NotificationRecord.class.getDeclaredField("sbn"),
sbn1);
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();
// Neither user0 and user1 is in the lockdown mode
when(r0.getUser()).thenReturn(uh0);
when(uh0.getIdentifier()).thenReturn(0);
when(mNm.isInLockDownMode(0)).thenReturn(false);
// 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();
when(r1.getUser()).thenReturn(uh1);
when(uh1.getIdentifier()).thenReturn(1);
when(mNm.isInLockDownMode(1)).thenReturn(false);
mListeners.notifyRemovedLocked(r0, 0, rs0);
mListeners.notifyRemovedLocked(r0, 0, rs0);
verify(sbn0, times(2)).cloneLight();
mListeners.notifyRemovedLocked(r1, 0, rs1);
mListeners.notifyRemovedLocked(r1, 0, rs1);
verify(sbn1, times(2)).cloneLight();
// Reset
reset(sbn0);
reset(r0);
reset(rs0);
reset(sbn1);
reset(r1);
reset(rs1);
FieldSetter.setField(r0,
NotificationRecord.class.getDeclaredField("sbn"),
sbn0);
FieldSetter.setField(r1,
NotificationRecord.class.getDeclaredField("sbn"),
sbn1);
// 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.notifyRemovedLocked(r0, 0, rs0);
mListeners.notifyRemovedLocked(r0, 0, rs0);
verify(sbn0, never()).cloneLight();
mListeners.notifyRemovedLocked(r1, 0, rs1);
mListeners.notifyRemovedLocked(r1, 0, rs1);
verify(sbn1, times(2)).cloneLight();
}
}

View File

@@ -123,6 +123,7 @@ import android.provider.MediaStore;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationRankingUpdate;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenPolicy;
@@ -148,6 +149,7 @@ import com.android.server.SystemService;
import com.android.server.UiServiceTestCase;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.pm.PackageManagerService;
@@ -267,6 +269,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Nullable
NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
@Nullable
Boolean mIsVisibleToListenerReturnValue = null;
TestableNotificationManagerService(Context context, InjectableSystemClock systemClock) {
super(context, systemClock);
}
@@ -347,6 +352,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
return true;
}
protected void setIsVisibleToListenerReturnValue(boolean value) {
mIsVisibleToListenerReturnValue = value;
}
@Override
boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
if (mIsVisibleToListenerReturnValue != null) {
return mIsVisibleToListenerReturnValue;
}
return super.isVisibleToListener(sbn, listener);
}
class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
private int mGetStrongAuthForUserReturnValue = 0;
StrongAuthTrackerFake(Context context) {
@@ -442,7 +459,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
mService.init(mTestableLooper.getLooper(),
mPackageManager, mPackageManagerClient, mockLightsManager,
mListeners, mAssistants, mConditionProviders,
@@ -5519,14 +5535,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
assertTrue(mStrongAuthTracker.isInLockDownMode());
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
assertTrue(mStrongAuthTracker.isInLockDownMode(mContext.getUserId()));
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(mContext.getUserId());
mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
assertFalse(mStrongAuthTracker.isInLockDownMode());
assertFalse(mStrongAuthTracker.isInLockDownMode(mContext.getUserId()));
}
@Test
public void testCancelAndPostNotificationsWhenEnterAndExitLockDownMode() {
NotificationManagerService.WorkerHandler mWorkerHandler = spy(
mService.new WorkerHandler(mTestableLooper.getLooper()));
mService.setHandler(mWorkerHandler);
// post 2 notifications from 2 packages
NotificationRecord pkgA = new NotificationRecord(mContext,
generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
@@ -5538,8 +5558,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// when entering the lockdown mode, cancel the 2 notifications.
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
assertTrue(mStrongAuthTracker.isInLockDownMode());
mStrongAuthTracker.onStrongAuthRequiredChanged(0);
assertTrue(mStrongAuthTracker.isInLockDownMode(0));
// the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN.
ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
@@ -5548,9 +5568,45 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// exit lockdown mode.
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0);
mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId());
mStrongAuthTracker.onStrongAuthRequiredChanged(0);
assertFalse(mStrongAuthTracker.isInLockDownMode(0));
// 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);
}
}