Merge "Expand notifications like accordian" into sc-dev

This commit is contained in:
Lyn Han
2021-04-15 17:42:51 +00:00
committed by Android (Google) Code Review
6 changed files with 446 additions and 146 deletions

View File

@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.phone.NotificationIconContainer;
@@ -82,6 +83,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
private Rect mClipRect = new Rect(); private Rect mClipRect = new Rect();
private int mCutoutHeight; private int mCutoutHeight;
private int mGapHeight; private int mGapHeight;
private int mIndexOfFirstViewInShelf = -1;
private int mIndexOfFirstViewInOverflowingSection = -1;
private NotificationShelfController mController; private NotificationShelfController mController;
public NotificationShelf(Context context, AttributeSet attrs) { public NotificationShelf(Context context, AttributeSet attrs) {
@@ -159,30 +163,49 @@ public class NotificationShelf extends ActivatableNotificationView implements
} }
/** Update the state of the shelf. */ /** Update the state of the shelf. */
public void updateState(AmbientState ambientState) { public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
ExpandableView lastView = ambientState.getLastVisibleBackgroundChild(); ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
ShelfState viewState = (ShelfState) getViewState(); ShelfState viewState = (ShelfState) getViewState();
if (mShowNotificationShelf && lastView != null) { if (mShowNotificationShelf && lastView != null) {
float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
ExpandableViewState lastViewState = lastView.getViewState(); ExpandableViewState lastViewState = lastView.getViewState();
float viewEnd = lastViewState.yTranslation + lastViewState.height;
viewState.copyFrom(lastViewState); viewState.copyFrom(lastViewState);
viewState.height = getIntrinsicHeight(); viewState.height = getIntrinsicHeight();
viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
getFullyClosedTranslation());
viewState.zTranslation = ambientState.getBaseZHeight(); viewState.zTranslation = ambientState.getBaseZHeight();
viewState.clipTopAmount = 0; viewState.clipTopAmount = 0;
viewState.alpha = 1f - ambientState.getHideAmount(); viewState.alpha = 1f - ambientState.getHideAmount();
viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0; viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
viewState.hideSensitive = false; viewState.hideSensitive = false;
viewState.xTranslation = getTranslationX(); viewState.xTranslation = getTranslationX();
viewState.hasItemsInStableShelf = lastViewState.inShelf;
viewState.firstViewInShelf = algorithmState.firstViewInShelf;
viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection;
if (mNotGoneIndex != -1) { if (mNotGoneIndex != -1) {
viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex); viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
} }
viewState.hasItemsInStableShelf = lastViewState.inShelf;
viewState.hidden = !mAmbientState.isShadeExpanded() viewState.hidden = !mAmbientState.isShadeExpanded()
|| mAmbientState.isQsCustomizerShowing(); || mAmbientState.isQsCustomizerShowing()
|| algorithmState.firstViewInShelf == null;
final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf(
algorithmState.firstViewInShelf);
if (mAmbientState.isExpansionChanging()
&& algorithmState.firstViewInShelf != null
&& indexOfFirstViewInShelf > 0) {
// Show shelf if section before it is showing.
final ExpandableView viewBeforeShelf = algorithmState.visibleChildren.get(
indexOfFirstViewInShelf - 1);
if (viewBeforeShelf.getViewState().hidden) {
viewState.hidden = true;
}
}
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
viewState.yTranslation = stackEnd - viewState.height;
} else { } else {
viewState.hidden = true; viewState.hidden = true;
viewState.location = ExpandableViewState.LOCATION_GONE; viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -199,13 +222,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (!mShowNotificationShelf) { if (!mShowNotificationShelf) {
return; return;
} }
mShelfIcons.resetViewStates(); mShelfIcons.resetViewStates();
float shelfStart = getTranslationY(); float shelfStart = getTranslationY();
float numViewsInShelf = 0.0f; float numViewsInShelf = 0.0f;
View lastChild = mAmbientState.getLastVisibleBackgroundChild(); View lastChild = mAmbientState.getLastVisibleBackgroundChild();
mNotGoneIndex = -1; mNotGoneIndex = -1;
float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
// find the first view that doesn't overlap with the shelf // find the first view that doesn't overlap with the shelf
int notGoneIndex = 0; int notGoneIndex = 0;
int colorOfViewBeforeLast = NO_COLOR; int colorOfViewBeforeLast = NO_COLOR;
@@ -233,22 +254,37 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (!child.needsClippingToShelf() || child.getVisibility() == GONE) { if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
continue; continue;
} }
float notificationClipEnd; float notificationClipEnd;
boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight
|| child.isPinned(); || child.isPinned();
boolean isLastChild = child == lastChild; boolean isLastChild = child == lastChild;
float rowTranslationY = child.getTranslationY(); float rowTranslationY = child.getTranslationY();
final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
expandingAnimated, isLastChild);
final float stackEnd = mAmbientState.getStackY()
+ mAmbientState.getStackHeight();
// TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) { if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
notificationClipEnd = shelfStart + getIntrinsicHeight(); notificationClipEnd = stackEnd;
} else if (mAmbientState.isExpansionChanging()) {
if (mIndexOfFirstViewInOverflowingSection != -1
&& i >= mIndexOfFirstViewInOverflowingSection) {
// Clip notifications in (section overflowing into shelf) to shelf start.
notificationClipEnd = shelfStart - mPaddingBetweenElements;
} else {
// Clip notifications before the section overflowing into shelf
// to stackEnd because we do not show the shelf if the section right before the
// shelf is still hidden.
notificationClipEnd = stackEnd;
}
} else { } else {
notificationClipEnd = shelfStart - mPaddingBetweenElements; notificationClipEnd = shelfStart - mPaddingBetweenElements;
} }
int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex); int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex);
clipTopAmount = Math.max(clipTop, clipTopAmount); clipTopAmount = Math.max(clipTop, clipTopAmount);
final float inShelfAmount = updateShelfTransformation(child, scrollingFast,
expandingAnimated, isLastChild);
// If the current row is an ExpandableNotificationRow, update its color, roundedness, // If the current row is an ExpandableNotificationRow, update its color, roundedness,
// and icon state. // and icon state.
if (child instanceof ExpandableNotificationRow) { if (child instanceof ExpandableNotificationRow) {
@@ -314,19 +350,23 @@ public class NotificationShelf extends ActivatableNotificationView implements
distanceToGapTop / mGapHeight); distanceToGapTop / mGapHeight);
previousAnv.setBottomRoundness(firstElementRoundness, previousAnv.setBottomRoundness(firstElementRoundness,
false /* don't animate */); false /* don't animate */);
backgroundTop = (int) distanceToGapBottom;
} }
} }
previousAnv = anv; previousAnv = anv;
} }
} }
clipTransientViews(); clipTransientViews();
setClipTopAmount(clipTopAmount); setClipTopAmount(clipTopAmount);
boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight();
if (mShowNotificationShelf) { boolean isHidden = getViewState().hidden
|| clipTopAmount >= getIntrinsicHeight()
|| !mShowNotificationShelf
|| numViewsInShelf < 1f;
// TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
}
setBackgroundTop(backgroundTop); setBackgroundTop(backgroundTop);
setFirstElementRoundness(firstElementRoundness); setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
@@ -339,11 +379,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
continue; continue;
} }
ExpandableNotificationRow row = (ExpandableNotificationRow) child; ExpandableNotificationRow row = (ExpandableNotificationRow) child;
updateIconClipAmount(row);
updateContinuousClipping(row); updateContinuousClipping(row);
} }
boolean hideBackground = numViewsInShelf < 1.0f; boolean hideBackground = isHidden;
setHideBackground(hideBackground || backgroundForceHidden); setHideBackground(hideBackground);
if (mNotGoneIndex == -1) { if (mNotGoneIndex == -1) {
mNotGoneIndex = notGoneIndex; mNotGoneIndex = notGoneIndex;
} }
@@ -476,10 +515,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
/** /**
* @return the amount how much this notification is in the shelf * @return the amount how much this notification is in the shelf
*/ */
private float updateShelfTransformation(ExpandableView view, boolean scrollingFast, private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast,
boolean expandingAnimated, boolean isLastChild) { boolean expandingAnimated, boolean isLastChild) {
// Let calculate how much the view is in the shelf // Let's calculate how much the view is in the shelf
float viewStart = view.getTranslationY(); float viewStart = view.getTranslationY();
int fullHeight = view.getActualHeight() + mPaddingBetweenElements; int fullHeight = view.getActualHeight() + mPaddingBetweenElements;
float iconTransformStart = calculateIconTransformationStart(view); float iconTransformStart = calculateIconTransformationStart(view);
@@ -496,12 +535,18 @@ public class NotificationShelf extends ActivatableNotificationView implements
transformDistance, transformDistance,
view.getMinHeight() - getIntrinsicHeight()); view.getMinHeight() - getIntrinsicHeight());
} }
float viewEnd = viewStart + fullHeight; float viewEnd = viewStart + fullHeight;
float fullTransitionAmount = 0.0f; float fullTransitionAmount = 0.0f;
float iconTransitionAmount = 0.0f; float iconTransitionAmount = 0.0f;
float shelfStart = getTranslationY(); float shelfStart = getTranslationY();
if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
if (viewEnd >= shelfStart // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {
fullTransitionAmount = 1f;
iconTransitionAmount = 1f;
}
} else if (viewEnd >= shelfStart
&& (!mAmbientState.isUnlockHintRunning() || view.isInShelf()) && (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
&& (mAmbientState.isShadeExpanded() && (mAmbientState.isShadeExpanded()
|| (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
@@ -572,7 +617,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
&& !mNoAnimationsInThisFrame; && !mNoAnimationsInThisFrame;
} }
iconState.clampedAppearAmount = clampedAmount; iconState.clampedAppearAmount = clampedAmount;
setIconTransformationAmount(view, transitionAmount, isLastChild); setIconTransformationAmount(view, transitionAmount);
} }
private boolean isTargetClipped(ExpandableView view) { private boolean isTargetClipped(ExpandableView view) {
@@ -585,12 +630,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
+ view.getContentTranslation() + view.getContentTranslation()
+ view.getRelativeTopPadding(target) + view.getRelativeTopPadding(target)
+ target.getHeight(); + target.getHeight();
return endOfTarget >= getTranslationY() - mPaddingBetweenElements; return endOfTarget >= getTranslationY() - mPaddingBetweenElements;
} }
private void setIconTransformationAmount(ExpandableView view, float transitionAmount, private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
boolean isLastChild) {
if (!(view instanceof ExpandableNotificationRow)) { if (!(view instanceof ExpandableNotificationRow)) {
return; return;
} }
@@ -601,7 +644,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
return; return;
} }
iconState.alpha = transitionAmount; iconState.alpha = transitionAmount;
boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf(); boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
iconState.hidden = isAppearing iconState.hidden = isAppearing
|| (view instanceof ExpandableNotificationRow || (view instanceof ExpandableNotificationRow
@@ -610,8 +652,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
|| (transitionAmount == 0.0f && !iconState.isAnimating(icon)) || (transitionAmount == 0.0f && !iconState.isAnimating(icon))
|| row.isAboveShelf() || row.isAboveShelf()
|| row.showingPulsing() || row.showingPulsing()
|| (!row.isInShelf() && isLastChild)
|| row.getTranslationZ() > mAmbientState.getBaseZHeight(); || row.getTranslationZ() > mAmbientState.getBaseZHeight();
iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount; iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
// Fade in icons at shelf start // Fade in icons at shelf start
@@ -790,8 +832,19 @@ public class NotificationShelf extends ActivatableNotificationView implements
mController = notificationShelfController; mController = notificationShelfController;
} }
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) {
mIndexOfFirstViewInOverflowingSection =
mHostLayoutController.indexOfChild(firstViewInOverflowingSection);
}
private class ShelfState extends ExpandableViewState { private class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf; private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
private ExpandableView firstViewInOverflowSection;
@Override @Override
public void applyToView(View view) { public void applyToView(View view) {
@@ -800,6 +853,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
} }
super.applyToView(view); super.applyToView(view);
setIndexOfFirstViewInShelf(firstViewInShelf);
setFirstViewInOverflowingSection(firstViewInOverflowSection);
updateAppearance(); updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf); setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
@@ -812,6 +867,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
} }
super.animateTo(child, properties); super.animateTo(child, properties);
setIndexOfFirstViewInShelf(firstViewInShelf);
setFirstViewInOverflowingSection(firstViewInOverflowSection);
updateAppearance(); updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf); setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);

