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 { /**