Enabled x button for clearable silent Notif[s] am: 5e6673f96a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16150089

Change-Id: I3da83d270dd114a848181b0c88af921b99f92326
This commit is contained in:
Jay Aliomer
2021-10-30 00:18:27 +00:00
committed by Automerger Merge Worker
10 changed files with 133 additions and 15 deletions

View File

@@ -36,7 +36,7 @@ public abstract class ListEntry {
private final ListAttachState mPreviousAttachState = ListAttachState.create();
private final ListAttachState mAttachState = ListAttachState.create();
ListEntry(String key, long creationTime) {
protected ListEntry(String key, long creationTime) {
mKey = key;
mCreationTime = creationTime;
}

View File

@@ -78,6 +78,8 @@ public class ShadeListBuilder implements Dumpable {
private final SystemClock mSystemClock;
private final ShadeListBuilderLogger mLogger;
private final NotificationInteractionTracker mInteractionTracker;
// used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
private List<ListEntry> mNotifList = new ArrayList<>();
private List<ListEntry> mNewNotifList = new ArrayList<>();
@@ -356,7 +358,7 @@ public class ShadeListBuilder implements Dumpable {
// section by our list of custom comparators
dispatchOnBeforeSort(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_SORTING);
sortList();
sortListAndNotifySections();
// Step 7: Lock in our group structure and log anything that's changed since the last run
mPipelineState.incrementTo(STATE_FINALIZING);
@@ -382,6 +384,22 @@ public class ShadeListBuilder implements Dumpable {
mIterationCount++;
}
private void notifySectionEntriesUpdated() {
NotifSection currentSection = null;
mTempSectionMembers.clear();
for (int i = 0; i < mNotifList.size(); i++) {
ListEntry currentEntry = mNotifList.get(i);
if (currentSection != currentEntry.getSection()) {
if (currentSection != null) {
currentSection.getSectioner().onEntriesUpdated(mTempSectionMembers);
mTempSectionMembers.clear();
}
currentSection = currentEntry.getSection();
}
mTempSectionMembers.add(currentEntry);
}
}
/**
* Points mNotifList to the list stored in mNewNotifList.
* Reuses the (emptied) mNotifList as mNewNotifList.
@@ -713,7 +731,7 @@ public class ShadeListBuilder implements Dumpable {
}
}
private void sortList() {
private void sortListAndNotifySections() {
// Assign sections to top-level elements and sort their children
for (ListEntry entry : mNotifList) {
NotifSection section = applySections(entry);
@@ -728,6 +746,9 @@ public class ShadeListBuilder implements Dumpable {
// Finally, sort all top-level elements
mNotifList.sort(mTopLevelComparator);
// notify sections since the list is sorted now
notifySectionEntriesUpdated();
}
private void freeEmptyGroups() {
@@ -937,7 +958,6 @@ public class ShadeListBuilder implements Dumpable {
}
entry.getAttachState().setSection(finalSection);
return finalSection;
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
@@ -27,9 +28,12 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import java.util.List;
import javax.inject.Inject;
/**
@@ -44,7 +48,8 @@ public class RankingCoordinator implements Coordinator {
public static final boolean SHOW_ALL_SECTIONS = false;
private final StatusBarStateController mStatusBarStateController;
private final HighPriorityProvider mHighPriorityProvider;
private final NodeController mSilentHeaderController;
private final NodeController mSilentNodeController;
private final SectionHeaderController mSilentHeaderController;
private final NodeController mAlertingHeaderController;
@Inject
@@ -52,10 +57,12 @@ public class RankingCoordinator implements Coordinator {
StatusBarStateController statusBarStateController,
HighPriorityProvider highPriorityProvider,
@AlertingHeader NodeController alertingHeaderController,
@SilentHeader NodeController silentHeaderController) {
@SilentHeader SectionHeaderController silentHeaderController,
@SilentHeader NodeController silentNodeController) {
mStatusBarStateController = statusBarStateController;
mHighPriorityProvider = highPriorityProvider;
mAlertingHeaderController = alertingHeaderController;
mSilentNodeController = silentNodeController;
mSilentHeaderController = silentHeaderController;
}
@@ -101,7 +108,19 @@ public class RankingCoordinator implements Coordinator {
@Nullable
@Override
public NodeController getHeaderNodeController() {
return mSilentHeaderController;
return mSilentNodeController;
}
@Nullable
@Override
public void onEntriesUpdated(@NonNull List<ListEntry> entries) {
for (int i = 0; i < entries.size(); i++) {
if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
mSilentHeaderController.setClearSectionEnabled(true);
return;
}
}
mSilentHeaderController.setClearSectionEnabled(false);
}
};

View File

@@ -23,6 +23,8 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.NodeSpec;
import java.util.List;
/**
* Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
*/
@@ -46,4 +48,10 @@ public abstract class NotifSectioner extends Pluggable<NotifSectioner> {
public @Nullable NodeController getHeaderNodeController() {
return null;
}
/**
* Notify of children of this section being updated
* @param entries of this section that are borrowed (must clone to store)
*/
public void onEntriesUpdated(List<ListEntry> entries) {}
}

View File

@@ -33,7 +33,8 @@ import javax.inject.Inject
interface SectionHeaderController {
fun reinflateView(parent: ViewGroup)
val headerView: SectionHeaderView?
fun setOnClearAllClickListener(listener: View.OnClickListener)
fun setClearSectionEnabled(enabled: Boolean)
fun setOnClearSectionClickListener(listener: View.OnClickListener)
}
@SectionHeaderScope
@@ -46,6 +47,7 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor(
) : NodeController, SectionHeaderController {
private var _view: SectionHeaderView? = null
private var clearAllButtonEnabled = false
private var clearAllClickListener: View.OnClickListener? = null
private val onHeaderClickListener = View.OnClickListener {
activityStarter.startActivity(
@@ -76,12 +78,18 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor(
parent.addView(inflated, oldPos)
}
_view = inflated
_view?.setClearSectionButtonEnabled(clearAllButtonEnabled)
}
override val headerView: SectionHeaderView?
get() = _view
override fun setOnClearAllClickListener(listener: View.OnClickListener) {
override fun setClearSectionEnabled(enabled: Boolean) {
clearAllButtonEnabled = enabled
_view?.setClearSectionButtonEnabled(enabled)
}
override fun setOnClearSectionClickListener(listener: View.OnClickListener) {
clearAllClickListener = listener
_view?.setOnClearAllClickListener(listener)
}

View File

@@ -350,7 +350,7 @@ class NotificationSectionsManager @Inject internal constructor(
silentHeaderView?.run {
val hasActiveClearableNotifications = this@NotificationSectionsManager.parent
.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
setAreThereDismissableGentleNotifs(hasActiveClearableNotifications)
setClearSectionButtonEnabled(hasActiveClearableNotifications)
}
}

View File

@@ -813,7 +813,7 @@ public class NotificationStackScrollLayoutController {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
mSilentHeaderController.setOnClearAllClickListener(v -> clearSilentNotifications());
mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
}
private boolean isInVisibleLocation(NotificationEntry entry) {

View File

@@ -85,8 +85,12 @@ public class SectionHeaderView extends StackScrollerDecorView {
return true;
}
void setAreThereDismissableGentleNotifs(boolean areThereDismissableGentleNotifs) {
mClearAllButton.setVisibility(areThereDismissableGentleNotifs ? View.VISIBLE : View.GONE);
/**
* Show the clear section [X] button
* @param enabled
*/
public void setClearSectionButtonEnabled(boolean enabled) {
mClearAllButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
}
@Override

View File

@@ -33,6 +33,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static java.util.Collections.singletonList;
@@ -42,6 +43,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -78,6 +80,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
@SmallTest
@@ -607,6 +610,30 @@ public class ShadeListBuilderTest extends SysuiTestCase {
assertEquals(promoter2, mEntrySet.get(3).getNotifPromoter());
}
@Test
public void testNotifSectionsChildrenUpdated() {
AtomicBoolean validChildren = new AtomicBoolean(false);
final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1) {
@Nullable
@Override
public void onEntriesUpdated(List<ListEntry> entries) {
super.onEntriesUpdated(entries);
validChildren.set(entries.size() == 2);
}
});
mListBuilder.setSectioners(Arrays.asList(pkg1Sectioner));
addNotif(0, PACKAGE_4);
addNotif(1, PACKAGE_1);
addNotif(2, PACKAGE_1);
addNotif(3, PACKAGE_3);
dispatchBuild();
verify(pkg1Sectioner, times(1)).onEntriesUpdated(any());
assertTrue(validChildren.get());
}
@Test
public void testNotifSections() {
// GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide

View File

@@ -21,17 +21,22 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.RankingBuilder;
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;
@@ -39,6 +44,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import org.junit.Before;
import org.junit.Test;
@@ -46,8 +52,11 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class RankingCoordinatorTest extends SysuiTestCase {
@@ -56,7 +65,8 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NodeController mAlertingHeaderController;
@Mock private NodeController mSilentHeaderController;
@Mock private NodeController mSilentNodeController;
@Mock private SectionHeaderController mSilentHeaderController;
@Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor;
@@ -72,7 +82,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
RankingCoordinator rankingCoordinator = new RankingCoordinator(
mStatusBarStateController, mHighPriorityProvider, mAlertingHeaderController,
mSilentHeaderController);
mSilentHeaderController, mSilentNodeController);
mEntry = new NotificationEntryBuilder().build();
rankingCoordinator.attach(mNotifPipeline);
@@ -84,6 +94,28 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mSilentSectioner = rankingCoordinator.getSilentSectioner();
}
@Test
public void testSilentHeaderClearableChildrenUpdate() {
StatusBarNotification sbn = Mockito.mock(StatusBarNotification.class);
Mockito.doReturn("key").when(sbn).getKey();
Mockito.doReturn(Mockito.mock(Notification.class)).when(sbn).getNotification();
NotificationEntry entry = new NotificationEntryBuilder().setSbn(sbn).build();
ListEntry listEntry = new ListEntry("key", 0L) {
@Nullable
@Override
public NotificationEntry getRepresentativeEntry() {
return entry;
}
};
Mockito.doReturn(true).when(sbn).isClearable();
mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry));
verify(mSilentHeaderController).setClearSectionEnabled(eq(true));
Mockito.doReturn(false).when(sbn).isClearable();
mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry));
verify(mSilentHeaderController).setClearSectionEnabled(eq(false));
}
@Test
public void testUnfilteredState() {
// GIVEN no suppressed visual effects + app not suspended