New Pipeline: Bring in a couple missing features

* Auto-expansion now works correctly
* Bell icon now appears on alerting notifications
* Assistant feedback icon now populates when appropriate

Fixes: 205463922
Test: atest RowCoordinatorTest PreparationCoordinatorTest RankingCoordinatorTest
Change-Id: Ia181ba2556673003f3edd73a4ecd288945536aac
This commit is contained in:
Jeff DeCew
2021-11-19 14:40:12 +00:00
parent 43018ba232
commit 0d835fd112
12 changed files with 315 additions and 34 deletions

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2021 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
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import javax.inject.Inject
/**
* A class which is used to classify the sections.
* NOTE: This class exists to avoid putting metadata like "isMinimized" on the NotifSection
*/
@SysUISingleton
class SectionClassifier @Inject constructor() {
private lateinit var lowPrioritySections: Set<NotifSectioner>
/**
* Feed the provider the information it needs about which sections should have minimized top
* level views, so that it can calculate the correct minimized state.
*/
fun setMinimizedSections(sections: Collection<NotifSectioner>) {
lowPrioritySections = sections.toSet()
}
/**
* Determine if the given section is minimized
*/
fun isMinimizedSection(section: NotifSection): Boolean {
return lowPrioritySections.contains(section.sectioner)
}
}

View File

@@ -49,6 +49,7 @@ class NotifCoordinatorsImpl @Inject constructor(
groupCountCoordinator: GroupCountCoordinator,
mediaCoordinator: MediaCoordinator,
remoteInputCoordinator: RemoteInputCoordinator,
rowAppearanceCoordinator: RowAppearanceCoordinator,
stackCoordinator: StackCoordinator,
shadeEventCoordinator: ShadeEventCoordinator,
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
@@ -77,6 +78,7 @@ class NotifCoordinatorsImpl @Inject constructor(
mCoordinators.add(groupCountCoordinator)
mCoordinators.add(mediaCoordinator)
mCoordinators.add(remoteInputCoordinator)
mCoordinators.add(rowAppearanceCoordinator)
mCoordinators.add(stackCoordinator)
mCoordinators.add(shadeEventCoordinator)
mCoordinators.add(viewConfigCoordinator)

View File

@@ -20,11 +20,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
@@ -51,7 +51,7 @@ public class RankingCoordinator implements Coordinator {
public static final boolean SHOW_ALL_SECTIONS = false;
private final StatusBarStateController mStatusBarStateController;
private final HighPriorityProvider mHighPriorityProvider;
private final NotifUiAdjustmentProvider mAdjustmentProvider;
private final SectionClassifier mSectionClassifier;
private final NodeController mSilentNodeController;
private final SectionHeaderController mSilentHeaderController;
private final NodeController mAlertingHeaderController;
@@ -62,13 +62,13 @@ public class RankingCoordinator implements Coordinator {
public RankingCoordinator(
StatusBarStateController statusBarStateController,
HighPriorityProvider highPriorityProvider,
NotifUiAdjustmentProvider adjustmentProvider,
SectionClassifier sectionClassifier,
@AlertingHeader NodeController alertingHeaderController,
@SilentHeader SectionHeaderController silentHeaderController,
@SilentHeader NodeController silentNodeController) {
mStatusBarStateController = statusBarStateController;
mHighPriorityProvider = highPriorityProvider;
mAdjustmentProvider = adjustmentProvider;
mSectionClassifier = sectionClassifier;
mAlertingHeaderController = alertingHeaderController;
mSilentNodeController = silentNodeController;
mSilentHeaderController = silentHeaderController;
@@ -77,7 +77,7 @@ public class RankingCoordinator implements Coordinator {
@Override
public void attach(NotifPipeline pipeline) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
mAdjustmentProvider.setLowPrioritySections(Collections.singleton(mMinimizedNotifSectioner));
mSectionClassifier.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
pipeline.addPreGroupFilter(mSuspendedFilter);
pipeline.addPreGroupFilter(mDndVisualEffectsFilter);

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2021 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.collection.coordinator
import android.content.Context
import com.android.systemui.R
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.render.NotifRowController
import javax.inject.Inject
/**
* A small coordinator which updates the notif rows with data related to the current shade after
* they are fully attached.
*/
@CoordinatorScope
class RowAppearanceCoordinator @Inject internal constructor(
context: Context,
private var mAssistantFeedbackController: AssistantFeedbackController,
private var mSectionClassifier: SectionClassifier
) : Coordinator {
private var entryToExpand: NotificationEntry? = null
/**
* `true` if notifications not part of a group should by default be rendered in their
* expanded state. If `false`, then only the first notification will be expanded if
* possible.
*/
private val mAlwaysExpandNonGroupedNotification =
context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications)
override fun attach(pipeline: NotifPipeline) {
pipeline.addOnBeforeRenderListListener(::onBeforeRenderList)
pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry)
}
private fun onBeforeRenderList(list: List<ListEntry>) {
entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
!mSectionClassifier.isMinimizedSection(entry.section!!)
}
}
private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
// If mAlwaysExpandNonGroupedNotification is false, then only expand the
// very first notification and if it's not a child of grouped notifications.
controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification || entry == entryToExpand)
// Show/hide the feedback icon
controller.showFeedbackIcon(
mAssistantFeedbackController.showFeedbackIndicator(entry),
mAssistantFeedbackController.getFeedbackResources(entry)
)
// Show the "alerted" bell icon
controller.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs)
}
}

