diff options
| author | 2023-07-24 21:49:09 +0000 | |
|---|---|---|
| committer | 2023-07-24 21:49:09 +0000 | |
| commit | bc57eaeb68568c641ddd5f88dc759e7355c1b9b7 (patch) | |
| tree | d329e5008406ffd6b8bcbdbf0fc9e01049677772 | |
| parent | 104e4c60d27933e6e2a0fd9de511630d6bac944d (diff) | |
| parent | d40f1a344148bbbd854f83beb93d4491161b5086 (diff) | |
Merge "Do less on the main thread" into udc-qpr-dev am: e9557c2541 am: d40f1a3441
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24029223
Change-Id: Ice87bd0ca1faa5eb7e39fbfdb6dabbaface50d70
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
6 files changed, 544 insertions, 5 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index a4e8c2ece894..80f5d1939ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -21,12 +21,16 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; +import android.net.Uri; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; @@ -71,6 +75,10 @@ import javax.inject.Named; @NotificationRowScope public class ExpandableNotificationRowController implements NotifViewController { private static final String TAG = "NotifRowController"; + + static final Uri BUBBLES_SETTING_URI = + Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES); + private static final String BUBBLES_SETTING_ENABLED_VALUE = "1"; private final ExpandableNotificationRow mView; private final NotificationListContainer mListContainer; private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory; @@ -104,6 +112,23 @@ public class ExpandableNotificationRowController implements NotifViewController private final ExpandableNotificationRowDragController mDragController; private final NotificationDismissibilityProvider mDismissibilityProvider; private final IStatusBarService mStatusBarService; + + private final NotificationSettingsController mSettingsController; + + @VisibleForTesting + final NotificationSettingsController.Listener mSettingsListener = + new NotificationSettingsController.Listener() { + @Override + public void onSettingChanged(Uri setting, int userId, String value) { + if (BUBBLES_SETTING_URI.equals(setting)) { + final int viewUserId = mView.getEntry().getSbn().getUserId(); + if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) { + mView.getPrivateLayout().setBubblesEnabledForUser( + BUBBLES_SETTING_ENABLED_VALUE.equals(value)); + } + } + } + }; private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback = new ExpandableNotificationRow.ExpandableNotificationRowLogger() { @Override @@ -201,6 +226,7 @@ public class ExpandableNotificationRowController implements NotifViewController FeatureFlags featureFlags, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, + NotificationSettingsController settingsController, ExpandableNotificationRowDragController dragController, NotificationDismissibilityProvider dismissibilityProvider, IStatusBarService statusBarService) { @@ -229,6 +255,7 @@ public class ExpandableNotificationRowController implements NotifViewController mFeatureFlags = featureFlags; mPeopleNotificationIdentifier = peopleNotificationIdentifier; mBubblesManagerOptional = bubblesManagerOptional; + mSettingsController = settingsController; mDragController = dragController; mMetricsLogger = metricsLogger; mChildrenContainerLogger = childrenContainerLogger; @@ -298,12 +325,14 @@ public class ExpandableNotificationRowController implements NotifViewController NotificationMenuRowPlugin.class, false /* Allow multiple */); mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD); mStatusBarStateController.addCallback(mStatusBarStateListener); + mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener); } @Override public void onViewDetachedFromWindow(View v) { mPluginManager.removePluginListener(mView); mStatusBarStateController.removeCallback(mStatusBarStateListener); + mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 20f4429f294b..7b6802f95cda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -44,6 +44,7 @@ import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; @@ -65,7 +66,6 @@ import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt; import com.android.systemui.statusbar.policy.SmartReplyView; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; -import com.android.systemui.wmshell.BubblesManager; import java.io.PrintWriter; import java.util.ArrayList; @@ -134,6 +134,7 @@ public class NotificationContentView extends FrameLayout implements Notification private PeopleNotificationIdentifier mPeopleIdentifier; private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory; private IStatusBarService mStatusBarService; + private boolean mBubblesEnabledForUser; /** * List of listeners for when content views become inactive (i.e. not the showing view). @@ -1440,12 +1441,17 @@ public class NotificationContentView extends FrameLayout implements Notification } } + @Background + public void setBubblesEnabledForUser(boolean enabled) { + mBubblesEnabledForUser = enabled; + } + @VisibleForTesting boolean shouldShowBubbleButton(NotificationEntry entry) { boolean isPersonWithShortcut = mPeopleIdentifier.getPeopleNotificationType(entry) >= PeopleNotificationIdentifier.TYPE_FULL_PERSON; - return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser()) + return mBubblesEnabledForUser && isPersonWithShortcut && entry.getBubbleMetadata() != null; } @@ -2079,6 +2085,7 @@ public class NotificationContentView extends FrameLayout implements Notification pw.print("null"); } pw.println(); + pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser); pw.print("RemoteInputViews { "); pw.print(" visibleType: " + mVisibleType); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java new file mode 100644 index 000000000000..585ff523b9a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerExecutor; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.Dumpable; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.settings.SecureSettings; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; + +import javax.inject.Inject; + +/** + * Centralized controller for listening to Secure Settings changes and informing in-process + * listeners, on a background thread. + */ +@SysUISingleton +public class NotificationSettingsController implements Dumpable { + + private final static String TAG = "NotificationSettingsController"; + private final UserTracker mUserTracker; + private final UserTracker.Callback mCurrentUserTrackerCallback; + private final Handler mHandler; + private final ContentObserver mContentObserver; + private final SecureSettings mSecureSettings; + private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>(); + + @Inject + public NotificationSettingsController(UserTracker userTracker, + @Background Handler handler, + SecureSettings secureSettings, + DumpManager dumpManager) { + mUserTracker = userTracker; + mHandler = handler; + mSecureSettings = secureSettings; + mContentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + synchronized (mListeners) { + if (mListeners.containsKey(uri)) { + for (Listener listener : mListeners.get(uri)) { + notifyListener(listener, uri); + } + } + } + } + }; + + mCurrentUserTrackerCallback = new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, Context userContext) { + synchronized (mListeners) { + if (mListeners.size() > 0) { + mSecureSettings.unregisterContentObserver(mContentObserver); + for (Uri uri : mListeners.keySet()) { + mSecureSettings.registerContentObserverForUser( + uri, false, mContentObserver, newUser); + } + } + } + } + }; + mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler)); + + dumpManager.registerNormalDumpable(TAG, this); + } + + /** + * Register callback whenever the given secure settings changes. + * + * On registration, will call back on the provided handler with the current value of + * the setting. + */ + public void addCallback(@NonNull Uri uri, @NonNull Listener listener) { + if (uri == null || listener == null) { + return; + } + synchronized (mListeners) { + ArrayList<Listener> currentListeners = mListeners.get(uri); + if (currentListeners == null) { + currentListeners = new ArrayList<>(); + } + if (!currentListeners.contains(listener)) { + currentListeners.add(listener); + } + mListeners.put(uri, currentListeners); + if (currentListeners.size() == 1) { + mSecureSettings.registerContentObserverForUser( + uri, false, mContentObserver, mUserTracker.getUserId()); + } + } + mHandler.post(() -> notifyListener(listener, uri)); + + } + + public void removeCallback(Uri uri, Listener listener) { + synchronized (mListeners) { + ArrayList<Listener> currentListeners = mListeners.get(uri); + + if (currentListeners != null) { + currentListeners.remove(listener); + } + if (currentListeners == null || currentListeners.size() == 0) { + mListeners.remove(uri); + } + + if (mListeners.size() == 0) { + mSecureSettings.unregisterContentObserver(mContentObserver); + } + } + } + + @Override + public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + synchronized (mListeners) { + pw.println("Settings Uri Listener List:"); + for (Uri uri : mListeners.keySet()) { + pw.println(" Uri=" + uri); + for (Listener listener : mListeners.get(uri)) { + pw.println(" Listener=" + listener.getClass().getName()); + } + } + } + } + + private void notifyListener(Listener listener, Uri uri) { + final String setting = uri == null ? null : uri.getLastPathSegment(); + int userId = mUserTracker.getUserId(); + listener.onSettingChanged(uri, userId, mSecureSettings.getStringForUser(setting, userId)); + } + + /** + * Listener invoked whenever settings are changed. + */ + public interface Listener { + void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index 2e68cec1fe63..4d4d319a3540 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -17,6 +17,10 @@ package com.android.systemui.statusbar.notification.row +import android.app.Notification +import android.net.Uri +import android.os.UserHandle +import android.os.UserHandle.USER_ALL import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -28,13 +32,17 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.PluginManager import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.SmartReplyController +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider import com.android.systemui.statusbar.notification.collection.render.FakeNodeController import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager import com.android.systemui.statusbar.notification.logging.NotificationLogger import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -45,9 +53,9 @@ import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.SystemClock import com.android.systemui.wmshell.BubblesManager -import java.util.Optional import junit.framework.Assert import org.junit.After import org.junit.Before @@ -55,7 +63,10 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import java.util.* import org.mockito.Mockito.`when` as whenever @SmallTest @@ -92,10 +103,10 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val featureFlags: FeatureFlags = mock() private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock() private val bubblesManager: BubblesManager = mock() + private val settingsController: NotificationSettingsController = mock() private val dragController: ExpandableNotificationRowDragController = mock() private val dismissibilityProvider: NotificationDismissibilityProvider = mock() private val statusBarService: IStatusBarService = mock() - private lateinit var controller: ExpandableNotificationRowController @Before @@ -132,11 +143,16 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { featureFlags, peopleNotificationIdentifier, Optional.of(bubblesManager), + settingsController, dragController, dismissibilityProvider, statusBarService ) whenever(view.childrenContainer).thenReturn(childrenContainer) + + val notification = Notification.Builder(mContext).build() + val sbn = SbnBuilder().setNotification(notification).build() + whenever(view.entry).thenReturn(NotificationEntryBuilder().setSbn(sbn).build()) } @After @@ -204,4 +220,74 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { Mockito.verify(view).removeChildNotification(eq(childView)) Mockito.verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer)) } + + @Test + fun registerSettingsListener_forBubbles() { + controller.init(mock(NotificationEntry::class.java)) + val viewStateObserver = withArgCaptor { + verify(view).addOnAttachStateChangeListener(capture()); + } + viewStateObserver.onViewAttachedToWindow(view); + verify(settingsController).addCallback(any(), any()); + } + + @Test + fun unregisterSettingsListener_forBubbles() { + controller.init(mock(NotificationEntry::class.java)) + val viewStateObserver = withArgCaptor { + verify(view).addOnAttachStateChangeListener(capture()); + } + viewStateObserver.onViewDetachedFromWindow(view); + verify(settingsController).removeCallback(any(), any()); + } + + @Test + fun settingsListener_invalidUri() { + controller.mSettingsListener.onSettingChanged(Uri.EMPTY, view.entry.sbn.userId, "1") + + verify(view, never()).getPrivateLayout() + } + + @Test + fun settingsListener_invalidUserId() { + controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, "1") + controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, null) + + verify(view, never()).getPrivateLayout() + } + + @Test + fun settingsListener_validUserId() { + val childView: NotificationContentView = mock() + whenever(view.privateLayout).thenReturn(childView) + + controller.mSettingsListener.onSettingChanged( + BUBBLES_SETTING_URI, view.entry.sbn.userId, "1") + verify(childView).setBubblesEnabledForUser(true) + + controller.mSettingsListener.onSettingChanged( + BUBBLES_SETTING_URI, view.entry.sbn.userId, "9") + verify(childView).setBubblesEnabledForUser(false) + } + + @Test + fun settingsListener_userAll() { + val childView: NotificationContentView = mock() + whenever(view.privateLayout).thenReturn(childView) + + val notification = Notification.Builder(mContext).build() + val sbn = SbnBuilder().setNotification(notification) + .setUser(UserHandle.of(USER_ALL)) + .build() + whenever(view.entry).thenReturn(NotificationEntryBuilder() + .setSbn(sbn) + .setUser(UserHandle.of(USER_ALL)) + .build()) + + controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1") + verify(childView).setBubblesEnabledForUser(true) + + controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 1, "0") + verify(childView).setBubblesEnabledForUser(false) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 0b90ebec3ec6..ba6c7fd50bc5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -250,6 +250,9 @@ class NotificationContentViewTest : SysuiTestCase() { .thenReturn(actionListMarginTarget) view.setContainingNotification(mockContainingNotification) + // Given: controller says bubbles are enabled for the user + view.setBubblesEnabledForUser(true); + // When: call NotificationContentView.setExpandedChild() to set the expandedChild view.expandedChild = mockExpandedChild @@ -301,6 +304,9 @@ class NotificationContentViewTest : SysuiTestCase() { view.expandedChild = mockExpandedChild assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget)) + // Given: controller says bubbles are enabled for the user + view.setBubblesEnabledForUser(true); + // When: call NotificationContentView.onNotificationUpdated() to update the // NotificationEntry, which should show bubble button view.onNotificationUpdated(createMockNotificationEntry(true)) @@ -405,7 +411,6 @@ class NotificationContentViewTest : SysuiTestCase() { val userMock: UserHandle = mock() whenever(this.sbn).thenReturn(sbnMock) whenever(sbnMock.user).thenReturn(userMock) - doReturn(showButton).whenever(view).shouldShowBubbleButton(this) } private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt new file mode 100644 index 000000000000..2bccdcafbb6e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.row + +import android.app.ActivityManager +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.provider.Settings.Secure +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.notification.row.NotificationSettingsController.Listener +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.SecureSettings +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class NotificationSettingsControllerTest : SysuiTestCase() { + + val setting1: String = Secure.NOTIFICATION_BUBBLES + val setting2: String = Secure.ACCESSIBILITY_ENABLED + val settingUri1: Uri = Secure.getUriFor(setting1) + val settingUri2: Uri = Secure.getUriFor(setting2) + + @Mock + private lateinit var userTracker: UserTracker + private lateinit var handler: Handler + private lateinit var testableLooper: TestableLooper + @Mock + private lateinit var secureSettings: SecureSettings + @Mock + private lateinit var dumpManager: DumpManager + + @Captor + private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback> + @Captor + private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> + + private lateinit var controller: NotificationSettingsController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + handler = Handler(testableLooper.looper) + allowTestableLooperAsMainThread() + controller = + NotificationSettingsController( + userTracker, + handler, + secureSettings, + dumpManager + ) + } + + @After + fun tearDown() { + disallowTestableLooperAsMainThread() + } + + @Test + fun creationRegistersCallbacks() { + verify(userTracker).addCallback(any(), any()) + verify(dumpManager).registerNormalDumpable(anyString(), eq(controller)) + } + @Test + fun updateContentObserverRegistration_onUserChange_noSettingsListeners() { + verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any()) + val userCallback = userTrackerCallbackCaptor.value + val userId = 9 + + // When: User is changed + userCallback.onUserChanged(userId, context) + + // Validate: Nothing to do, since we aren't monitoring settings + verify(secureSettings, never()).unregisterContentObserver(any()) + verify(secureSettings, never()).registerContentObserverForUser( + any(Uri::class.java), anyBoolean(), any(), anyInt()) + } + @Test + fun updateContentObserverRegistration_onUserChange_withSettingsListeners() { + // When: someone is listening to a setting + controller.addCallback(settingUri1, + Mockito.mock(Listener::class.java)) + + verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any()) + val userCallback = userTrackerCallbackCaptor.value + val userId = 9 + + // Then: User is changed + userCallback.onUserChanged(userId, context) + + // Validate: The tracker is unregistered and re-registered with the new user + verify(secureSettings).unregisterContentObserver(any()) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), eq(false), any(), eq(userId)) + } + + @Test + fun addCallback_onlyFirstForUriRegistersObserver() { + controller.addCallback(settingUri1, + Mockito.mock(Listener::class.java)) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) + + controller.addCallback(settingUri1, + Mockito.mock(Listener::class.java)) + verify(secureSettings).registerContentObserverForUser( + any(Uri::class.java), anyBoolean(), any(), anyInt()) + } + + @Test + fun addCallback_secondUriRegistersObserver() { + controller.addCallback(settingUri1, + Mockito.mock(Listener::class.java)) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) + + controller.addCallback(settingUri2, + Mockito.mock(Listener::class.java)) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri2), eq(false), any(), eq(ActivityManager.getCurrentUser())) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), anyBoolean(), any(), anyInt()) + } + + @Test + fun removeCallback_lastUnregistersObserver() { + val listenerSetting1 : Listener = mock() + val listenerSetting2 : Listener = mock() + controller.addCallback(settingUri1, listenerSetting1) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) + + controller.addCallback(settingUri2, listenerSetting2) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri2), anyBoolean(), any(), anyInt()) + + controller.removeCallback(settingUri2, listenerSetting2) + verify(secureSettings, never()).unregisterContentObserver(any()) + + controller.removeCallback(settingUri1, listenerSetting1) + verify(secureSettings).unregisterContentObserver(any()) + } + + @Test + fun addCallback_updatesCurrentValue() { + whenever(secureSettings.getStringForUser( + setting1, ActivityManager.getCurrentUser())).thenReturn("9") + whenever(secureSettings.getStringForUser( + setting2, ActivityManager.getCurrentUser())).thenReturn("5") + + val listenerSetting1a : Listener = mock() + val listenerSetting1b : Listener = mock() + val listenerSetting2 : Listener = mock() + + controller.addCallback(settingUri1, listenerSetting1a) + controller.addCallback(settingUri1, listenerSetting1b) + controller.addCallback(settingUri2, listenerSetting2) + + testableLooper.processAllMessages() + + verify(listenerSetting1a).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + verify(listenerSetting1b).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + verify(listenerSetting2).onSettingChanged( + settingUri2, ActivityManager.getCurrentUser(), "5") + } + + @Test + fun removeCallback_noMoreUpdates() { + whenever(secureSettings.getStringForUser( + setting1, ActivityManager.getCurrentUser())).thenReturn("9") + + val listenerSetting1a : Listener = mock() + val listenerSetting1b : Listener = mock() + + // First, register + controller.addCallback(settingUri1, listenerSetting1a) + controller.addCallback(settingUri1, listenerSetting1b) + testableLooper.processAllMessages() + + verify(secureSettings).registerContentObserverForUser( + any(Uri::class.java), anyBoolean(), capture(settingsObserverCaptor), anyInt()) + verify(listenerSetting1a).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + verify(listenerSetting1b).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + Mockito.clearInvocations(listenerSetting1b) + Mockito.clearInvocations(listenerSetting1a) + + // Remove one of them + controller.removeCallback(settingUri1, listenerSetting1a) + + // On update, only remaining listener should get the callback + settingsObserverCaptor.value.onChange(false, settingUri1) + testableLooper.processAllMessages() + + verify(listenerSetting1a, never()).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + verify(listenerSetting1b).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + } + +}
\ No newline at end of file |