diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt index e2b01ffe59b8d..d7b391ff03e49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt @@ -22,6 +22,7 @@ import android.provider.DeviceConfig import com.android.internal.annotations.VisibleForTesting import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE @@ -52,12 +53,14 @@ class NotificationSectionsFeatureManager @Inject constructor( fun getNotificationBuckets(): IntArray { return when { isFilteringEnabled() && isMediaControlsEnabled() -> - intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_PEOPLE, BUCKET_ALERTING, - BUCKET_SILENT) + intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS, + BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT) !isFilteringEnabled() && isMediaControlsEnabled() -> - intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_ALERTING, BUCKET_SILENT) + intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS, + BUCKET_ALERTING, BUCKET_SILENT) isFilteringEnabled() && !isMediaControlsEnabled() -> - intArrayOf(BUCKET_HEADS_UP, BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT) + intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_PEOPLE, + BUCKET_ALERTING, BUCKET_SILENT) NotificationUtils.useNewInterruptionModel(context) -> intArrayOf(BUCKET_ALERTING, BUCKET_SILENT) else -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index c78370ec46431..9ac42298e539a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT @@ -162,6 +163,8 @@ open class NotificationRankingManager @Inject constructor( val isMedia = isImportantMedia(entry) val isSystemMax = entry.isSystemMax() return when { + entry.sbn.notification.isForegroundService && entry.sbn.notification.isColorized -> + BUCKET_FOREGROUND_SERVICE usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON -> BUCKET_PEOPLE isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java index 2a257f6198d7b..f2c6fb007de92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java @@ -29,12 +29,10 @@ import android.content.Intent; import android.provider.Settings; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.media.KeyguardMediaController; -import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; @@ -271,8 +269,6 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section boolean peopleNotifsPresent = false; int currentMediaControlsIdx = -1; - // Currently, just putting media controls in the front and incrementing the position based - // on the number of heads-up notifs. int mediaControlsTarget = usingMediaControls ? 0 : -1; int currentIncomingHeaderIdx = -1; int incomingHeaderTarget = -1; @@ -343,6 +339,11 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section mediaControlsTarget++; } break; + case BUCKET_FOREGROUND_SERVICE: + if (mediaControlsTarget != -1) { + mediaControlsTarget++; + } + break; case BUCKET_PEOPLE: peopleNotifsPresent = true; if (showHeaders && peopleHeaderTarget == -1) { @@ -412,7 +413,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section adjustHeaderVisibilityAndPosition( peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx); adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx); - adjustViewPosition(incomingHeaderTarget, mIncomingHeader, currentIncomingHeaderIdx); + adjustHeaderVisibilityAndPosition(incomingHeaderTarget, mIncomingHeader, + currentIncomingHeaderIdx); // Update headers to reflect state of section contents mGentleHeader.setAreThereDismissableGentleNotifs( @@ -425,45 +427,29 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section private void adjustHeaderVisibilityAndPosition( int targetPosition, StackScrollerDecorView header, int currentPosition) { - if (targetPosition == -1) { - if (currentPosition != -1) { - mParent.removeView(header); - } - } else { - if (currentPosition == -1) { - // If the header is animating away, it will still have a parent, so detach it first - // TODO: We should really cancel the active animations here. This will happen - // automatically when the view's intro animation starts, but it's a fragile link. - if (header.getTransientContainer() != null) { - header.getTransientContainer().removeTransientView(header); - header.setTransientContainer(null); - } - header.setContentVisible(true); - mParent.addView(header, targetPosition); - } else { - mParent.changeViewPosition(header, targetPosition); - } + adjustViewPosition(targetPosition, header, currentPosition); + if (targetPosition != -1 && currentPosition == -1) { + header.setContentVisible(true); } } - private void adjustViewPosition(int targetPosition, ExpandableView header, - int currentPosition) { + private void adjustViewPosition(int targetPosition, ExpandableView view, int currentPosition) { if (targetPosition == -1) { if (currentPosition != -1) { - mParent.removeView(header); + mParent.removeView(view); } } else { if (currentPosition == -1) { // If the header is animating away, it will still have a parent, so detach it first // TODO: We should really cancel the active animations here. This will happen // automatically when the view's intro animation starts, but it's a fragile link. - if (header.getTransientContainer() != null) { - header.getTransientContainer().removeTransientView(header); - header.setTransientContainer(null); + if (view.getTransientContainer() != null) { + view.getTransientContainer().removeTransientView(view); + view.setTransientContainer(null); } - mParent.addView(header, targetPosition); + mParent.addView(view, targetPosition); } else { - mParent.changeViewPosition(header, targetPosition); + mParent.changeViewPosition(view, targetPosition); } } } @@ -557,6 +543,11 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section return mMediaControlsView; } + @VisibleForTesting + ExpandableView getIncomingHeaderView() { + return mIncomingHeader; + } + @VisibleForTesting void setPeopleHubVisible(boolean visible) { mPeopleHubVisible = visible; @@ -602,6 +593,7 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section @Retention(SOURCE) @IntDef(prefix = { "BUCKET_" }, value = { BUCKET_HEADS_UP, + BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS, BUCKET_PEOPLE, BUCKET_ALERTING, @@ -609,8 +601,9 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section }) public @interface PriorityBucket {} public static final int BUCKET_HEADS_UP = 0; - public static final int BUCKET_MEDIA_CONTROLS = 1; - public static final int BUCKET_PEOPLE = 2; - public static final int BUCKET_ALERTING = 3; - public static final int BUCKET_SILENT = 4; + public static final int BUCKET_FOREGROUND_SERVICE = 1; + public static final int BUCKET_MEDIA_CONTROLS = 2; + public static final int BUCKET_PEOPLE = 3; + public static final int BUCKET_ALERTING = 4; + public static final int BUCKET_SILENT = 5; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index f4b5a5b7f3cf8..ede66cd3b5695 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -19,15 +19,19 @@ package com.android.systemui.statusbar.notification.stack; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE; import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP; import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE; import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -62,6 +66,9 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.ArrayList; +import java.util.List; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -83,10 +90,23 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { @Before public void setUp() { - when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(2); - when(mNotificationRowComponent.getActivatableNotificationViewController()).thenReturn( - mActivatableNotificationViewController - ); + when(mSectionsFeatureManager.getNumberOfBuckets()).thenAnswer( + invocation -> { + int count = 2; + if (mSectionsFeatureManager.isFilteringEnabled()) { + count = 5; + } + if (mSectionsFeatureManager.isMediaControlsEnabled()) { + if (!mSectionsFeatureManager.isFilteringEnabled()) { + count = 5; + } else { + count += 1; + } + } + return count; + }); + when(mNotificationRowComponent.getActivatableNotificationViewController()) + .thenReturn(mActivatableNotificationViewController); mSectionsManager = new NotificationSectionsManager( mActivityStarterDelegate, @@ -102,6 +122,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext)); when(mNssl.indexOfChild(any(View.class))).thenReturn(-1); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); + } @Test(expected = IllegalStateException.class) @@ -336,6 +357,58 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0); } + @Test + public void testPeopleFiltering_HunWhilePeopleVisible() { + enablePeopleFiltering(); + + setupMockStack( + ChildType.PEOPLE_HEADER, + ChildType.HEADS_UP, + ChildType.PERSON, + ChildType.ALERTING_HEADER, + ChildType.GENTLE_HEADER, + ChildType.GENTLE + ); + mSectionsManager.updateSectionBoundaries(); + + verifyMockStack( + ChildType.INCOMING_HEADER, + ChildType.HEADS_UP, + ChildType.PEOPLE_HEADER, + ChildType.PERSON, + ChildType.GENTLE_HEADER, + ChildType.GENTLE + ); + } + + @Test + public void testPeopleFiltering_Fsn() { + enablePeopleFiltering(); + + setupMockStack( + ChildType.INCOMING_HEADER, + ChildType.HEADS_UP, + ChildType.PEOPLE_HEADER, + ChildType.FSN, + ChildType.PERSON, + ChildType.ALERTING, + ChildType.GENTLE + ); + mSectionsManager.updateSectionBoundaries(); + + verifyMockStack( + ChildType.INCOMING_HEADER, + ChildType.HEADS_UP, + ChildType.FSN, + ChildType.PEOPLE_HEADER, + ChildType.PERSON, + ChildType.ALERTING_HEADER, + ChildType.ALERTING, + ChildType.GENTLE_HEADER, + ChildType.GENTLE + ); + } + @Test public void testMediaControls_AddWhenEnterKeyguard() { enableMediaControls(); @@ -356,30 +429,28 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { enableMediaControls(); // GIVEN a stack that doesn't include media controls but includes HEADS_UP - setStackState(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER, + setupMockStack(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE); // WHEN we go back to the keyguard when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); mSectionsManager.updateSectionBoundaries(); - // Then the media controls are added after HEADS_UP - verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 1); + verifyMockStack(ChildType.HEADS_UP, ChildType.MEDIA_CONTROLS, ChildType.ALERTING, + ChildType.GENTLE); } private void enablePeopleFiltering() { when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true); - when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4); } private void enableMediaControls() { when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true); - when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4); } private enum ChildType { - MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING, - GENTLE, OTHER + INCOMING_HEADER, MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, + FSN, PERSON, ALERTING, GENTLE, OTHER } private void setStackState(ChildType... children) { @@ -387,6 +458,9 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { for (int i = 0; i < children.length; i++) { View child; switch (children[i]) { + case INCOMING_HEADER: + child = mSectionsManager.getIncomingHeaderView(); + break; case MEDIA_CONTROLS: child = mSectionsManager.getMediaControlsView(); break; @@ -402,6 +476,9 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { case HEADS_UP: child = mockNotification(BUCKET_HEADS_UP); break; + case FSN: + child = mockNotification(BUCKET_FOREGROUND_SERVICE); + break; case PERSON: child = mockNotification(BUCKET_PEOPLE); break; @@ -432,4 +509,127 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { when(notifRow.getParent()).thenReturn(mNssl); return notifRow; } + + private void verifyMockStack(ChildType... expected) { + final List actual = new ArrayList<>(); + int childCount = mNssl.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = mNssl.getChildAt(i); + if (child == mSectionsManager.getIncomingHeaderView()) { + actual.add(ChildType.INCOMING_HEADER); + continue; + } + if (child == mSectionsManager.getMediaControlsView()) { + actual.add(ChildType.MEDIA_CONTROLS); + continue; + } + if (child == mSectionsManager.getPeopleHeaderView()) { + actual.add(ChildType.PEOPLE_HEADER); + continue; + } + if (child == mSectionsManager.getAlertingHeaderView()) { + actual.add(ChildType.ALERTING_HEADER); + continue; + } + if (child == mSectionsManager.getGentleHeaderView()) { + actual.add(ChildType.GENTLE_HEADER); + continue; + } + if (child instanceof ExpandableNotificationRow) { + switch (((ExpandableNotificationRow) child).getEntry().getBucket()) { + case BUCKET_HEADS_UP: + actual.add(ChildType.HEADS_UP); + break; + case BUCKET_FOREGROUND_SERVICE: + actual.add(ChildType.FSN); + break; + case BUCKET_PEOPLE: + actual.add(ChildType.PERSON); + break; + case BUCKET_ALERTING: + actual.add(ChildType.ALERTING); + break; + case BUCKET_SILENT: + actual.add(ChildType.GENTLE); + break; + default: + actual.add(ChildType.OTHER); + break; + } + continue; + } + actual.add(ChildType.OTHER); + } + assertThat(actual).containsExactly((Object[]) expected).inOrder(); + } + + private void setupMockStack(ChildType... childTypes) { + final List children = new ArrayList<>(); + when(mNssl.getChildCount()).thenAnswer(invocation -> children.size()); + when(mNssl.getChildAt(anyInt())) + .thenAnswer(invocation -> children.get(invocation.getArgument(0))); + when(mNssl.indexOfChild(any())) + .thenAnswer(invocation -> children.indexOf(invocation.getArgument(0))); + doAnswer(invocation -> { + View child = invocation.getArgument(0); + int index = invocation.getArgument(1); + children.add(index, child); + return null; + }).when(mNssl).addView(any(), anyInt()); + doAnswer(invocation -> { + View child = invocation.getArgument(0); + children.remove(child); + return null; + }).when(mNssl).removeView(any()); + doAnswer(invocation -> { + View child = invocation.getArgument(0); + int newIndex = invocation.getArgument(1); + children.remove(child); + children.add(newIndex, child); + return null; + }).when(mNssl).changeViewPosition(any(), anyInt()); + for (ChildType childType : childTypes) { + View child; + switch (childType) { + case INCOMING_HEADER: + child = mSectionsManager.getIncomingHeaderView(); + break; + case MEDIA_CONTROLS: + child = mSectionsManager.getMediaControlsView(); + break; + case PEOPLE_HEADER: + child = mSectionsManager.getPeopleHeaderView(); + break; + case ALERTING_HEADER: + child = mSectionsManager.getAlertingHeaderView(); + break; + case GENTLE_HEADER: + child = mSectionsManager.getGentleHeaderView(); + break; + case HEADS_UP: + child = mockNotification(BUCKET_HEADS_UP); + break; + case FSN: + child = mockNotification(BUCKET_FOREGROUND_SERVICE); + break; + case PERSON: + child = mockNotification(BUCKET_PEOPLE); + break; + case ALERTING: + child = mockNotification(BUCKET_ALERTING); + break; + case GENTLE: + child = mockNotification(BUCKET_SILENT); + break; + case OTHER: + child = mock(View.class); + when(child.getVisibility()).thenReturn(View.VISIBLE); + when(child.getParent()).thenReturn(mNssl); + break; + default: + throw new RuntimeException("Unknown ChildType: " + childType); + } + children.add(child); + } + } }