View File

@@ -17,9 +17,9 @@
package com.android.systemui.statusbar.notification.collection.inflation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import javax.inject.Inject
/**
@@ -27,25 +27,17 @@ import javax.inject.Inject
* to ensure that notifications are reinflated when ranking-derived information changes.
*/
@SysUISingleton
open class NotifUiAdjustmentProvider @Inject constructor() {
private lateinit var lowPrioritySections: Set<NotifSectioner>
/**
* Feed the provider the information it needs about which sections should have minimized top
* level views, so that it can calculate the correct minimized value in the adjustment.
*/
fun setLowPrioritySections(sections: Collection<NotifSectioner>) {
lowPrioritySections = sections.toSet()
}
open class NotifUiAdjustmentProvider @Inject constructor(
private val sectionClassifier: SectionClassifier
) {
private fun isEntryMinimized(entry: NotificationEntry): Boolean {
val section = entry.section ?: error("Entry must have a section to determine if minimized")
val parent = entry.parent ?: error("Entry must have a parent to determine if minimized")
val isLowPrioritySection = lowPrioritySections.contains(section.sectioner)
val isMinimizedSection = sectionClassifier.isMinimizedSection(section)
val isTopLevelEntry = parent == GroupEntry.ROOT_ENTRY
val isGroupSummary = parent.summary == entry
return isLowPrioritySection && (isTopLevelEntry || isGroupSummary)
return isMinimizedSection && (isTopLevelEntry || isGroupSummary)
}
/**

View File

@@ -16,5 +16,27 @@
package com.android.systemui.statusbar.notification.collection.render
import android.util.Pair
/** A view controller for a notification row */
interface NotifRowController
interface NotifRowController {
/**
* This tells the row what the 'default expanded' state should be. Once a user expands or
* contracts a row, that will set the user expanded state, which takes precedence, but
* collapsing the shade and re-opening it will clear the user expanded state. This allows for
* nice auto expansion of the next notification as users dismiss the top notification.
*/
fun setSystemExpanded(systemExpanded: Boolean)
/**
* Sets the timestamp that the notification was last audibly alerted, which the row uses to
* show a bell icon in the header which indicates to the user which notification made a noise.
*/
fun setLastAudiblyAlertedMs(lastAudiblyAlertedMs: Long)
/**
* Sets both whether to show a feedback indicator and which resources to use for the drawable
* and content description.
*/
fun showFeedbackIcon(showFeedbackIndicator: Boolean, feedbackResources: Pair<Int, Int>?)
}

View File

@@ -812,6 +812,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setUntruncatedChildCount(childCount);
}
/** Called after children have been attached to set the expansion states */
public void resetChildSystemExpandedStates() {
if (isSummaryWithChildren()) {
mChildrenContainer.updateExpansionStates();
}
}
/**
* Add a child notification to this view.
*
@@ -2339,6 +2346,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
onExpansionChanged(false /* userAction */, wasExpanded);
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
resetChildSystemExpandedStates();
}
}
}

View File

@@ -21,6 +21,7 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
@@ -279,4 +280,19 @@ public class ExpandableNotificationRowController implements NotifViewController
Log.w(TAG, "Called setUntruncatedChildCount(" + childCount + ") on a leaf row");
}
}
@Override
public void setSystemExpanded(boolean systemExpanded) {
mView.setSystemExpanded(systemExpanded);
}
@Override
public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
mView.setLastAudiblyAlertedMs(lastAudiblyAlertedMs);
}
@Override
public void showFeedbackIcon(boolean show, Pair<Integer, Integer> feedbackResources) {
mView.showFeedbackIcon(show, feedbackResources);
}
}

View File

