From 1685e634fb0b14033bd436af8d7174436699ffec Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Tue, 8 Apr 2014 02:27:49 +0200 Subject: [PATCH] Further improved NotificationStackScroller The top card is now collapsed during the pulldown of the notification shade and expanded during the transition. The scrollstate is also reset once the shade is closed. Change-Id: Ibf17eef1f338d674c545e5bf55261e60db62b2ce --- .../systemui/statusbar/BaseStatusBar.java | 22 +-- .../statusbar/ExpandableNotificationRow.java | 139 ++++++++++++++++-- .../phone/NotificationPanelView.java | 13 ++ .../systemui/statusbar/phone/PanelView.java | 25 +++- .../statusbar/phone/PhoneStatusBar.java | 3 +- .../policy/HeadsUpNotificationView.java | 2 +- .../stack/NotificationStackScrollLayout.java | 26 +++- .../statusbar/stack/StackScrollAlgorithm.java | 92 +++++++++++- 8 files changed, 289 insertions(+), 33 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index f1299fed96c58..2f135ec916bee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -138,7 +138,8 @@ public abstract class BaseStatusBar extends SystemUI implements protected IDreamManager mDreamManager; PowerManager mPowerManager; - protected int mRowHeight; + protected int mRowMinHeight; + protected int mRowMaxHeight; // public mode, private notifications, etc private boolean mLockscreenPublicMode = false; @@ -880,7 +881,7 @@ public abstract class BaseStatusBar extends SystemUI implements } } entry.row = row; - entry.row.setRowHeight(mRowHeight); + entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight); entry.content = content; entry.expanded = contentViewLocal; entry.expandedPublic = publicViewLocal; @@ -1055,17 +1056,19 @@ public abstract class BaseStatusBar extends SystemUI implements } protected void updateExpansionStates() { + + // TODO: Handle user expansion better int N = mNotificationData.size(); for (int i = 0; i < N; i++) { NotificationData.Entry entry = mNotificationData.get(i); if (!entry.row.isUserLocked()) { if (i == (N-1)) { if (DEBUG) Log.d(TAG, "expanding top notification at " + i); - entry.row.setExpanded(true); + entry.row.setSystemExpanded(true); } else { if (!entry.row.isUserExpanded()) { if (DEBUG) Log.d(TAG, "collapsing notification at " + i); - entry.row.setExpanded(false); + entry.row.setSystemExpanded(false); } else { if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i); } @@ -1218,13 +1221,14 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); - final boolean wasExpanded = oldEntry.row.isUserExpanded(); removeNotificationViews(key); addNotificationViews(key, notification); // will also replace the heads up - if (wasExpanded) { - final NotificationData.Entry newEntry = mNotificationData.findByKey(key); - newEntry.row.setExpanded(true); - newEntry.row.setUserExpanded(true); + final NotificationData.Entry newEntry = mNotificationData.findByKey(key); + final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion(); + if (userChangedExpansion) { + boolean userExpanded = oldEntry.row.isUserExpanded(); + newEntry.row.applyExpansionToLayout(userExpanded); + newEntry.row.setUserExpanded(userExpanded); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index b3d8688d4b42f..2daf619d0122e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -22,30 +22,49 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import com.android.internal.widget.SizeAdaptiveLayout; import com.android.systemui.R; public class ExpandableNotificationRow extends FrameLayout { - private int mRowHeight; + private int mRowMinHeight; + private int mRowMaxHeight; - /** does this row contain layouts that can adapt to row expansion */ + /** Does this row contain layouts that can adapt to row expansion */ private boolean mExpandable; - /** has the user manually expanded this row */ + /** Has the user actively changed the expansion state of this row */ + private boolean mHasUserChangedExpansion; + /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ private boolean mUserExpanded; - /** is the user touching this row */ + /** Is the user touching this row */ private boolean mUserLocked; - /** are we showing the "public" version */ + /** Are we showing the "public" version */ private boolean mShowingPublic; + /** + * Is this notification expanded by the system. The expansion state can be overridden by the + * user expansion. + */ + private boolean mIsSystemExpanded; + private SizeAdaptiveLayout mPublicLayout; + private SizeAdaptiveLayout mPrivateLayout; + private int mMaxExpandHeight; + private boolean mMaxHeightNeedsUpdate; + public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); } - public int getRowHeight() { - return mRowHeight; + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mPublicLayout = (SizeAdaptiveLayout) findViewById(R.id.expandedPublic); + mPrivateLayout = (SizeAdaptiveLayout) findViewById(R.id.expanded); } - public void setRowHeight(int rowHeight) { - this.mRowHeight = rowHeight; + public void setHeightRange(int rowMinHeight, int rowMaxHeight) { + mRowMinHeight = rowMinHeight; + mRowMaxHeight = rowMaxHeight; + mMaxHeightNeedsUpdate = true; } public boolean isExpandable() { @@ -56,11 +75,24 @@ public class ExpandableNotificationRow extends FrameLayout { mExpandable = expandable; } + /** + * @return whether the user has changed the expansion state + */ + public boolean hasUserChangedExpansion() { + return mHasUserChangedExpansion; + } + public boolean isUserExpanded() { return mUserExpanded; } + /** + * Set this notification to be expanded by the user + * + * @param userExpanded whether the user wants this notification to be expanded + */ public void setUserExpanded(boolean userExpanded) { + mHasUserChangedExpansion = true; mUserExpanded = userExpanded; } @@ -72,25 +104,102 @@ public class ExpandableNotificationRow extends FrameLayout { mUserLocked = userLocked; } - public void setExpanded(boolean expand) { + /** + * @return has the system set this notification to be expanded + */ + public boolean isSystemExpanded() { + return mIsSystemExpanded; + } + + /** + * Set this notification to be expanded by the system. + * + * @param expand whether the system wants this notification to be expanded. + */ + public void setSystemExpanded(boolean expand) { + mIsSystemExpanded = expand; + applyExpansionToLayout(expand); + } + + /** + * Apply an expansion state to the layout. + * + * @param expand should the layout be in the expanded state + */ + public void applyExpansionToLayout(boolean expand) { ViewGroup.LayoutParams lp = getLayoutParams(); if (expand && mExpandable) { lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; } else { - lp.height = mRowHeight; + lp.height = mRowMinHeight; } setLayoutParams(lp); } + /** + * If {@link #isExpanded()} then this is the greatest possible height this view can + * get and otherwise it is {@link #mRowMinHeight}. + * + * @return the maximum allowed expansion height of this view. + */ + public int getMaximumAllowedExpandHeight() { + boolean inExpansionState = isExpanded(); + if (!inExpansionState) { + // not expanded, so we return the collapsed size + return mRowMinHeight; + } + + return mShowingPublic ? mRowMinHeight : getMaxExpandHeight(); + } + + + + private void updateMaxExpandHeight() { + ViewGroup.LayoutParams lp = getLayoutParams(); + int oldHeight = lp.height; + lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + setLayoutParams(lp); + measure(View.MeasureSpec.makeMeasureSpec(getMeasuredWidth(), View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(mRowMaxHeight, View.MeasureSpec.AT_MOST)); + lp.height = oldHeight; + setLayoutParams(lp); + mMaxExpandHeight = getMeasuredHeight(); + } + + /** + * Check whether the view state is currently expanded. This is given by the system in {@link + * #setSystemExpanded(boolean)} and can be overridden by user expansion or + * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this + * view can differ from this state, if layout params are modified from outside. + * + * @return whether the view state is currently expanded. + */ + private boolean isExpanded() { + return !hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mMaxHeightNeedsUpdate = true; + } + public void setShowingPublic(boolean show) { mShowingPublic = show; - final ViewGroup publicLayout = (ViewGroup) findViewById(R.id.expandedPublic); // bail out if no public version - if (publicLayout.getChildCount() == 0) return; + if (mPublicLayout.getChildCount() == 0) return; // TODO: animation? - publicLayout.setVisibility(show ? View.VISIBLE : View.GONE); - findViewById(R.id.expanded).setVisibility(show ? View.GONE : View.VISIBLE); + mPublicLayout.setVisibility(show ? View.VISIBLE : View.GONE); + mPrivateLayout.setVisibility(show ? View.GONE : View.VISIBLE); + } + + public int getMaxExpandHeight() { + if (mMaxHeightNeedsUpdate) { + updateMaxExpandHeight(); + mMaxHeightNeedsUpdate = false; + } + return mMaxExpandHeight; } } 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 2d2f2f121e4e0..6f93bedbaf5db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -139,6 +139,7 @@ public class NotificationPanelView extends PanelView { * @param expandedHeight the new expanded height */ private void updateNotificationStackHeight(float expandedHeight) { + mNotificationStackScroller.setIsExpanded(expandedHeight > 0.0f); float childOffset = getRelativeTop(mNotificationStackScroller) - mNotificationParent.getTranslationY(); int newStackHeight = (int) (expandedHeight - childOffset); @@ -168,4 +169,16 @@ public class NotificationPanelView extends PanelView { protected int getDesiredMeasureHeight() { return mMaxPanelHeight; } + + @Override + protected void onExpandingStarted() { + super.onExpandingStarted(); + mNotificationStackScroller.onExpansionStarted(); + } + + @Override + protected void onExpandingFinished() { + super.onExpandingFinished(); + mNotificationStackScroller.onExpansionStopped(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 20fb225c83821..6922ca55285d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -216,6 +216,7 @@ public class PanelView extends FrameLayout { mTimeAnimator.end(); mRubberbanding = false; mClosing = false; + onExpandingFinished(); } } }; @@ -230,6 +231,12 @@ public class PanelView extends FrameLayout { mRubberbandingEnabled = enable; } + protected void onExpandingFinished() { + } + + protected void onExpandingStarted() { + } + private void runPeekAnimation() { if (DEBUG) logf("peek to height=%.1f", mPeekHeight); if (mTimeAnimator.isStarted()) { @@ -398,7 +405,7 @@ public class PanelView extends FrameLayout { initVelocityTracker(); trackMovement(event); mTimeAnimator.cancel(); // end any outstanding animations - mBar.onTrackingStarted(PanelView.this); + onTrackingStarted(); mInitialOffsetOnTouch = mExpandedHeight; if (mExpandedHeight == 0) { mJustPeeked = true; @@ -443,7 +450,7 @@ public class PanelView extends FrameLayout { mHandleView.setPressed(false); postInvalidate(); // catch the press state change } - mBar.onTrackingStopped(PanelView.this); + onTrackingStopped(); trackMovement(event); float vel = getCurrentVelocity(); @@ -458,6 +465,15 @@ public class PanelView extends FrameLayout { return true; } + protected void onTrackingStopped() { + mBar.onTrackingStopped(PanelView.this); + } + + protected void onTrackingStarted() { + mBar.onTrackingStarted(PanelView.this); + onExpandingStarted(); + } + private float getCurrentVelocity() { float vel = 0; float yVel = 0, xVel = 0; @@ -561,6 +577,7 @@ public class PanelView extends FrameLayout { mInitialOffsetOnTouch = mExpandedHeight; mInitialTouchY = y; mTracking = true; + onTrackingStarted(); return true; } } @@ -598,6 +615,8 @@ public class PanelView extends FrameLayout { if (always||mVel != 0) { animationTick(0); // begin the animation + } else { + onExpandingFinished(); } } @@ -770,6 +789,7 @@ public class PanelView extends FrameLayout { if (!isFullyCollapsed()) { mTimeAnimator.cancel(); mClosing = true; + onExpandingStarted(); // collapse() should never be a rubberband, even if an animation is already running mRubberbanding = false; fling(-mSelfCollapseVelocityPx, /*always=*/ true); @@ -780,6 +800,7 @@ public class PanelView extends FrameLayout { if (DEBUG) logf("expand: " + this); if (isFullyCollapsed()) { mBar.startOpeningPanel(this); + onExpandingStarted(); fling(mSelfExpandVelocityPx, /*always=*/ true); } else if (DEBUG) { if (DEBUG) logf("skipping expansion: is expanded"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index f3dd7e3dbe04c..841f3ca1e545e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -2641,7 +2641,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } mHeadsUpNotificationDecay = res.getInteger(R.integer.heads_up_notification_decay); - mRowHeight = res.getDimensionPixelSize(R.dimen.notification_row_min_height); + mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_row_min_height); + mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_row_max_height); if (false) Log.v(TAG, "updateResources"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java index 79932a7ca8022..2dba669eacd79 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java @@ -78,7 +78,7 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. } if (mHeadsUp != null) { - mHeadsUp.row.setExpanded(true); + mHeadsUp.row.setSystemExpanded(true); mHeadsUp.row.setShowingPublic(false); if (mContentHolder == null) { // too soon! diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index e2d6c5b5f54ef..f31896a07e326 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -89,7 +89,7 @@ public class NotificationStackScrollLayout extends ViewGroup * The current State this Layout is in */ private final StackScrollState mCurrentStackScrollState = new StackScrollState(this); - + private OnChildLocationsChangedListener mListener; public NotificationStackScrollLayout(Context context) { @@ -336,7 +336,7 @@ public class NotificationStackScrollLayout extends ViewGroup continue; } float top = slidingChild.getTranslationY(); - float bottom = top + slidingChild.getMeasuredHeight(); + float bottom = top + slidingChild.getHeight(); int left = slidingChild.getLeft(); int right = slidingChild.getRight(); @@ -713,6 +713,13 @@ public class NotificationStackScrollLayout extends ViewGroup protected void onViewRemoved(View child) { super.onViewRemoved(child); mCurrentStackScrollState.removeViewStateForView(child); + mStackScrollAlgorithm.notifyChildrenChanged(this); + } + + @Override + protected void onViewAdded(View child) { + super.onViewAdded(child); + mStackScrollAlgorithm.notifyChildrenChanged(this); } private boolean onInterceptTouchEventScroll(MotionEvent ev) { @@ -858,6 +865,21 @@ public class NotificationStackScrollLayout extends ViewGroup return Math.max(getHeight() - mContentHeight, 0); } + public void onExpansionStarted() { + mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState); + } + + public void onExpansionStopped() { + mStackScrollAlgorithm.onExpansionStopped(); + } + + public void setIsExpanded(boolean isExpanded) { + mStackScrollAlgorithm.setIsExpanded(isExpanded); + if (!isExpanded) { + mOwnScrollY = 0; + } + } + /** * A listener that is notified when some child locations might have changed. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index 4745f3bd53565..5506a55585afc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -21,6 +21,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; /** * The Algorithm of the {@link com.android.systemui.statusbar.stack @@ -46,6 +47,11 @@ public class StackScrollAlgorithm { private float mLayoutHeight; private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); + private boolean mIsExpansionChanging; + private int mFirstChildMaxHeight; + private boolean mIsExpanded; + private View mFirstChildWhileExpanding; + private boolean mExpandedOnStart; public StackScrollAlgorithm(Context context) { initConstants(context); @@ -117,8 +123,11 @@ public class StackScrollAlgorithm { StackScrollAlgorithmState algorithmState) { float stackHeight = getLayoutHeight(); + // The starting position of the bottom stack peek + float bottomPeekStart = stackHeight - mBottomStackPeekSize; + // The position where the bottom stack starts. - float transitioningPositionStart = stackHeight - mCollapsedSize - mBottomStackPeekSize; + float transitioningPositionStart = bottomPeekStart - mCollapsedSize; // The y coordinate of the current child. float currentYPosition = 0.0f; @@ -151,8 +160,8 @@ public class StackScrollAlgorithm { // Case 2: // First element of regular scrollview comes next, so the position is just the // scrolling position - nextYPosition = Math.min(scrollOffset, transitioningPositionStart); - childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING; + nextYPosition = updateStateForFirstScrollingChild(transitioningPositionStart, + childViewState, scrollOffset); } else if (nextYPosition >= transitioningPositionStart) { if (currentYPosition >= transitioningPositionStart) { // Case 3: @@ -186,6 +195,32 @@ public class StackScrollAlgorithm { } } + /** + * Update the state for the first child which is in the regular scrolling area. + * + * @param transitioningPositionStart the transition starting position of the bottom stack + * @param childViewState the view state of the child + * @param scrollOffset the position in the regular scroll view after this child + * @return the next child position + */ + private float updateStateForFirstScrollingChild(float transitioningPositionStart, + StackScrollState.ViewState childViewState, float scrollOffset) { + childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING; + if (scrollOffset < transitioningPositionStart) { + return scrollOffset; + } else { + return transitioningPositionStart; + } + } + + private int getMaxAllowedChildHeight(View child) { + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + return row.getMaximumAllowedExpandHeight(); + } + return child.getHeight(); + } + private float updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState, float stackHeight, float transitioningPositionStart, float currentYPosition, StackScrollState.ViewState childViewState, int childHeight, float nextYPosition) { @@ -335,7 +370,17 @@ public class StackScrollAlgorithm { } } else { algorithmState.lastTopStackIndex = i; + if (i == 0) { + // The starting position of the bottom stack peek + float bottomPeekStart = getLayoutHeight() - mBottomStackPeekSize; + // Collapse and expand the first child while the shade is being expanded + float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding + ? mFirstChildMaxHeight + : childHeight; + childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight), + mCollapsedSize); + } // We are already past the stack so we can end the loop break; } @@ -381,6 +426,47 @@ public class StackScrollAlgorithm { this.mLayoutHeight = layoutHeight; } + public void onExpansionStarted(StackScrollState currentState) { + mIsExpansionChanging = true; + mExpandedOnStart = mIsExpanded; + ViewGroup hostView = currentState.getHostView(); + updateFirstChildHeightWhileExpanding(hostView); + } + + private void updateFirstChildHeightWhileExpanding(ViewGroup hostView) { + if (hostView.getChildCount() > 0) { + mFirstChildWhileExpanding = hostView.getChildAt(0); + if (mExpandedOnStart) { + + // We are collapsing the shade, so the first child can get as most as high as the + // current height. + mFirstChildMaxHeight = mFirstChildWhileExpanding.getHeight(); + } else { + + // We are expanding the shade, expand it to its full height. + mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding); + } + } else { + mFirstChildWhileExpanding = null; + mFirstChildMaxHeight = 0; + } + } + + public void onExpansionStopped() { + mIsExpansionChanging = false; + mFirstChildWhileExpanding = null; + } + + public void setIsExpanded(boolean isExpanded) { + this.mIsExpanded = isExpanded; + } + + public void notifyChildrenChanged(ViewGroup hostView) { + if (mIsExpansionChanging) { + updateFirstChildHeightWhileExpanding(hostView); + } + } + class StackScrollAlgorithmState { /**