Do less on the main thread
With a settings ContentObserver Test: ExpandableNotificationRowControllerTest Test: NotificationSettingsControllerTest Fixes: 271435682 Change-Id: I76b73acb3336f8bd321e1c5e3df800a029155a90
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user