Merge "Do less on the main thread" into udc-qpr-dev am: e9557c2541
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24029223 Change-Id: I14580762fb7dfb663a8ba041d0abd1956471db04 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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.StatusBarState.KEYGUARD;
|
||||||
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
|
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.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.android.internal.logging.MetricsLogger;
|
import com.android.internal.logging.MetricsLogger;
|
||||||
import com.android.internal.statusbar.IStatusBarService;
|
import com.android.internal.statusbar.IStatusBarService;
|
||||||
@@ -71,6 +75,10 @@ import javax.inject.Named;
|
|||||||
@NotificationRowScope
|
@NotificationRowScope
|
||||||
public class ExpandableNotificationRowController implements NotifViewController {
|
public class ExpandableNotificationRowController implements NotifViewController {
|
||||||
private static final String TAG = "NotifRowController";
|
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 ExpandableNotificationRow mView;
|
||||||
private final NotificationListContainer mListContainer;
|
private final NotificationListContainer mListContainer;
|
||||||
private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
|
private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
|
||||||
@@ -104,6 +112,23 @@ public class ExpandableNotificationRowController implements NotifViewController
|
|||||||
private final ExpandableNotificationRowDragController mDragController;
|
private final ExpandableNotificationRowDragController mDragController;
|
||||||
private final NotificationDismissibilityProvider mDismissibilityProvider;
|
private final NotificationDismissibilityProvider mDismissibilityProvider;
|
||||||
private final IStatusBarService mStatusBarService;
|
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 =
|
private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
|
||||||
new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
|
new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
|
||||||
@Override
|
@Override
|
||||||
@@ -201,6 +226,7 @@ public class ExpandableNotificationRowController implements NotifViewController
|
|||||||
FeatureFlags featureFlags,
|
FeatureFlags featureFlags,
|
||||||
PeopleNotificationIdentifier peopleNotificationIdentifier,
|
PeopleNotificationIdentifier peopleNotificationIdentifier,
|
||||||
Optional<BubblesManager> bubblesManagerOptional,
|
Optional<BubblesManager> bubblesManagerOptional,
|
||||||
|
NotificationSettingsController settingsController,
|
||||||
ExpandableNotificationRowDragController dragController,
|
ExpandableNotificationRowDragController dragController,
|
||||||
NotificationDismissibilityProvider dismissibilityProvider,
|
NotificationDismissibilityProvider dismissibilityProvider,
|
||||||
IStatusBarService statusBarService) {
|
IStatusBarService statusBarService) {
|
||||||
@@ -229,6 +255,7 @@ public class ExpandableNotificationRowController implements NotifViewController
|
|||||||
mFeatureFlags = featureFlags;
|
mFeatureFlags = featureFlags;
|
||||||
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
|
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
|
||||||
mBubblesManagerOptional = bubblesManagerOptional;
|
mBubblesManagerOptional = bubblesManagerOptional;
|
||||||
|
mSettingsController = settingsController;
|
||||||
mDragController = dragController;
|
mDragController = dragController;
|
||||||
mMetricsLogger = metricsLogger;
|
mMetricsLogger = metricsLogger;
|
||||||
mChildrenContainerLogger = childrenContainerLogger;
|
mChildrenContainerLogger = childrenContainerLogger;
|
||||||
@@ -298,12 +325,14 @@ public class ExpandableNotificationRowController implements NotifViewController
|
|||||||
NotificationMenuRowPlugin.class, false /* Allow multiple */);
|
NotificationMenuRowPlugin.class, false /* Allow multiple */);
|
||||||
mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
|
mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
|
||||||
mStatusBarStateController.addCallback(mStatusBarStateListener);
|
mStatusBarStateController.addCallback(mStatusBarStateListener);
|
||||||
|
mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewDetachedFromWindow(View v) {
|
public void onViewDetachedFromWindow(View v) {
|
||||||
mPluginManager.removePluginListener(mView);
|
mPluginManager.removePluginListener(mView);
|
||||||
mStatusBarStateController.removeCallback(mStatusBarStateListener);
|
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.annotations.VisibleForTesting;
|
||||||
import com.android.internal.statusbar.IStatusBarService;
|
import com.android.internal.statusbar.IStatusBarService;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
|
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
|
||||||
import com.android.systemui.statusbar.RemoteInputController;
|
import com.android.systemui.statusbar.RemoteInputController;
|
||||||
import com.android.systemui.statusbar.SmartReplyController;
|
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.SmartReplyView;
|
||||||
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
|
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
|
||||||
import com.android.systemui.util.Compile;
|
import com.android.systemui.util.Compile;
|
||||||
import com.android.systemui.wmshell.BubblesManager;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -134,6 +134,7 @@ public class NotificationContentView extends FrameLayout implements Notification
|
|||||||
private PeopleNotificationIdentifier mPeopleIdentifier;
|
private PeopleNotificationIdentifier mPeopleIdentifier;
|
||||||
private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory;
|
private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory;
|
||||||
private IStatusBarService mStatusBarService;
|
private IStatusBarService mStatusBarService;
|
||||||
|
private boolean mBubblesEnabledForUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of listeners for when content views become inactive (i.e. not the showing view).
|
* 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
|
@VisibleForTesting
|
||||||
boolean shouldShowBubbleButton(NotificationEntry entry) {
|
boolean shouldShowBubbleButton(NotificationEntry entry) {
|
||||||
boolean isPersonWithShortcut =
|
boolean isPersonWithShortcut =
|
||||||
mPeopleIdentifier.getPeopleNotificationType(entry)
|
mPeopleIdentifier.getPeopleNotificationType(entry)
|
||||||
>= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
|
>= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
|
||||||
return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
|
return mBubblesEnabledForUser
|
||||||
&& isPersonWithShortcut
|
&& isPersonWithShortcut
|
||||||
&& entry.getBubbleMetadata() != null;
|
&& entry.getBubbleMetadata() != null;
|
||||||
}
|
}
|
||||||
@@ -2079,6 +2085,7 @@ public class NotificationContentView extends FrameLayout implements Notification
|
|||||||
pw.print("null");
|
pw.print("null");
|
||||||
}
|
}
|
||||||
pw.println();
|
pw.println();
|
||||||
|
pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser);
|
||||||
|
|
||||||
pw.print("RemoteInputViews { ");
|
pw.print("RemoteInputViews { ");
|
||||||
pw.print(" visibleType: " + mVisibleType);
|
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
|
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.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import androidx.test.filters.SmallTest
|
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.FalsingManager
|
||||||
import com.android.systemui.plugins.PluginManager
|
import com.android.systemui.plugins.PluginManager
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||||
|
import com.android.systemui.statusbar.SbnBuilder
|
||||||
import com.android.systemui.statusbar.SmartReplyController
|
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.provider.NotificationDismissibilityProvider
|
||||||
import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
|
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.GroupExpansionManager
|
||||||
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
|
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
|
||||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger
|
import com.android.systemui.statusbar.notification.logging.NotificationLogger
|
||||||
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
|
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.NotificationChildrenContainer
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
|
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
|
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.any
|
||||||
import com.android.systemui.util.mockito.eq
|
import com.android.systemui.util.mockito.eq
|
||||||
import com.android.systemui.util.mockito.mock
|
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.util.time.SystemClock
|
||||||
import com.android.systemui.wmshell.BubblesManager
|
import com.android.systemui.wmshell.BubblesManager
|
||||||
import java.util.Optional
|
|
||||||
import junit.framework.Assert
|
import junit.framework.Assert
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -55,7 +63,10 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.anyBoolean
|
import org.mockito.Mockito.anyBoolean
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import java.util.*
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -92,10 +103,10 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
|
|||||||
private val featureFlags: FeatureFlags = mock()
|
private val featureFlags: FeatureFlags = mock()
|
||||||
private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
|
private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
|
||||||
private val bubblesManager: BubblesManager = mock()
|
private val bubblesManager: BubblesManager = mock()
|
||||||
|
private val settingsController: NotificationSettingsController = mock()
|
||||||
private val dragController: ExpandableNotificationRowDragController = mock()
|
private val dragController: ExpandableNotificationRowDragController = mock()
|
||||||
private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
|
private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
|
||||||
private val statusBarService: IStatusBarService = mock()
|
private val statusBarService: IStatusBarService = mock()
|
||||||
|
|
||||||
private lateinit var controller: ExpandableNotificationRowController
|
private lateinit var controller: ExpandableNotificationRowController
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -132,11 +143,16 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
|
|||||||
featureFlags,
|
featureFlags,
|
||||||
peopleNotificationIdentifier,
|
peopleNotificationIdentifier,
|
||||||
Optional.of(bubblesManager),
|
Optional.of(bubblesManager),
|
||||||
|
settingsController,
|
||||||
dragController,
|
dragController,
|
||||||
dismissibilityProvider,
|
dismissibilityProvider,
|
||||||
statusBarService
|
statusBarService
|
||||||
)
|
)
|
||||||
whenever(view.childrenContainer).thenReturn(childrenContainer)
|
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
|
@After
|
||||||
@@ -204,4 +220,74 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
|
|||||||
Mockito.verify(view).removeChildNotification(eq(childView))
|
Mockito.verify(view).removeChildNotification(eq(childView))
|
||||||
Mockito.verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer))
|
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)
|
.thenReturn(actionListMarginTarget)
|
||||||
view.setContainingNotification(mockContainingNotification)
|
view.setContainingNotification(mockContainingNotification)
|
||||||
|
|
||||||
|
// Given: controller says bubbles are enabled for the user
|
||||||
|
view.setBubblesEnabledForUser(true);
|
||||||
|
|
||||||
// When: call NotificationContentView.setExpandedChild() to set the expandedChild
|
// When: call NotificationContentView.setExpandedChild() to set the expandedChild
|
||||||
view.expandedChild = mockExpandedChild
|
view.expandedChild = mockExpandedChild
|
||||||
|
|
||||||
@@ -301,6 +304,9 @@ class NotificationContentViewTest : SysuiTestCase() {
|
|||||||
view.expandedChild = mockExpandedChild
|
view.expandedChild = mockExpandedChild
|
||||||
assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
|
assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
|
||||||
|
|
||||||
|
// Given: controller says bubbles are enabled for the user
|
||||||
|
view.setBubblesEnabledForUser(true);
|
||||||
|
|
||||||
// When: call NotificationContentView.onNotificationUpdated() to update the
|
// When: call NotificationContentView.onNotificationUpdated() to update the
|
||||||
// NotificationEntry, which should show bubble button
|
// NotificationEntry, which should show bubble button
|
||||||
view.onNotificationUpdated(createMockNotificationEntry(true))
|
view.onNotificationUpdated(createMockNotificationEntry(true))
|
||||||
@@ -405,7 +411,6 @@ class NotificationContentViewTest : SysuiTestCase() {
|
|||||||
val userMock: UserHandle = mock()
|
val userMock: UserHandle = mock()
|
||||||
whenever(this.sbn).thenReturn(sbnMock)
|
whenever(this.sbn).thenReturn(sbnMock)
|
||||||
whenever(sbnMock.user).thenReturn(userMock)
|
whenever(sbnMock.user).thenReturn(userMock)
|
||||||
doReturn(showButton).whenever(view).shouldShowBubbleButton(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
|
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