Update clear all animation

- Fade out clear all button on tap
- Fade out silent label once there are no silent notifs
- Fade out footer once there are no notifs
- Round corners of dismissing notifs
- Pause to show "no notifications" before closing shade

Timing
- Remove wait before clear all swipe animations start
- Wait less between individual swipe animations
- Faster swipe animation

Bug: 172065269
Test: clear all with scroll, silent notifs, no ongoing notifs
	=> clear button fades out on tap
	=> clearable notifs swipe out with new delay, timing
	=> silent header fades
	=> no scroll change
	=> show "no notifications" at top of shade
	=> close shade
Test: clear all with no scroll, ongoing silent notifs
        => clearable silent notifs swipe out
	=> ongoing silent notifs stay
	=> silent header "x" fades
	=> show "no notifications" at top of shade
	=> close shade
Test: clear a lot of notifications
	=> pause same amount of time after swipe animations
	before closing shade
Test: press "x" to clear silent notifications
	=> silent notifications animate away
	=> shade stays open if there are other clearable notifs

Change-Id: I4db85068f46b95a34e71abb6428d3c2ae757cf0a
Merged-In: I4db85068f46b95a34e71abb6428d3c2ae757cf0a
This commit is contained in:
Lyn Han
2021-08-16 21:14:44 -05:00
parent f1eb5cc17a
commit 07470c7c05
8 changed files with 208 additions and 134 deletions

View File

@@ -437,6 +437,12 @@ public class SwipeHelper implements Gefingerpoken {
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCallback.onBeginDrag(animView);
}
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;

View File