View File

@@ -22,6 +22,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter; import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter;
@@ -103,9 +104,10 @@ public class NotificationShelfController {
return mView.getHeight(); return mView.getHeight();
} }
public void updateState(AmbientState ambientState) { public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
mAmbientState = ambientState; mAmbientState = ambientState;
mView.updateState(ambientState); mView.updateState(algorithmState, ambientState);
} }
public int getIntrinsicHeight() { public int getIntrinsicHeight() {

View File

@@ -60,7 +60,7 @@ public class AmbientState {
private NotificationShelf mShelf; private NotificationShelf mShelf;
private int mZDistanceBetweenElements; private int mZDistanceBetweenElements;
private int mBaseZHeight; private int mBaseZHeight;
private int mMaxLayoutHeight; private int mContentHeight;
private ExpandableView mLastVisibleBackgroundChild; private ExpandableView mLastVisibleBackgroundChild;
private float mCurrentScrollVelocity; private float mCurrentScrollVelocity;
private int mStatusBarState; private int mStatusBarState;
@@ -84,6 +84,75 @@ public class AmbientState {
private boolean mIsShadeOpening; private boolean mIsShadeOpening;
private float mSectionPadding; private float mSectionPadding;
/** Distance of top of notifications panel from top of screen. */
private float mStackY = 0;
/** Height of notifications panel. */
private float mStackHeight = 0;
/** Fraction of shade expansion. */
private float mExpansionFraction;
/** Height of the notifications panel without top padding when expansion completes. */
private float mStackEndHeight;
/**
* @return Height of the notifications panel without top padding when expansion completes.
*/
public float getStackEndHeight() {
return mStackEndHeight;
}
/**
* @param stackEndHeight Height of the notifications panel without top padding
* when expansion completes.
*/
public void setStackEndHeight(float stackEndHeight) {
mStackEndHeight = stackEndHeight;
}
/**
* @param stackY Distance of top of notifications panel from top of screen.
*/
public void setStackY(float stackY) {
mStackY = stackY;
}
/**
* @return Distance of top of notifications panel from top of screen.
*/
public float getStackY() {
return mStackY;
}
/**
* @param expansionFraction Fraction of shade expansion.
*/
public void setExpansionFraction(float expansionFraction) {
mExpansionFraction = expansionFraction;
}
/**
* @return Fraction of shade expansion.
*/
public float getExpansionFraction() {
return mExpansionFraction;
}
/**
* @param stackHeight Height of notifications panel.
*/
public void setStackHeight(float stackHeight) {
mStackHeight = stackHeight;
}
/**
* @return Height of notifications panel.
*/
public float getStackHeight() {
return mStackHeight;
}
/** Tracks the state from AlertingNotificationManager#hasNotifications() */ /** Tracks the state from AlertingNotificationManager#hasNotifications() */
private boolean mHasAlertEntries; private boolean mHasAlertEntries;
@@ -263,8 +332,8 @@ public class AmbientState {
if (mDozeAmount == 1.0f && !isPulseExpanding()) { if (mDozeAmount == 1.0f && !isPulseExpanding()) {
return mShelf.getHeight(); return mShelf.getHeight();
} }
int height = Math.max(mLayoutMinHeight, int height = (int) Math.max(mLayoutMinHeight,
Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding); Math.min(mLayoutHeight, mContentHeight) - mTopPadding);
if (ignorePulseHeight) { if (ignorePulseHeight) {
return height; return height;
} }
@@ -313,8 +382,12 @@ public class AmbientState {
return mShelf; return mShelf;
} }
public void setLayoutMaxHeight(int maxLayoutHeight) { public void setContentHeight(int contentHeight) {
mMaxLayoutHeight = maxLayoutHeight; mContentHeight = contentHeight;
}
public float getContentHeight() {
return mContentHeight;
} }
/** /**

View File

@@ -658,6 +658,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
y = getHeight() - getEmptyBottomMargin(); y = getHeight() - getEmptyBottomMargin();
mDebugPaint.setColor(Color.GREEN); mDebugPaint.setColor(Color.GREEN);
canvas.drawLine(0, y, getWidth(), y, mDebugPaint); canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
y = (int) (mAmbientState.getStackY());
mDebugPaint.setColor(Color.CYAN);
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
mDebugPaint.setColor(Color.BLUE);
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
} }
} }
@@ -1123,11 +1131,25 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mTopPaddingNeedsAnimation = true; mTopPaddingNeedsAnimation = true;
mNeedsAnimation = true; mNeedsAnimation = true;
} }
updateStackPosition();
requestChildrenUpdate(); requestChildrenUpdate();
notifyHeightChangeListener(null, animate); notifyHeightChangeListener(null, animate);
} }
} }
/**
* Apply expansion fraction to the y position and height of the notifications panel.
*/
private void updateStackPosition() {
// Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
mAmbientState.setStackY(
MathUtils.lerp(0, mTopPadding, mAmbientState.getExpansionFraction()));
final float shadeBottom = getHeight() - getEmptyBottomMargin();
mAmbientState.setStackEndHeight(shadeBottom - mTopPadding);
mAmbientState.setStackHeight(
MathUtils.lerp(0, shadeBottom - mTopPadding, mAmbientState.getExpansionFraction()));
}
/** /**
* Update the height of the panel. * Update the height of the panel.
* *
@@ -1135,6 +1157,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/ */
@ShadeViewRefactor(RefactorComponent.COORDINATOR) @ShadeViewRefactor(RefactorComponent.COORDINATOR)
public void setExpandedHeight(float height) { public void setExpandedHeight(float height) {
final float shadeBottom = getHeight() - getEmptyBottomMargin();
final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f);
mAmbientState.setExpansionFraction(expansionFraction);
updateStackPosition();
mExpandedHeight = height; mExpandedHeight = height;
setIsExpanded(height > 0); setIsExpanded(height > 0);
int minExpansionHeight = getMinExpansionHeight(); int minExpansionHeight = getMinExpansionHeight();
@@ -2067,7 +2094,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin; mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
updateScrollability(); updateScrollability();
clampScrollPosition(); clampScrollPosition();
mAmbientState.setLayoutMaxHeight(mContentHeight); updateStackPosition();
mAmbientState.setContentHeight(mContentHeight);
} }
/** /**

View File

@@ -788,6 +788,10 @@ public class NotificationStackScrollLayoutController {
return mView.getTranslationX(); return mView.getTranslationX();
} }
public int indexOfChild(View view) {
return mView.indexOfChild(view);
}
public void setOnHeightChangedListener( public void setOnHeightChangedListener(
ExpandableView.OnHeightChangedListener listener) { ExpandableView.OnHeightChangedListener listener) {
mView.setOnHeightChangedListener(listener); mView.setOnHeightChangedListener(listener);

View File

@@ -20,21 +20,22 @@ import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.util.Log;
import android.util.MathUtils; import android.util.MathUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.FooterView; import com.android.systemui.statusbar.notification.row.FooterView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* The Algorithm of the {@link com.android.systemui.statusbar.notification.stack * The Algorithm of the {@link com.android.systemui.statusbar.notification.stack
@@ -92,20 +93,16 @@ public class StackScrollAlgorithm {
// First we reset the view states to their default values. // First we reset the view states to their default values.
resetChildViewStates(); resetChildViewStates();
initAlgorithmState(mHostView, algorithmState, ambientState); initAlgorithmState(mHostView, algorithmState, ambientState);
updatePositionsForState(algorithmState, ambientState); updatePositionsForState(algorithmState, ambientState);
updateZValuesForState(algorithmState, ambientState); updateZValuesForState(algorithmState, ambientState);
updateHeadsUpStates(algorithmState, ambientState); updateHeadsUpStates(algorithmState, ambientState);
updatePulsingStates(algorithmState, ambientState); updatePulsingStates(algorithmState, ambientState);
updateDimmedActivatedHideSensitive(ambientState, algorithmState); updateDimmedActivatedHideSensitive(ambientState, algorithmState);
updateClipping(algorithmState, ambientState); updateClipping(algorithmState, ambientState);
updateSpeedBumpState(algorithmState, speedBumpIndex); updateSpeedBumpState(algorithmState, speedBumpIndex);
updateShelfState(ambientState); updateShelfState(algorithmState, ambientState);
getNotificationChildrenStates(algorithmState, ambientState); getNotificationChildrenStates(algorithmState, ambientState);
} }
@@ -144,10 +141,13 @@ public class StackScrollAlgorithm {
} }
private void updateShelfState(AmbientState ambientState) { private void updateShelfState(
StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
NotificationShelf shelf = ambientState.getShelf(); NotificationShelf shelf = ambientState.getShelf();
if (shelf != null) { if (shelf != null) {
shelf.updateState(ambientState); shelf.updateState(algorithmState, ambientState);
} }
} }
@@ -172,7 +172,8 @@ public class StackScrollAlgorithm {
&& ((ExpandableNotificationRow) child).isPinned(); && ((ExpandableNotificationRow) child).isPinned();
if (mClipNotificationScrollToTop if (mClipNotificationScrollToTop
&& (!state.inShelf || (isHeadsUp && !firstHeadsUp)) && (!state.inShelf || (isHeadsUp && !firstHeadsUp))
&& newYTranslation < clipStart) { && newYTranslation < clipStart
&& !ambientState.isShadeOpening()) {
// The previous view is overlapping on top, clip! // The previous view is overlapping on top, clip!
float overlapAmount = clipStart - newYTranslation; float overlapAmount = clipStart - newYTranslation;
state.clipTopAmount = (int) overlapAmount; state.clipTopAmount = (int) overlapAmount;
@@ -217,7 +218,6 @@ public class StackScrollAlgorithm {
private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state, private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state,
AmbientState ambientState) { AmbientState ambientState) {
float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */); float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
int scrollY = ambientState.getScrollY(); int scrollY = ambientState.getScrollY();
// Due to the overScroller, the stackscroller can have negative scroll state. This is // Due to the overScroller, the stackscroller can have negative scroll state. This is
@@ -230,7 +230,6 @@ public class StackScrollAlgorithm {
state.visibleChildren.clear(); state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount); state.visibleChildren.ensureCapacity(childCount);
int notGoneIndex = 0; int notGoneIndex = 0;
ExpandableView lastView = null;
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i); ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) { if (v.getVisibility() != View.GONE) {
@@ -255,12 +254,101 @@ public class StackScrollAlgorithm {
} }
} }
} }
ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
state.indexOfExpandingNotification = expandingNotification != null state.firstViewInShelf = null;
? expandingNotification.isChildInGroup() // Save y, sectionStart, sectionEnd from when shade is fully expanded.
? state.visibleChildren.indexOf(expandingNotification.getNotificationParent()) // Consider updating these states in updateContentView instead so that we don't have to
: state.visibleChildren.indexOf(expandingNotification) // recalculate in every frame.
: -1; float currentY = -scrollY;
int sectionStartIndex = 0;
int sectionEndIndex = 0;
for (int i = 0; i < state.visibleChildren.size(); i++) {
final ExpandableView view = state.visibleChildren.get(i);
// Add space between sections.
final boolean applyGapHeight = childNeedsGapHeight(
ambientState.getSectionProvider(), i,
view, getPreviousView(i, state));
if (applyGapHeight) {
currentY += mGapHeight;
}
// Save index of first view in the shelf
final float shelfStart = ambientState.getStackEndHeight()
- ambientState.getShelf().getIntrinsicHeight();
if (currentY >= shelfStart
&& !(view instanceof FooterView)
&& state.firstViewInShelf == null) {
state.firstViewInShelf = view;
}
// Record y position when fully expanded
ExpansionData expansionData = new ExpansionData();
expansionData.fullyExpandedY = currentY;
state.expansionData.put(view, expansionData);
if (ambientState.getSectionProvider()
.beginsSection(view, getPreviousView(i, state))) {
// Save section start/end for views in the section before this new section
ExpandableView sectionStartView = state.visibleChildren.get(sectionStartIndex);
final float sectionStart =
state.expansionData.get(sectionStartView).fullyExpandedY;
ExpandableView sectionEndView = state.visibleChildren.get(sectionEndIndex);
float sectionEnd = state.expansionData.get(sectionEndView).fullyExpandedY
+ sectionEndView.getIntrinsicHeight();
// If we show the shelf, trim section end to shelf start
// This means section end > start for views in the shelf
if (state.firstViewInShelf != null && sectionEnd > shelfStart) {
sectionEnd = shelfStart;
}
// Update section bounds of every view in the previous section
// Consider using shared SectionInfo for views in same section to avoid looping back
for (int j = sectionStartIndex; j < i; j++) {
ExpandableView sectionView = state.visibleChildren.get(j);
ExpansionData viewExpansionData =
state.expansionData.get(sectionView);
viewExpansionData.sectionStart = sectionStart;
viewExpansionData.sectionEnd = sectionEnd;
state.expansionData.put(sectionView, viewExpansionData);
}
sectionStartIndex = i;
if (view instanceof FooterView) {
// Also record section bounds for FooterView (same as its own)
// because it is the last view and we won't get to this point again
// after the loop ends
ExpansionData footerExpansionData = state.expansionData.get(view);
footerExpansionData.sectionStart = expansionData.fullyExpandedY;
footerExpansionData.sectionEnd = expansionData.fullyExpandedY
+ view.getIntrinsicHeight();
state.expansionData.put(view, footerExpansionData);
}
}
sectionEndIndex = i;
currentY = currentY
+ getMaxAllowedChildHeight(view)
+ mPaddingBetweenElements;
}
// Which view starts the section of the view right before the shelf?
// Save it for later when we clip views in that section to shelf start.
state.firstViewInOverflowSection = null;
if (state.firstViewInShelf != null) {
ExpandableView nextView = null;
final int startIndex = state.visibleChildren.indexOf(state.firstViewInShelf);
for (int i = startIndex - 1; i >= 0; i--) {
ExpandableView view = state.visibleChildren.get(i);
if (nextView != null && ambientState.getSectionProvider()
.beginsSection(nextView, view)) {
break;
}
nextView = view;
}
state.firstViewInOverflowSection = nextView;
}
} }
private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex, private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
@@ -272,6 +360,10 @@ public class StackScrollAlgorithm {
return notGoneIndex; return notGoneIndex;
} }
private ExpandableView getPreviousView(int i, StackScrollAlgorithmState algorithmState) {
return i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
}
/** /**
* Determine the positions for the views. This is the main part of the algorithm. * Determine the positions for the views. This is the main part of the algorithm.
* *
@@ -288,6 +380,15 @@ public class StackScrollAlgorithm {
} }
} }
private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
int i) {
expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
if (currentYPosition <= 0) {
expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
}
}
// TODO(b/172289889) polish shade open from HUN
/** /**
* Populates the {@link ExpandableViewState} for a single child. * Populates the {@link ExpandableViewState} for a single child.
* *
@@ -306,53 +407,84 @@ public class StackScrollAlgorithm {
StackScrollAlgorithmState algorithmState, StackScrollAlgorithmState algorithmState,
AmbientState ambientState, AmbientState ambientState,
float currentYPosition) { float currentYPosition) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null; ExpandableView view = algorithmState.visibleChildren.get(i);
ExpandableViewState viewState = view.getViewState();
viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
viewState.alpha = 1f - ambientState.getHideAmount();
if (view.mustStayOnScreen() && viewState.yTranslation >= 0) {
// Even if we're not scrolled away we're in view and we're also not in the
// shelf. We can relax the constraints and let us scroll off the top!
float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
}
// TODO(b/172289889) move sectionFraction and showSection to initAlgorithmState
// Get fraction of section showing, and later apply it to view height and gaps between views
float sectionFraction = 1f;
boolean showSection = true;
if (!ambientState.isOnKeyguard()
&& !ambientState.isPulseExpanding()
&& ambientState.isExpansionChanging()) {
final ExpansionData expansionData = algorithmState.expansionData.get(view);
final float sectionHeight = expansionData.sectionEnd - expansionData.sectionStart;
sectionFraction = MathUtils.constrain(
(ambientState.getStackHeight() - expansionData.sectionStart) / sectionHeight,
0f, 1f);
showSection = expansionData.sectionStart < ambientState.getStackHeight();
}
// Add gap between sections.
final boolean applyGapHeight = final boolean applyGapHeight =
childNeedsGapHeight( childNeedsGapHeight(
ambientState.getSectionProvider(), i, ambientState.getSectionProvider(), i,
child, previousChild); view, getPreviousView(i, algorithmState));
ExpandableViewState childViewState = child.getViewState();
childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
if (applyGapHeight) { if (applyGapHeight) {
currentYPosition += mGapHeight; currentYPosition += sectionFraction * mGapHeight;
}
int childHeight = getMaxAllowedChildHeight(child);
childViewState.yTranslation = currentYPosition;
childViewState.alpha = 1f - ambientState.getHideAmount();
boolean isFooterView = child instanceof FooterView;
boolean isEmptyShadeView = child instanceof EmptyShadeView;
childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
float inset = ambientState.getTopPadding() + ambientState.getStackTranslation()
+ ambientState.getSectionPadding();
if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
// Even if we're not scrolled away we're in view and we're also not in the
// shelf. We can relax the constraints and let us scroll off the top!
float end = childViewState.yTranslation + childViewState.height + inset;
childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
}
if (isFooterView) {
childViewState.yTranslation = Math.min(childViewState.yTranslation,
ambientState.getInnerHeight() - childHeight);
} else if (isEmptyShadeView) {
childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
+ ambientState.getStackTranslation() * 0.25f;
} else if (child != ambientState.getTrackedHeadsUpRow()) {
clampPositionToShelf(child, childViewState, ambientState);
} }
currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; viewState.yTranslation = currentYPosition;
if (currentYPosition <= 0) {
childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; if (view instanceof SectionHeaderView) {
} // Add padding before sections for overscroll effect.
if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) { viewState.yTranslation += ambientState.getSectionPadding();
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
} }
childViewState.yTranslation += inset; if (view != ambientState.getTrackedHeadsUpRow()) {
if (ambientState.isExpansionChanging()) {
viewState.hidden = !showSection;
viewState.inShelf = algorithmState.firstViewInShelf != null
&& i >= algorithmState.visibleChildren.indexOf(
algorithmState.firstViewInShelf)
&& !(view instanceof FooterView);
} else {
// When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
// to shelf start, thereby hiding all notifications (except the first one, which we
// later unhide in updatePulsingState)
final int shelfStart = ambientState.getInnerHeight()
- ambientState.getShelf().getIntrinsicHeight();
if (!(view instanceof FooterView)) {
viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
}
if (viewState.yTranslation >= shelfStart) {
viewState.hidden = !view.isExpandAnimationRunning()
&& !view.hasExpandingChild()
&& !(view instanceof FooterView);
viewState.inShelf = true;
// Notifications in the shelf cannot be visible HUNs.
viewState.headsUpIsVisible = false;
}
}
viewState.height = (int) MathUtils.lerp(
0, getMaxAllowedChildHeight(view), sectionFraction);
}
currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements;
setLocation(view.getViewState(), currentYPosition, i);
viewState.yTranslation += ambientState.getStackY();
return currentYPosition; return currentYPosition;
} }
@@ -393,10 +525,10 @@ public class StackScrollAlgorithm {
int visibleIndex, int visibleIndex,
View child, View child,
View previousChild) { View previousChild) {
return sectionProvider.beginsSection(child, previousChild)
boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild) && visibleIndex > 0
&& visibleIndex > 0; && !(previousChild instanceof SilentHeader)
return needsGapHeight; && !(child instanceof FooterView);
} }
private void updatePulsingStates(StackScrollAlgorithmState algorithmState, private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
@@ -514,42 +646,6 @@ public class StackScrollAlgorithm {
childState.yTranslation = newTranslation; childState.yTranslation = newTranslation;
} }
/**
* Clamp the height of the child down such that its end is at most on the beginning of
* the shelf.
*
* @param childViewState the view state of the child
* @param ambientState the ambient state
*/
private void clampPositionToShelf(ExpandableView child,
ExpandableViewState childViewState,
AmbientState ambientState) {
if (ambientState.getShelf() == null) {
return;
}
ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null
&& mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow);
int shelfStart = ambientState.getInnerHeight()
- ambientState.getShelf().getIntrinsicHeight();
if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) {
// Don't show none heads-up notifications while in appearing phase.
childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart);
}
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
if (child instanceof SectionHeaderView) {
// Add padding before sections for overscroll effect.
childViewState.yTranslation += ambientState.getSectionPadding();
}
if (childViewState.yTranslation >= shelfStart) {
childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
childViewState.inShelf = true;
childViewState.headsUpIsVisible = false;
}
}
protected int getMaxAllowedChildHeight(View child) { protected int getMaxAllowedChildHeight(View child) {
if (child instanceof ExpandableView) { if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child; ExpandableView expandableView = (ExpandableView) child;
@@ -641,6 +737,35 @@ public class StackScrollAlgorithm {
this.mIsExpanded = isExpanded; this.mIsExpanded = isExpanded;
} }
/**
* Data used to layout views while shade expansion changes.
*/
public class ExpansionData {
/**
* Y position of top of first view in section.
*/
public float sectionStart;
/**
* Y position of bottom of last view in section.
*/
public float sectionEnd;
/**
* Y position of view when shade is fully expanded.
* Does not include distance between top notifications panel and top of screen.
*/
public float fullyExpandedY;
/**
* Whether this notification is in the same section as the notification right before the
* shelf. Used to determine which notification should be clipped to shelf start while
* shade expansion changes.
*/
public boolean inOverflowingSection;
}
public class StackScrollAlgorithmState { public class StackScrollAlgorithmState {
/** /**
@@ -648,16 +773,27 @@ public class StackScrollAlgorithm {
*/ */
public int scrollY; public int scrollY;
/**
* First view in shelf.
*/
public ExpandableView firstViewInShelf;
/**
* First view in section overflowing into shelf while shade expansion changes.
*/
public ExpandableView firstViewInOverflowSection;
/**
* Map of view to ExpansionData used for layout during shade expansion.
* Use view instead of index as key, because visibleChildren indices do not match the ones
* used in the shelf.
*/
public Map<ExpandableView, ExpansionData> expansionData = new HashMap<>();
/** /**
* The children from the host view which are not gone. * The children from the host view which are not gone.
*/ */
public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>(); public final ArrayList<ExpandableView> visibleChildren = new ArrayList<>();
private int indexOfExpandingNotification;
public int getIndexOfExpandingNotification() {
return indexOfExpandingNotification;
}
} }
/** /**