diff --git a/packages/SystemUI/res/drawable-night/status_bar_notification_section_header_clear_btn.xml b/packages/SystemUI/res/drawable-night/status_bar_notification_section_header_clear_btn.xml new file mode 100644 index 0000000000000..c471b38306d5f --- /dev/null +++ b/packages/SystemUI/res/drawable-night/status_bar_notification_section_header_clear_btn.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml b/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml new file mode 100644 index 0000000000000..15f14d8af89b5 --- /dev/null +++ b/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/packages/SystemUI/res/layout/status_bar_notification_section_header.xml b/packages/SystemUI/res/layout/status_bar_notification_section_header.xml index 8fe80cb8956e8..2b210064023be 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_section_header.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_section_header.xml @@ -50,9 +50,10 @@ /> 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 ed4a6ab9c69bf..82599f02ddf10 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 @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; + import android.annotation.Nullable; import android.content.Context; import android.content.Intent; @@ -42,6 +44,7 @@ class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvide private SectionHeaderView mGentleHeader; private boolean mGentleHeaderVisible = false; + @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener; NotificationSectionsManager( NotificationStackScrollLayout parent, @@ -70,12 +73,18 @@ class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvide mGentleHeader = (SectionHeaderView) LayoutInflater.from(context).inflate( R.layout.status_bar_notification_section_header, mParent, false); mGentleHeader.setOnHeaderClickListener(this::onGentleHeaderClick); + mGentleHeader.setOnClearAllClickListener(this::onClearGentleNotifsClick); if (oldPos != -1) { mParent.addView(mGentleHeader, oldPos); } } + /** Listener for when the "clear all" buttton is clciked on the gentle notification header. */ + void setOnClearGentleNotifsClickListener(View.OnClickListener listener) { + mOnClearGentleNotifsClickListener = listener; + } + /** Must be called whenever the UI mode changes (i.e. when we enter night mode). */ void onUiModeChanged() { mGentleHeader.onUiModeChanged(); @@ -111,6 +120,9 @@ class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvide } adjustGentleHeaderVisibilityAndPosition(firstGentleNotifIndex); + + mGentleHeader.setAreThereDismissableGentleNotifs( + mParent.hasActiveClearableNotifications(ROWS_GENTLE)); } private void adjustGentleHeaderVisibilityAndPosition(int firstGentleNotifIndex) { @@ -225,4 +237,10 @@ class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvide true, Intent.FLAG_ACTIVITY_SINGLE_TOP); } + + private void onClearGentleNotifsClick(View v) { + if (mOnClearGentleNotifsClickListener != null) { + mOnClearGentleNotifsClickListener.onClick(v); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 81f3eeef491e9..5bd6cab1809be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -23,10 +23,13 @@ import static com.android.systemui.statusbar.notification.stack.StackStateAnimat import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeAnimator; import android.animation.ValueAnimator; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WallpaperManager; @@ -143,6 +146,7 @@ import com.android.systemui.tuner.TunerService; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -525,12 +529,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } mAmbientPulseManager = ambientPulseManager; + mSectionsManager = new NotificationSectionsManager( this, activityStarter, NotificationUtils.useNewInterruptionModel(context)); mSectionsManager.inflateViews(context); + mSectionsManager.setOnClearGentleNotifsClickListener(v -> { + // Leave the shade open if there will be other notifs left over to clear + final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY); + clearNotifications(ROWS_GENTLE, closeShade); + }); + mAmbientState = new AmbientState(context, mSectionsManager); mRoundnessManager = notificationRoundnessManager; mBgColor = context.getColor(R.color.notification_shade_background_color); @@ -672,7 +683,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @VisibleForTesting @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void updateFooter() { - boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(); + boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL); boolean showFooterView = (showDismissView || mEntryManager.getNotificationData().getActiveNotifications().size() != 0) && mStatusBarState != StatusBarState.KEYGUARD @@ -685,14 +696,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd * Return whether there are any clearable notifications */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public boolean hasActiveClearableNotifications() { + public boolean hasActiveClearableNotifications(@SelectedRows int selection) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (!(child instanceof ExpandableNotificationRow)) { continue; } - if (((ExpandableNotificationRow) child).canViewBeDismissed()) { + final ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (row.canViewBeDismissed() && matchesSelection(row, selection)) { return true; } } @@ -1695,11 +1707,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd return mScrollingEnabled; } - @ShadeViewRefactor(RefactorComponent.ADAPTER) - private boolean canChildBeDismissed(View v) { - return StackScrollAlgorithm.canChildBeDismissed(v); - } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean onKeyguard() { return mStatusBarState == StatusBarState.KEYGUARD; @@ -4917,7 +4924,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } else { child.setMinClipTopAmount(0); } - previousChildWillBeDismissed = canChildBeDismissed(child); + previousChildWillBeDismissed = StackScrollAlgorithm.canChildBeDismissed(child); } } @@ -5473,7 +5480,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void clearAllNotifications() { + private void clearNotifications( + @SelectedRows int selection, + boolean closeShade) { // animate-swipe all dismissable notifications, then animate the shade closed int numChildren = getChildCount(); @@ -5485,7 +5494,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd ExpandableNotificationRow row = (ExpandableNotificationRow) child; boolean parentVisible = false; boolean hasClipBounds = child.getClipBounds(mTmpRect); - if (canChildBeDismissed(child)) { + if (includeChildInDismissAll(row, selection)) { viewsToRemove.add(row); if (child.getVisibility() == View.VISIBLE && (!hasClipBounds || mTmpRect.height() > 0)) { @@ -5499,51 +5508,94 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd List children = row.getNotificationChildren(); if (children != null) { for (ExpandableNotificationRow childRow : children) { - viewsToRemove.add(childRow); - if (parentVisible && row.areChildrenExpanded() - && canChildBeDismissed(childRow)) { - hasClipBounds = childRow.getClipBounds(mTmpRect); - if (childRow.getVisibility() == View.VISIBLE - && (!hasClipBounds || mTmpRect.height() > 0)) { - viewsToHide.add(childRow); + if (includeChildInDismissAll(row, selection)) { + viewsToRemove.add(childRow); + if (parentVisible && row.areChildrenExpanded()) { + hasClipBounds = childRow.getClipBounds(mTmpRect); + if (childRow.getVisibility() == View.VISIBLE + && (!hasClipBounds || mTmpRect.height() > 0)) { + viewsToHide.add(childRow); + } } } } } } } + if (viewsToRemove.isEmpty()) { - mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + if (closeShade) { + mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + } return; } - mShadeController.addPostCollapseAction(() -> { - setDismissAllInProgress(false); + performDismissAllAnimations(viewsToHide, closeShade, () -> { for (ExpandableNotificationRow rowToRemove : viewsToRemove) { - if (canChildBeDismissed(rowToRemove)) { - mEntryManager.removeNotification(rowToRemove.getEntry().key, null /* ranking */, - NotificationListenerService.REASON_CANCEL_ALL); + if (StackScrollAlgorithm.canChildBeDismissed(rowToRemove)) { + if (selection == ROWS_ALL) { + // TODO: This is a listener method; we shouldn't be calling it. Can we just + // call performRemoveNotification as below? + mEntryManager.removeNotification( + rowToRemove.getEntry().key, + null /* ranking */, + NotificationListenerService.REASON_CANCEL_ALL); + } else { + mEntryManager.performRemoveNotification( + rowToRemove.getEntry().notification, + NotificationListenerService.REASON_CANCEL_ALL); + } } else { rowToRemove.resetTranslation(); } } - try { - mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId()); - } catch (Exception ex) { + if (selection == ROWS_ALL) { + try { + mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId()); + } catch (Exception ex) { + } } }); - - performDismissAllAnimations(viewsToHide); } + private boolean includeChildInDismissAll( + ExpandableNotificationRow row, + @SelectedRows int selection) { + return StackScrollAlgorithm.canChildBeDismissed(row) && matchesSelection(row, selection); + } + + /** + * Given a list of rows, animates them away in a staggered fashion as if they were dismissed. + * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete + * handler. + * + * @param hideAnimatedList List of rows to animated away. Should only be views that are + * currently visible, or else the stagger will look funky. + * @param closeShade Whether to close the shade after the stagger animation completes. + * @param onAnimationComplete Called after the entire animation completes (including the shade + * closing if appropriate). The rows must be dismissed for real here. + */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void performDismissAllAnimations(ArrayList hideAnimatedList) { - Runnable animationFinishAction = () -> { - mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + private void performDismissAllAnimations( + final ArrayList hideAnimatedList, + final boolean closeShade, + final Runnable onAnimationComplete) { + + final Runnable onSlideAwayAnimationComplete = () -> { + if (closeShade) { + mShadeController.addPostCollapseAction(() -> { + setDismissAllInProgress(false); + onAnimationComplete.run(); + }); + mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + } else { + setDismissAllInProgress(false); + onAnimationComplete.run(); + } }; if (hideAnimatedList.isEmpty()) { - animationFinishAction.run(); + onSlideAwayAnimationComplete.run(); return; } @@ -5560,7 +5612,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd View view = hideAnimatedList.get(i); Runnable endRunnable = null; if (i == 0) { - endRunnable = animationFinishAction; + endRunnable = onSlideAwayAnimationComplete; } dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE); currentDelay = Math.max(50, currentDelay - rowDelayDecrement); @@ -5575,7 +5627,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd R.layout.status_bar_notification_footer, this, false); footerView.setDismissButtonClickListener(v -> { mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES); - clearAllNotifications(); + clearNotifications(ROWS_ALL, true /* closeShade */); }); footerView.setManageButtonClickListener(this::manageNotifications); setFooterView(footerView); @@ -5782,6 +5834,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mSwipeHelper.resetExposedMenuView(animate, force); } + private static boolean matchesSelection( + ExpandableNotificationRow row, + @SelectedRows int selection) { + switch (selection) { + case ROWS_ALL: + return true; + case ROWS_HIGH_PRIORITY: + return row.getEntry().isHighPriority(); + case ROWS_GENTLE: + return !row.getEntry().isHighPriority(); + default: + throw new IllegalArgumentException("Unknown selection: " + selection); + } + } + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) static class AnimationEvent { @@ -6266,7 +6333,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override public boolean canChildBeDismissed(View v) { - return NotificationStackScrollLayout.this.canChildBeDismissed(v); + return StackScrollAlgorithm.canChildBeDismissed(v); } @Override @@ -6473,4 +6540,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd public ExpandHelper.Callback getExpandHelperCallback() { return mExpandHelperCallback; } + + /** Enum for selecting some or all notification rows (does not included non-notif views). */ + @Retention(SOURCE) + @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE}) + public @interface SelectedRows {} + /** All rows representing notifs. */ + public static final int ROWS_ALL = 0; + /** Only rows where entry.isHighPriority() is true. */ + public static final int ROWS_HIGH_PRIORITY = 1; + /** Only rows where entry.isHighPriority() is false. */ + public static final int ROWS_GENTLE = 2; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java index c32c529110df5..e2f702dcb7323 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java @@ -60,6 +60,12 @@ public class SectionHeaderView extends ActivatableNotificationView { updateBackgroundColors(); mLabelView.setTextColor( getContext().getColor(R.color.notification_section_header_label_color)); + mClearAllButton.setImageResource( + R.drawable.status_bar_notification_section_header_clear_btn); + } + + void setAreThereDismissableGentleNotifs(boolean areThereDismissableGentleNotifs) { + mClearAllButton.setVisibility(areThereDismissableGentleNotifs ? View.VISIBLE : View.GONE); } @Override @@ -79,4 +85,9 @@ public class SectionHeaderView extends ActivatableNotificationView { void setOnHeaderClickListener(View.OnClickListener listener) { mContents.setOnClickListener(listener); } + + /** Fired when the user clicks on the "X" button on the far right of the header. */ + void setOnClearAllClickListener(View.OnClickListener listener) { + mClearAllButton.setOnClickListener(listener); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 9d24e1efc66a2..168813a02252e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.SysUiServiceProvider.getComponent; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; import android.animation.Animator; @@ -2992,7 +2993,7 @@ public class NotificationPanelView extends PanelView implements } public boolean hasActiveClearableNotifications() { - return mNotificationStackScroller.hasActiveClearableNotifications(); + return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL); } @Override