Do less on the main thread

With a settings ContentObserver

Test: ExpandableNotificationRowControllerTest
Test: NotificationSettingsControllerTest
Fixes: 271435682

Change-Id: I76b73acb3336f8bd321e1c5e3df800a029155a90
This commit is contained in:
Julia Reynolds
2023-07-10 18:22:25 -04:00
parent ef900a3efa
commit e1713e606c
6 changed files with 544 additions and 5 deletions

View File

@@ -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);
}
});
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -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")
}
}