@@ -85,23 +85,26 @@ public abstract class StackScrollerDecorView extends ExpandableView {
}
/**
* Set the content of this view to be visible in an animated way.
*
* @param contentVisible True if the content should be visible or false if it should be hidden.
* @param visible True if we should animate contents visible
*/
public void setContentVisible(boolean contentVisible) {
setContentVisible(contentVisible, true /* animate */);
public void setContentVisible(boolean visible) {
setContentVisible(visible, true /* animate */, null /* runAfter */);
}
/**
* Set the content of this view to be visible.
* @param contentVisible True if the content should be visible or false if it should be hidden.
* @param animate Should an animation be performed.
* @param visible True if the contents should be visible
* @param animate True if we should fade to new visibility
* @param runAfter Runnable to run after visibility updates
*/
private void setContentVisible(boolean contentVisible, boolean animate) {
if (mContentVisible != contentVisible) {
public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) {
if (mContentVisible != visible) {
mContentAnimating = animate;
mContentVisible = contentVisible;
setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
mContentVisible = visible;
Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> {
mContentVisibilityEndRunnable.run();
runAfter.run();
};
setViewVisible(mContent, visible, animate, endRunnable);
}
if (!mContentAnimating) {
@@ -113,6 +116,10 @@ public abstract class StackScrollerDecorView extends ExpandableView {
return mContentVisible;
}
public void setVisible(boolean nowVisible, boolean animate) {
setVisible(nowVisible, animate, null);
}
/**
* Make this view visible. If {@code false} is passed, the view will fade out it's content
* and set the view Visibility to GONE. If only the content should be changed
@@ -121,7 +128,7 @@ public abstract class StackScrollerDecorView extends ExpandableView {
* @param nowVisible should the view be visible
* @param animate should the change be animated.
*/
public void setVisible(boolean nowVisible, boolean animate) {
public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) {
if (mIsVisible != nowVisible) {
mIsVisible = nowVisible;
if (animate) {
@@ -132,10 +139,10 @@ public abstract class StackScrollerDecorView extends ExpandableView {
} else {
setWillBeGone(true);
}
setContentVisible(nowVisible, true /* animate */);
setContentVisible(nowVisible, true /* animate */, runAfter);
} else {
setVisibility(nowVisible ? VISIBLE : GONE);
setContentVisible(nowVisible, false /* animate */);
setContentVisible(nowVisible, false /* animate */, runAfter);
setWillBeGone(false);
notifyHeightChanged(false /* needsAnimation */);
}

View File

@@ -46,6 +46,7 @@ public class NotificationRoundnessManager {
private Runnable mRoundingChangedCallback;
private ExpandableNotificationRow mTrackedHeadsUp;
private float mAppearFraction;
private boolean mIsDismissAllInProgress;
private ExpandableView mSwipedView = null;
private ExpandableView mViewBeforeSwipedView = null;
@@ -162,6 +163,10 @@ public class NotificationRoundnessManager {
}
}
void setDismissAllInProgress(boolean isClearingAll) {
mIsDismissAllInProgress = isClearingAll;
}
private float getRoundness(ExpandableView view, boolean top) {
if (view == null) {
return 0f;
@@ -171,6 +176,11 @@ public class NotificationRoundnessManager {
|| view == mViewAfterSwipedView) {
return 1f;
}
if (view instanceof ExpandableNotificationRow
&& ((ExpandableNotificationRow) view).canViewBeDismissed()
&& mIsDismissAllInProgress) {
return 1.0f;
}
if ((view.isPinned()
|| (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
return 1.0f;

View File

@@ -144,6 +144,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean(
"persist.debug.nssl.dismiss", false /* default */);
// Delay in milli-seconds before shade closes for clear all.
private final int DELAY_BEFORE_SHADE_CLOSE = 200;
private boolean mShadeNeedsToClose = false;
private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
@@ -259,7 +263,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
protected FooterView mFooterView;
protected EmptyShadeView mEmptyShadeView;
private boolean mDismissAllInProgress;
private boolean mFadeNotificationsOnDismiss;
private FooterDismissListener mFooterDismissListener;
private boolean mFlingAfterUpEvent;
@@ -1192,7 +1195,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void clampScrollPosition() {
int scrollRange = getScrollRange();
if (scrollRange < mOwnScrollY) {
if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) {
boolean animateStackY = false;
if (scrollRange < getScrollAmountToScrollBoundary()
&& mAnimateStackYForContentHeightChange) {
@@ -1708,6 +1711,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
if (child instanceof SectionHeaderView) {
((StackScrollerDecorView) child).setContentVisible(
false /* visible */, true /* animate */, endRunnable);
return;
}
mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
true /* isDismissAll */);
}
@@ -4050,6 +4058,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
runAnimationFinishedRunnables();
clearTransient();
clearHeadsUpDisappearRunning();
if (mAmbientState.isDismissAllInProgress()) {
setDismissAllInProgress(false);
if (mShadeNeedsToClose) {
mShadeNeedsToClose = false;
postDelayed(
() -> {
mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
},
DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
}
}
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4407,6 +4428,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
mAmbientState.setDismissAllInProgress(dismissAllInProgress);
mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress);
handleDismissAllClipping();
}
@@ -4947,64 +4969,129 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mHeadsUpAppearanceController = headsUpAppearanceController;
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@VisibleForTesting
void clearNotifications(@SelectedRows int selection, boolean closeShade) {
// animate-swipe all dismissable notifications, then animate the shade closed
int numChildren = getChildCount();
private boolean isVisible(View child) {
boolean hasClipBounds = child.getClipBounds(mTmpRect);
return child.getVisibility() == View.VISIBLE
&& (!hasClipBounds || mTmpRect.height() > 0);
}
final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
for (int i = 0; i < numChildren; i++) {
final View child = getChildAt(i);
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
boolean parentVisible = false;
boolean hasClipBounds = child.getClipBounds(mTmpRect);
if (includeChildInDismissAll(row, selection)) {
viewsToRemove.add(row);
if (child.getVisibility() == View.VISIBLE
&& (!hasClipBounds || mTmpRect.height() > 0)) {
viewsToHide.add(child);
parentVisible = true;
}
} else if (child.getVisibility() == View.VISIBLE
&& (!hasClipBounds || mTmpRect.height() > 0)) {
parentVisible = true;
}
List<ExpandableNotificationRow> children = row.getAttachedChildren();
if (children != null) {
for (ExpandableNotificationRow childRow : children) {
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);
}
}
private boolean shouldHideParent(View view, @SelectedRows int selection) {
final boolean silentSectionWillBeGone =
!mController.hasNotifications(ROWS_GENTLE, false /* clearable */);
// The only SectionHeaderView we have is the silent section header.
if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
return true;
}
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
if (isVisible(row) && includeChildInDismissAll(row, selection)) {
return true;
}
}
return false;
}
private boolean isChildrenVisible(ExpandableNotificationRow parent) {
List<ExpandableNotificationRow> children = parent.getAttachedChildren();
return isVisible(parent)
&& children != null
&& parent.areChildrenExpanded();
}
// Similar to #getRowsToDismissInBackend, but filters for visible views.
private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) {
final int viewCount = getChildCount();
final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);
for (int i = 0; i < viewCount; i++) {
final View view = getChildAt(i);
if (shouldHideParent(view, selection)) {
viewsToHide.add(view);
}
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
if (isChildrenVisible(parent)) {
for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
if (isVisible(child) && includeChildInDismissAll(child, selection)) {
viewsToHide.add(child);
}
}
}
}
}
return viewsToHide;
}
private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend(
@SelectedRows int selection) {
final int childCount = getChildCount();
final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(childCount);
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (!(view instanceof ExpandableNotificationRow)) {
continue;
}
ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
if (includeChildInDismissAll(parent, selection)) {
viewsToRemove.add(parent);
}
List<ExpandableNotificationRow> children = parent.getAttachedChildren();
if (isVisible(parent) && children != null) {
for (ExpandableNotificationRow child : children) {
if (includeChildInDismissAll(parent, selection)) {
viewsToRemove.add(child);
}
}
}
}
return viewsToRemove;
}
/**
* Collects a list of visible rows, and animates them away in a staggered fashion as if they
* were dismissed. Notifications are dismissed in the backend via onDismissAllAnimationsEnd.
*/
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@VisibleForTesting
void clearNotifications(@SelectedRows int selection, boolean closeShade) {
// Animate-swipe all dismissable notifications, then animate the shade closed
final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
getRowsToDismissInBackend(selection);
if (mDismissListener != null) {
mDismissListener.onDismiss(selection);
}
if (viewsToRemove.isEmpty()) {
if (closeShade && mShadeController != null) {
mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
}
final Runnable dismissInBackend = () -> {
onDismissAllAnimationsEnd(rowsToDismissInBackend, selection);
};
if (viewsToAnimateAway.isEmpty()) {
dismissInBackend.run();
return;
}
// Disable normal animations
setDismissAllInProgress(true);
mShadeNeedsToClose = closeShade;
performDismissAllAnimations(
viewsToHide,
closeShade,
() -> onDismissAllAnimationsEnd(viewsToRemove, selection));
// Decrease the delay for every row we animate to give the sense of
// accelerating the swipes
final int rowDelayDecrement = 5;
int currentDelay = 60;
int totalDelay = 0;
final int numItems = viewsToAnimateAway.size();
for (int i = numItems - 1; i >= 0; i--) {
View view = viewsToAnimateAway.get(i);
Runnable endRunnable = null;
if (i == 0) {
endRunnable = dismissInBackend;
}
dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
currentDelay = Math.max(30, currentDelay - rowDelayDecrement);
totalDelay += currentDelay;
}
}
private boolean includeChildInDismissAll(
@@ -5013,63 +5100,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return 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)
private void performDismissAllAnimations(
final ArrayList<View> hideAnimatedList,
final boolean closeShade,
final Runnable onAnimationComplete) {
final Runnable onSlideAwayAnimationComplete = () -> {
if (closeShade) {
mShadeController.addPostCollapseAction(() -> {
setDismissAllInProgress(false);
onAnimationComplete.run();
});
mShadeController.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_NONE);
} else {
setDismissAllInProgress(false);
onAnimationComplete.run();
}
};
if (hideAnimatedList.isEmpty()) {
onSlideAwayAnimationComplete.run();
return;
}
// let's disable our normal animations
setDismissAllInProgress(true);
// Decrease the delay for every row we animate to give the sense of
// accelerating the swipes
int rowDelayDecrement = 10;
int currentDelay = 140;
int totalDelay = 180;
int numItems = hideAnimatedList.size();
for (int i = numItems - 1; i >= 0; i--) {
View view = hideAnimatedList.get(i);
Runnable endRunnable = null;
if (i == 0) {
endRunnable = onSlideAwayAnimationComplete;
}
dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
totalDelay += currentDelay;
}
}
public void setNotificationActivityStarter(
NotificationActivityStarter notificationActivityStarter) {
mNotificationActivityStarter = notificationActivityStarter;
@@ -5085,6 +5115,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mFooterDismissListener.onDismiss();
}
clearNotifications(ROWS_ALL, true /* closeShade */);
footerView.setSecondaryVisible(false /* visible */, true /* animate */);
});
footerView.setManageButtonClickListener(v -> {
mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown());

View File

@@ -1122,6 +1122,10 @@ public class NotificationStackScrollLayoutController {
mZenModeController.areNotificationsHiddenInShade());
}
public boolean areNotificationsHiddenInShade() {
return mZenModeController.areNotificationsHiddenInShade();
}
public boolean isShowingEmptyShadeView() {
return mShowEmptyShadeView;
}
@@ -1170,6 +1174,10 @@ public class NotificationStackScrollLayoutController {
* Return whether there are any clearable notifications
*/
public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
return hasNotifications(selection, true /* clearable */);
}
public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
if (mDynamicPrivacyController.isInLockedDownShade()) {
return false;
}
@@ -1180,8 +1188,11 @@ public class NotificationStackScrollLayoutController {
continue;
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (row.canViewBeDismissed() &&
NotificationStackScrollLayout.matchesSelection(row, selection)) {
final boolean matchClearable =
isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed();
final boolean inSection =
NotificationStackScrollLayout.matchesSelection(row, selection);
if (matchClearable && inSection) {
return true;
}
}

View File

@@ -366,6 +366,20 @@ public class StackScrollAlgorithm {
return stackHeight / stackEndHeight;
}
public boolean hasOngoingNotifs(StackScrollAlgorithmState algorithmState) {
for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
View child = algorithmState.visibleChildren.get(i);
if (!(child instanceof ExpandableNotificationRow)) {
continue;
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (!row.canViewBeDismissed()) {
return true;
}
}
return false;
}
// TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
@@ -430,7 +444,9 @@ public class StackScrollAlgorithm {
+ view.getIntrinsicHeight();
final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
((FooterView.FooterViewState) viewState).hideContent =
isShelfShowing || noSpaceForFooter;
isShelfShowing || noSpaceForFooter
|| (ambientState.isDismissAllInProgress()
&& !hasOngoingNotifs(algorithmState));
}
} else {
if (view != ambientState.getTrackedHeadsUpRow()) {
@@ -467,7 +483,6 @@ public class StackScrollAlgorithm {
}
}
}
// Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}

View File

@@ -46,7 +46,7 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_WAKEUP = 500;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
public static final int ANIMATION_DURATION_SWIPE = 260;
public static final int ANIMATION_DURATION_SWIPE = 200;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;

View File

@@ -310,17 +310,11 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
if (old != null) {
if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
&& !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
if (mStatusBarStateController.getState() == StatusBarState.SHADE
&& reason != NotificationListenerService.REASON_CLICK) {
mCommandQueue.animateCollapsePanels();
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
&& !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
&& mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
&& !isCollapsing()) {
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
}
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
}