Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +29 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -201,6 +226,7 @@ public class ExpandableNotificationRowController implements NotifViewController FeatureFlags featureFlags, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, NotificationSettingsController settingsController, ExpandableNotificationRowDragController dragController, NotificationDismissibilityProvider dismissibilityProvider, IStatusBarService statusBarService) { Loading Loading @@ -229,6 +255,7 @@ public class ExpandableNotificationRowController implements NotifViewController mFeatureFlags = featureFlags; mPeopleNotificationIdentifier = peopleNotificationIdentifier; mBubblesManagerOptional = bubblesManagerOptional; mSettingsController = settingsController; mDragController = dragController; mMetricsLogger = metricsLogger; mChildrenContainerLogger = childrenContainerLogger; Loading Loading @@ -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); } }); } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +9 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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). Loading Loading @@ -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; } Loading Loading @@ -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); Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java 0 → 100644 +167 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.row; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import javax.inject.Inject; /** * Centralized controller for listening to Secure Settings changes and informing in-process * listeners, on a background thread. */ @SysUISingleton public class NotificationSettingsController implements Dumpable { private final static String TAG = "NotificationSettingsController"; private final UserTracker mUserTracker; private final UserTracker.Callback mCurrentUserTrackerCallback; private final Handler mHandler; private final ContentObserver mContentObserver; private final SecureSettings mSecureSettings; private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>(); @Inject public NotificationSettingsController(UserTracker userTracker, @Background Handler handler, SecureSettings secureSettings, DumpManager dumpManager) { mUserTracker = userTracker; mHandler = handler; mSecureSettings = secureSettings; mContentObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); synchronized (mListeners) { if (mListeners.containsKey(uri)) { for (Listener listener : mListeners.get(uri)) { notifyListener(listener, uri); } } } } }; mCurrentUserTrackerCallback = new UserTracker.Callback() { @Override public void onUserChanged(int newUser, Context userContext) { synchronized (mListeners) { if (mListeners.size() > 0) { mSecureSettings.unregisterContentObserver(mContentObserver); for (Uri uri : mListeners.keySet()) { mSecureSettings.registerContentObserverForUser( uri, false, mContentObserver, newUser); } } } } }; mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler)); dumpManager.registerNormalDumpable(TAG, this); } /** * Register callback whenever the given secure settings changes. * * On registration, will call back on the provided handler with the current value of * the setting. */ public void addCallback(@NonNull Uri uri, @NonNull Listener listener) { if (uri == null || listener == null) { return; } synchronized (mListeners) { ArrayList<Listener> currentListeners = mListeners.get(uri); if (currentListeners == null) { currentListeners = new ArrayList<>(); } if (!currentListeners.contains(listener)) { currentListeners.add(listener); } mListeners.put(uri, currentListeners); if (currentListeners.size() == 1) { mSecureSettings.registerContentObserverForUser( uri, false, mContentObserver, mUserTracker.getUserId()); } } mHandler.post(() -> notifyListener(listener, uri)); } public void removeCallback(Uri uri, Listener listener) { synchronized (mListeners) { ArrayList<Listener> currentListeners = mListeners.get(uri); if (currentListeners != null) { currentListeners.remove(listener); } if (currentListeners == null || currentListeners.size() == 0) { mListeners.remove(uri); } if (mListeners.size() == 0) { mSecureSettings.unregisterContentObserver(mContentObserver); } } } @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { synchronized (mListeners) { pw.println("Settings Uri Listener List:"); for (Uri uri : mListeners.keySet()) { pw.println(" Uri=" + uri); for (Listener listener : mListeners.get(uri)) { pw.println(" Listener=" + listener.getClass().getName()); } } } } private void notifyListener(Listener listener, Uri uri) { final String setting = uri == null ? null : uri.getLastPathSegment(); int userId = mUserTracker.getUserId(); listener.onSettingChanged(uri, userId, mSecureSettings.getStringForUser(setting, userId)); } /** * Listener invoked whenever settings are changed. */ public interface Listener { void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value); } } No newline at end of file packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +88 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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) } } packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +6 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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)) Loading Loading @@ -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 { Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +29 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -201,6 +226,7 @@ public class ExpandableNotificationRowController implements NotifViewController FeatureFlags featureFlags, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, NotificationSettingsController settingsController, ExpandableNotificationRowDragController dragController, NotificationDismissibilityProvider dismissibilityProvider, IStatusBarService statusBarService) { Loading Loading @@ -229,6 +255,7 @@ public class ExpandableNotificationRowController implements NotifViewController mFeatureFlags = featureFlags; mPeopleNotificationIdentifier = peopleNotificationIdentifier; mBubblesManagerOptional = bubblesManagerOptional; mSettingsController = settingsController; mDragController = dragController; mMetricsLogger = metricsLogger; mChildrenContainerLogger = childrenContainerLogger; Loading Loading @@ -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); } }); } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +9 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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). Loading Loading @@ -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; } Loading Loading @@ -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); Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java 0 → 100644 +167 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.row; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import javax.inject.Inject; /** * Centralized controller for listening to Secure Settings changes and informing in-process * listeners, on a background thread. */ @SysUISingleton public class NotificationSettingsController implements Dumpable { private final static String TAG = "NotificationSettingsController"; private final UserTracker mUserTracker; private final UserTracker.Callback mCurrentUserTrackerCallback; private final Handler mHandler; private final ContentObserver mContentObserver; private final SecureSettings mSecureSettings; private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>(); @Inject public NotificationSettingsController(UserTracker userTracker, @Background Handler handler, SecureSettings secureSettings, DumpManager dumpManager) { mUserTracker = userTracker; mHandler = handler; mSecureSettings = secureSettings; mContentObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); synchronized (mListeners) { if (mListeners.containsKey(uri)) { for (Listener listener : mListeners.get(uri)) { notifyListener(listener, uri); } } } } }; mCurrentUserTrackerCallback = new UserTracker.Callback() { @Override public void onUserChanged(int newUser, Context userContext) { synchronized (mListeners) { if (mListeners.size() > 0) { mSecureSettings.unregisterContentObserver(mContentObserver); for (Uri uri : mListeners.keySet()) { mSecureSettings.registerContentObserverForUser( uri, false, mContentObserver, newUser); } } } } }; mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler)); dumpManager.registerNormalDumpable(TAG, this); } /** * Register callback whenever the given secure settings changes. * * On registration, will call back on the provided handler with the current value of * the setting. */ public void addCallback(@NonNull Uri uri, @NonNull Listener listener) { if (uri == null || listener == null) { return; } synchronized (mListeners) { ArrayList<Listener> currentListeners = mListeners.get(uri); if (currentListeners == null) { currentListeners = new ArrayList<>(); } if (!currentListeners.contains(listener)) { currentListeners.add(listener); } mListeners.put(uri, currentListeners); if (currentListeners.size() == 1) { mSecureSettings.registerContentObserverForUser( uri, false, mContentObserver, mUserTracker.getUserId()); } } mHandler.post(() -> notifyListener(listener, uri)); } public void removeCallback(Uri uri, Listener listener) { synchronized (mListeners) { ArrayList<Listener> currentListeners = mListeners.get(uri); if (currentListeners != null) { currentListeners.remove(listener); } if (currentListeners == null || currentListeners.size() == 0) { mListeners.remove(uri); } if (mListeners.size() == 0) { mSecureSettings.unregisterContentObserver(mContentObserver); } } } @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { synchronized (mListeners) { pw.println("Settings Uri Listener List:"); for (Uri uri : mListeners.keySet()) { pw.println(" Uri=" + uri); for (Listener listener : mListeners.get(uri)) { pw.println(" Listener=" + listener.getClass().getName()); } } } } private void notifyListener(Listener listener, Uri uri) { final String setting = uri == null ? null : uri.getLastPathSegment(); int userId = mUserTracker.getUserId(); listener.onSettingChanged(uri, userId, mSecureSettings.getStringForUser(setting, userId)); } /** * Listener invoked whenever settings are changed. */ public interface Listener { void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value); } } No newline at end of file
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +88 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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) } }
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +6 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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)) Loading Loading @@ -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 { Loading