From 48b5a61f6de448114a85dfbda6cc95e7f9e756be Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Fri, 18 Mar 2022 15:44:48 -0400 Subject: [PATCH] Register KeyguardNotifVisProvider CoreStartable KeyguardNotificationVisibilityProvider's `start` method was never being invoked, because it was never being registered as a CoreStartable. This was in turn causing all invalidaton events to be missed, resulting in stale notification shade states being visible. This CL properly registers the CoreStartable, and adds additional tests to verify that listeners are called as expected once registered. Fixes: 224763592 Test: atest KeyguardNotificationVisibilityProviderTest Test: 0. Have silent notifications 1. lock device 2. expand shade, keeping device locked 3. collapse shade 4. verify silent header no longer visible Change-Id: I70661583f0d98c6dac7924667dad89a3664334e2 --- .../coordinator/KeyguardCoordinator.java | 2 +- .../dagger/NotificationsModule.java | 4 +- .../KeyguardNotificationVisibilityProvider.kt | 99 +++++--- ...otificationInterruptStateProviderImpl.java | 2 +- .../systemui/util/settings/SettingsProxy.java | 71 ++++++ ...ardNotificationVisibilityProviderTest.java | 234 +++++++++++++++--- .../util/mockito/KotlinMockitoHelpers.kt | 9 + 7 files changed, 354 insertions(+), 67 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index 54cfb5d44a3bf..4895fa3fcb563 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -70,7 +70,7 @@ public class KeyguardCoordinator implements Coordinator { private final NotifFilter mNotifFilter = new NotifFilter(TAG) { @Override public boolean shouldFilterOut(NotificationEntry entry, long now) { - return mKeyguardNotificationVisibilityProvider.hideNotification(entry); + return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index efe88e6d4f0a0..34c8044ef0d3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -73,6 +73,7 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; +import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProviderModule; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.logging.NotificationLogger; @@ -107,9 +108,10 @@ import dagger.Provides; */ @Module(includes = { CoordinatorsModule.class, + KeyguardNotificationVisibilityProviderModule.class, NotifActivityLaunchEventsModule.class, - NotifPipelineChoreographerModule.class, NotifPanelEventsModule.class, + NotifPipelineChoreographerModule.class, NotificationSectionHeadersModule.class, }) public interface NotificationsModule { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index 70c9a16b9d7a1..ff0e47f3cfecc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -14,6 +14,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -22,13 +23,49 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ListenerSet +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SecureSettings +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import java.util.function.Consumer import javax.inject.Inject -/** - * Determines if notifications should be visible based on the state of the keyguard - */ -class KeyguardNotificationVisibilityProvider @Inject constructor( +/** Determines if notifications should be visible based on the state of the keyguard. */ +interface KeyguardNotificationVisibilityProvider { + /** + * Determines if the given notification should be hidden based on the current keyguard state. + * If a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this + * method may no longer be valid and should be re-queried. + */ + fun shouldHideNotification(entry: NotificationEntry): Boolean + + /** Registers a listener to be notified when the internal keyguard state has been updated. */ + fun addOnStateChangedListener(listener: Consumer) + + /** Unregisters a listener previously registered with [addOnStateChangedListener]. */ + fun removeOnStateChangedListener(listener: Consumer) +} + +/** Provides a [KeyguardNotificationVisibilityProvider] in [SysUISingleton] scope. */ +@Module(includes = [KeyguardNotificationVisibilityProviderImplModule::class]) +object KeyguardNotificationVisibilityProviderModule + +@Module +private interface KeyguardNotificationVisibilityProviderImplModule { + @Binds + fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl): + KeyguardNotificationVisibilityProvider + + @Binds + @IntoMap + @ClassKey(KeyguardNotificationVisibilityProvider::class) + fun bindStartable(impl: KeyguardNotificationVisibilityProviderImpl): CoreStartable +} + +@SysUISingleton +private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( context: Context, @Main private val handler: Handler, private val keyguardStateController: KeyguardStateController, @@ -36,8 +73,10 @@ class KeyguardNotificationVisibilityProvider @Inject constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val highPriorityProvider: HighPriorityProvider, private val statusBarStateController: StatusBarStateController, - private val broadcastDispatcher: BroadcastDispatcher -) : CoreStartable(context) { + private val broadcastDispatcher: BroadcastDispatcher, + private val secureSettings: SecureSettings, + private val globalSettings: GlobalSettings +) : CoreStartable(context), KeyguardNotificationVisibilityProvider { private val onStateChangedListeners = ListenerSet>() private var hideSilentNotificationsOnLockscreen: Boolean = false @@ -60,33 +99,28 @@ class KeyguardNotificationVisibilityProvider @Inject constructor( // register lockscreen settings changed callbacks: val settingsObserver: ContentObserver = object : ContentObserver(handler) { - override fun onChange(selfChange: Boolean, uri: Uri) { + override fun onChange(selfChange: Boolean, uri: Uri?) { if (keyguardStateController.isShowing) { notifyStateChanged("Settings $uri changed") } } } - mContext.contentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), - false, + secureSettings.registerContentObserverForUser( + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, settingsObserver, UserHandle.USER_ALL) - mContext.contentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + secureSettings.registerContentObserverForUser( + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true, settingsObserver, UserHandle.USER_ALL) - mContext.contentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ZEN_MODE), - false, - settingsObserver) + globalSettings.registerContentObserver(Settings.Global.ZEN_MODE, settingsObserver) - mContext.contentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS), - false, + secureSettings.registerContentObserverForUser( + Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, settingsObserver, UserHandle.USER_ALL) @@ -98,41 +132,36 @@ class KeyguardNotificationVisibilityProvider @Inject constructor( }) broadcastDispatcher.registerReceiver(object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - if (keyguardStateController.isShowing()) { + if (keyguardStateController.isShowing) { // maybe public mode changed - notifyStateChanged(intent.action) + notifyStateChanged(intent.action!!) } } }, IntentFilter(Intent.ACTION_USER_SWITCHED)) } - fun addOnStateChangedListener(listener: Consumer) { + override fun addOnStateChangedListener(listener: Consumer) { onStateChangedListeners.addIfAbsent(listener) } - fun removeOnStateChangedListener(listener: Consumer) { + override fun removeOnStateChangedListener(listener: Consumer) { onStateChangedListeners.remove(listener) } private fun notifyStateChanged(reason: String) { - onStateChangedListeners.forEach({ it.accept(reason) }) + onStateChangedListeners.forEach { it.accept(reason) } } - /** - * Determines if the given notification should be hidden based on the current keyguard state. - * If Listener#onKeyguardStateChanged is invoked, the results of this method may no longer - * be valid, and so should be re-queried - */ - fun hideNotification(entry: NotificationEntry): Boolean { + override fun shouldHideNotification(entry: NotificationEntry): Boolean { val sbn = entry.sbn // FILTER OUT the notification when the keyguard is showing and... - if (keyguardStateController.isShowing()) { + if (keyguardStateController.isShowing) { // ... user settings or the device policy manager doesn't allow lockscreen // notifications; if (!lockscreenUserManager.shouldShowLockscreenNotifications()) { return true } - val currUserId: Int = lockscreenUserManager.getCurrentUserId() + val currUserId: Int = lockscreenUserManager.currentUserId val notifUserId = if (sbn.user.identifier == UserHandle.USER_ALL) currUserId else sbn.user.identifier @@ -178,9 +207,7 @@ class KeyguardNotificationVisibilityProvider @Inject constructor( } private fun readShowSilentNotificationSetting() { - hideSilentNotificationsOnLockscreen = Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, - 1) == 0 + hideSilentNotificationsOnLockscreen = + secureSettings.getBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true) } } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index c1771ccce9d03..e210f193b0a1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -312,7 +312,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } - if (mKeyguardNotificationVisibilityProvider.hideNotification(entry)) { + if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) { mLogger.keyguardHideNotification(entry.getKey()); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java index 5aaf7f680d5cd..1bf5f076ac2f8 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java @@ -303,11 +303,82 @@ public interface SettingsProxy { default boolean putInt(String name, int value) { return putIntForUser(name, value, getUserId()); } + /** See {@link #putInt(String, int)}. */ default boolean putIntForUser(String name, int value, int userHandle) { return putStringForUser(name, Integer.toString(value), userHandle); } + /** + * Convenience function for retrieving a single secure settings value + * as a boolean. Note that internally setting values are always + * stored as strings; this function converts the string to a boolean + * for you. The default value will be returned if the setting is + * not defined or not a boolean. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid boolean. + */ + default boolean getBool(String name, boolean def) { + return getBoolForUser(name, def, getUserId()); + } + + /** See {@link #getBool(String, boolean)}. */ + default boolean getBoolForUser(String name, boolean def, int userHandle) { + return getIntForUser(name, def ? 1 : 0, userHandle) != 0; + } + + /** + * Convenience function for retrieving a single secure settings value + * as a boolean. Note that internally setting values are always + * stored as strings; this function converts the string to a boolean + * for you. + *

+ * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link Settings.SettingNotFoundException}. + * + * @param name The name of the setting to retrieve. + * + * @throws Settings.SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not a boolean. + * + * @return The setting's current value. + */ + default boolean getBool(String name) throws Settings.SettingNotFoundException { + return getBoolForUser(name, getUserId()); + } + + /** See {@link #getBool(String)}. */ + default boolean getBoolForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + return getIntForUser(name, userHandle) != 0; + } + + /** + * Convenience function for updating a single settings value as a + * boolean. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + default boolean putBool(String name, boolean value) { + return putBoolForUser(name, value, getUserId()); + } + + /** See {@link #putBool(String, boolean)}. */ + default boolean putBoolForUser(String name, boolean value, int userHandle) { + return putIntForUser(name, value ? 1 : 0, userHandle); + } + /** * Convenience function for retrieving a single secure settings value * as a {@code long}. Note that internally setting values are always diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 01aa0f2acb01f..32d625c707cf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -22,84 +22,238 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_MIN; import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry; +import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.spy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; import android.os.Handler; import android.os.UserHandle; +import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.CoreStartable; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.utils.os.FakeHandler; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Map; +import java.util.function.Consumer; + +import dagger.BindsInstance; +import dagger.Component; + @SmallTest @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { private static final int NOTIF_USER_ID = 0; private static final int CURR_USER_ID = 1; - @Mock - private Handler mMainHandler; @Mock private KeyguardStateController mKeyguardStateController; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider; - @Mock private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; @Mock private StatusBarStateController mStatusBarStateController; @Mock private BroadcastDispatcher mBroadcastDispatcher; + private final FakeSettings mFakeSettings = new FakeSettings(); + private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; private NotificationEntry mEntry; @Before public void setup() { MockitoAnnotations.initMocks(this); - // TODO refactor the test of KeyguardNotificationVisibilityProvider out - mKeyguardNotificationVisibilityProvider = spy(new KeyguardNotificationVisibilityProvider( - mContext, - mMainHandler, - mKeyguardStateController, - mLockscreenUserManager, - mKeyguardUpdateMonitor, - mHighPriorityProvider, - mStatusBarStateController, - mBroadcastDispatcher - )); - + TestComponent component = + DaggerKeyguardNotificationVisibilityProviderTest_TestComponent + .factory() + .create( + mContext, + new FakeHandler(TestableLooper.get(this).getLooper()), + mKeyguardStateController, + mLockscreenUserManager, + mKeyguardUpdateMonitor, + mHighPriorityProvider, + mStatusBarStateController, + mBroadcastDispatcher, + mFakeSettings, + mFakeSettings); + mKeyguardNotificationVisibilityProvider = component.getProvider(); + for (CoreStartable startable : component.getCoreStartables().values()) { + startable.start(); + } mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) .build(); } + @Test + public void notifyListeners_onUnlockedChanged() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + verify(mKeyguardStateController).addCallback(callbackCaptor.capture()); + KeyguardStateController.Callback callback = callbackCaptor.getValue(); + + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + callback.onUnlockedChanged(); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onKeyguardShowingChanged() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + verify(mKeyguardStateController).addCallback(callbackCaptor.capture()); + KeyguardStateController.Callback callback = callbackCaptor.getValue(); + + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + callback.onKeyguardShowingChanged(); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onStrongAuthStateChanged() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture()); + KeyguardUpdateMonitorCallback callback = callbackCaptor.getValue(); + + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + callback.onStrongAuthStateChanged(0); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onStatusBarStateChanged() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); + verify(mStatusBarStateController).addCallback(callbackCaptor.capture()); + StatusBarStateController.StateListener callback = callbackCaptor.getValue(); + + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + callback.onStateChanged(0); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onReceiveUserSwitchBroadcast() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mBroadcastDispatcher).registerReceiver( + callbackCaptor.capture(), + argThat(intentFilter -> intentFilter.hasAction(Intent.ACTION_USER_SWITCHED)), + isNull(), + isNull(), + eq(Context.RECEIVER_EXPORTED)); + BroadcastReceiver callback = callbackCaptor.getValue(); + + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + when(mKeyguardStateController.isShowing()).thenReturn(true); + callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED)); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onSettingChange_lockScreenShowNotifs() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + mFakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onSettingChange_lockScreenAllowPrivateNotifs() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + mFakeSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onSettingChange_zenMode() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + mFakeSettings.putInt(Settings.Global.ZEN_MODE, 1); + + verify(listener).accept(anyString()); + } + + @Test + public void notifyListeners_onSettingChange_lockScreenShowSilentNotifs() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + Consumer listener = mock(Consumer.class); + mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); + + mFakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1); + + verify(listener).accept(anyString()); + } + @Test public void unfilteredState() { // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); // THEN don't filter out the entry - assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -109,7 +263,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mKeyguardStateController.isShowing()).thenReturn(false); // THEN don't filter out the entry - assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -121,7 +275,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -133,7 +287,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -148,7 +302,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { .thenReturn(false); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -164,7 +318,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { .setVisibilityOverride(VISIBILITY_SECRET).build()); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -180,7 +334,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(mEntry)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @Test @@ -209,7 +363,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true); // THEN don't filter out the entry - assertFalse(mKeyguardNotificationVisibilityProvider.hideNotification(entryWithParent)); + assertFalse( + mKeyguardNotificationVisibilityProvider.shouldHideNotification(entryWithParent)); // WHEN its parent doesn't exceed threshold to show on lockscreen when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(false); @@ -218,7 +373,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { .done()); // THEN filter out the entry - assertTrue(mKeyguardNotificationVisibilityProvider.hideNotification(entryWithParent)); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(entryWithParent)); } /** @@ -259,4 +414,27 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { // notification is high priority, so it shouldn't be filtered when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(true); } -} + + @SysUISingleton + @Component(modules = { KeyguardNotificationVisibilityProviderModule.class }) + interface TestComponent { + KeyguardNotificationVisibilityProvider getProvider(); + Map, CoreStartable> getCoreStartables(); + + @Component.Factory + interface Factory { + TestComponent create( + @BindsInstance Context context, + @BindsInstance @Main Handler handler, + @BindsInstance KeyguardStateController keyguardStateController, + @BindsInstance NotificationLockscreenUserManager lockscreenUserManager, + @BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor, + @BindsInstance HighPriorityProvider highPriorityProvider, + @BindsInstance StatusBarStateController statusBarStateController, + @BindsInstance BroadcastDispatcher broadcastDispatcher, + @BindsInstance SecureSettings secureSettings, + @BindsInstance GlobalSettings globalSettings + ); + } + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt index 0f1b65cb7f04a..309acdf13a5de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt @@ -24,6 +24,7 @@ package com.android.systemui.util.mockito */ import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatcher import org.mockito.Mockito /** @@ -43,6 +44,14 @@ fun eq(obj: T): T = Mockito.eq(obj) fun any(type: Class): T = Mockito.any(type) inline fun any(): T = any(T::class.java) +/** + * Returns Mockito.argThat() as nullable type to avoid java.lang.IllegalStateException when + * null is returned. + * + * Generic T is nullable because implicitly bounded by Any?. + */ +fun argThat(matcher: ArgumentMatcher): T = Mockito.argThat(matcher) + /** * Kotlin type-inferred version of Mockito.nullable() */