@@ -473,7 +473,8 @@ public class NotificationChildrenContainer extends ViewGroup {
return result;
}
private void updateExpansionStates() {
/** To be called any time the rows have been updated */
public void updateExpansionStates() {
if (mChildrenExpanded || mUserLocked) {
// we don't modify it the group is expanded or if we are expanding it
return;

View File

@@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
@@ -92,7 +93,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Mock private NotifPipeline mNotifPipeline;
@Mock private IStatusBarService mService;
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
private final TestableAdjustmentProvider mAdjustmentProvider = new TestableAdjustmentProvider();
private final SectionClassifier mSectionClassifier = new SectionClassifier();
private final NotifUiAdjustmentProvider mAdjustmentProvider =
new NotifUiAdjustmentProvider(mSectionClassifier);
@NonNull
private NotificationEntryBuilder getNotificationEntryBuilder() {
@@ -107,7 +110,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mInflationError = new Exception(TEST_MESSAGE);
mErrorManager = new NotifInflationErrorManager();
when(mNotifSection.getSectioner()).thenReturn(mNotifSectioner);
mAdjustmentProvider.setSectionIsLowPriority(false);
setSectionIsLowPriority(false);
PreparationCoordinator coordinator = new PreparationCoordinator(
mock(PreparationCoordinatorLogger.class),
@@ -222,7 +225,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
// WHEN notification moves to a min priority section
mAdjustmentProvider.setSectionIsLowPriority(true);
setSectionIsLowPriority(true);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
// THEN we rebind it
@@ -236,7 +239,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testMinimizedEntryMovedIntoGroupWillRebindViews() {
// GIVEN an inflated, minimized notification
mAdjustmentProvider.setSectionIsLowPriority(true);
setSectionIsLowPriority(true);
mCollectionListener.onEntryAdded(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
@@ -471,11 +474,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
private static final int TEST_CHILD_BIND_CUTOFF = 9;
private static final int TEST_MAX_GROUP_DELAY = 100;
private class TestableAdjustmentProvider extends NotifUiAdjustmentProvider {
private void setSectionIsLowPriority(boolean lowPriority) {
setLowPrioritySections(lowPriority
? Collections.singleton(mNotifSection.getSectioner())
: Collections.emptyList());
}
private void setSectionIsLowPriority(boolean minimized) {
mSectionClassifier.setMinimizedSections(minimized
? Collections.singleton(mNotifSection.getSectioner())
: Collections.emptyList());
}
}

View File

@@ -22,6 +22,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -39,11 +40,11 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
@@ -67,7 +68,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private NotifUiAdjustmentProvider mAdjustmentProvider;
@Mock private SectionClassifier mSectionClassifier;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NodeController mAlertingHeaderController;
@Mock private NodeController mSilentNodeController;
@@ -91,7 +92,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mRankingCoordinator = new RankingCoordinator(
mStatusBarStateController,
mHighPriorityProvider,
mAdjustmentProvider,
mSectionClassifier,
mAlertingHeaderController,
mSilentHeaderController,
mSilentNodeController);
@@ -99,6 +100,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mEntry.setRanking(getRankingForUnfilteredNotif().build());
mRankingCoordinator.attach(mNotifPipeline);
verify(mSectionClassifier).setMinimizedSections(any());
verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture());
mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2021 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.collection.coordinator
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.util.Pair
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.render.NotifRowController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class RowAppearanceCoordinatorTest : SysuiTestCase() {
private lateinit var coordinator: RowAppearanceCoordinator
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
private lateinit var afterRenderEntryListener: OnAfterRenderEntryListener
private lateinit var entry1: NotificationEntry
private lateinit var entry2: NotificationEntry
@Mock private lateinit var pipeline: NotifPipeline
@Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
@Mock private lateinit var sectionClassifier: SectionClassifier
@Mock private lateinit var section1: NotifSection
@Mock private lateinit var section2: NotifSection
@Mock private lateinit var controller1: NotifRowController
@Mock private lateinit var controller2: NotifRowController
@Before
fun setUp() {
initMocks(this)
coordinator = RowAppearanceCoordinator(
mContext,
assistantFeedbackController,
sectionClassifier
)
coordinator.attach(pipeline)
beforeRenderListListener = withArgCaptor {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
afterRenderEntryListener = withArgCaptor {
verify(pipeline).addOnAfterRenderEntryListener(capture())
}
whenever(assistantFeedbackController.showFeedbackIndicator(any())).thenReturn(true)
whenever(assistantFeedbackController.getFeedbackResources(any())).thenReturn(Pair(1, 2))
entry1 = NotificationEntryBuilder().setSection(section1).setLastAudiblyAlertedMs(17).build()
entry2 = NotificationEntryBuilder().setSection(section2).build()
}
@Test
fun testSetSystemExpandedOnlyOnFirst() {
whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false)
whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false)
beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2))
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).setSystemExpanded(eq(true))
afterRenderEntryListener.onAfterRenderEntry(entry2, controller2)
verify(controller2).setSystemExpanded(eq(false))
}
@Test
fun testSetSystemExpandedNeverIfMinimized() {
whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true)
whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true)
beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2))
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).setSystemExpanded(eq(false))
afterRenderEntryListener.onAfterRenderEntry(entry2, controller2)
verify(controller2).setSystemExpanded(eq(false))
}
@Test
fun testSetLastAudiblyAlerted() {
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).setLastAudiblyAlertedMs(eq(17.toLong()))
}
@Test
fun testShowFeedbackIcon() {
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).showFeedbackIcon(eq(true), eq(Pair(1, 2)))
}
}