Home | History | Annotate | Download | only in stack
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.systemui.statusbar.stack;
     18 
     19 import android.content.Context;
     20 import android.util.Log;
     21 import android.view.View;
     22 import android.view.ViewGroup;
     23 
     24 import com.android.systemui.R;
     25 import com.android.systemui.statusbar.ExpandableNotificationRow;
     26 import com.android.systemui.statusbar.ExpandableView;
     27 import com.android.systemui.statusbar.notification.FakeShadowView;
     28 import com.android.systemui.statusbar.notification.NotificationUtils;
     29 
     30 import java.util.ArrayList;
     31 import java.util.HashMap;
     32 import java.util.List;
     33 
     34 /**
     35  * The Algorithm of the {@link com.android.systemui.statusbar.stack
     36  * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar
     37  * .stack.StackScrollState}
     38  */
     39 public class StackScrollAlgorithm {
     40 
     41     private static final String LOG_TAG = "StackScrollAlgorithm";
     42 
     43     private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
     44 
     45     private int mPaddingBetweenElements;
     46     private int mIncreasedPaddingBetweenElements;
     47     private int mCollapsedSize;
     48     private int mBottomStackPeekSize;
     49     private int mZDistanceBetweenElements;
     50     private int mZBasicHeight;
     51 
     52     private StackIndentationFunctor mBottomStackIndentationFunctor;
     53 
     54     private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
     55     private boolean mIsExpanded;
     56     private int mBottomStackSlowDownLength;
     57 
     58     public StackScrollAlgorithm(Context context) {
     59         initView(context);
     60     }
     61 
     62     public void initView(Context context) {
     63         initConstants(context);
     64     }
     65 
     66     public int getBottomStackSlowDownLength() {
     67         return mBottomStackSlowDownLength + mPaddingBetweenElements;
     68     }
     69 
     70     private void initConstants(Context context) {
     71         mPaddingBetweenElements = Math.max(1, context.getResources()
     72                 .getDimensionPixelSize(R.dimen.notification_divider_height));
     73         mIncreasedPaddingBetweenElements = context.getResources()
     74                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
     75         mCollapsedSize = context.getResources()
     76                 .getDimensionPixelSize(R.dimen.notification_min_height);
     77         mBottomStackPeekSize = context.getResources()
     78                 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
     79         mZDistanceBetweenElements = Math.max(1, context.getResources()
     80                 .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
     81         mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
     82         mBottomStackSlowDownLength = context.getResources()
     83                 .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length);
     84         mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
     85                 MAX_ITEMS_IN_BOTTOM_STACK,
     86                 mBottomStackPeekSize,
     87                 getBottomStackSlowDownLength(),
     88                 0.5f);
     89     }
     90 
     91     public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
     92         // The state of the local variables are saved in an algorithmState to easily subdivide it
     93         // into multiple phases.
     94         StackScrollAlgorithmState algorithmState = mTempAlgorithmState;
     95 
     96         // First we reset the view states to their default values.
     97         resultState.resetViewStates();
     98 
     99         initAlgorithmState(resultState, algorithmState, ambientState);
    100 
    101         updatePositionsForState(resultState, algorithmState, ambientState);
    102 
    103         updateZValuesForState(resultState, algorithmState, ambientState);
    104 
    105         updateHeadsUpStates(resultState, algorithmState, ambientState);
    106 
    107         handleDraggedViews(ambientState, resultState, algorithmState);
    108         updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
    109         updateClipping(resultState, algorithmState, ambientState);
    110         updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
    111         getNotificationChildrenStates(resultState, algorithmState);
    112     }
    113 
    114     private void getNotificationChildrenStates(StackScrollState resultState,
    115             StackScrollAlgorithmState algorithmState) {
    116         int childCount = algorithmState.visibleChildren.size();
    117         for (int i = 0; i < childCount; i++) {
    118             ExpandableView v = algorithmState.visibleChildren.get(i);
    119             if (v instanceof ExpandableNotificationRow) {
    120                 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    121                 row.getChildrenStates(resultState);
    122             }
    123         }
    124     }
    125 
    126     private void updateSpeedBumpState(StackScrollState resultState,
    127             StackScrollAlgorithmState algorithmState, int speedBumpIndex) {
    128         int childCount = algorithmState.visibleChildren.size();
    129         for (int i = 0; i < childCount; i++) {
    130             View child = algorithmState.visibleChildren.get(i);
    131             StackViewState childViewState = resultState.getViewStateForView(child);
    132 
    133             // The speed bump can also be gone, so equality needs to be taken when comparing
    134             // indices.
    135             childViewState.belowSpeedBump = speedBumpIndex != -1 && i >= speedBumpIndex;
    136         }
    137     }
    138 
    139     private void updateClipping(StackScrollState resultState,
    140             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
    141         float drawStart = ambientState.getTopPadding() + ambientState.getStackTranslation();
    142         float previousNotificationEnd = 0;
    143         float previousNotificationStart = 0;
    144         int childCount = algorithmState.visibleChildren.size();
    145         for (int i = 0; i < childCount; i++) {
    146             ExpandableView child = algorithmState.visibleChildren.get(i);
    147             StackViewState state = resultState.getViewStateForView(child);
    148             if (!child.mustStayOnScreen()) {
    149                 previousNotificationEnd = Math.max(drawStart, previousNotificationEnd);
    150                 previousNotificationStart = Math.max(drawStart, previousNotificationStart);
    151             }
    152             float newYTranslation = state.yTranslation;
    153             float newHeight = state.height;
    154             float newNotificationEnd = newYTranslation + newHeight;
    155             boolean isHeadsUp = (child instanceof ExpandableNotificationRow)
    156                     && ((ExpandableNotificationRow) child).isPinned();
    157             if (newYTranslation < previousNotificationEnd
    158                     && (!isHeadsUp || ambientState.isShadeExpanded())) {
    159                 // The previous view is overlapping on top, clip!
    160                 float overlapAmount = previousNotificationEnd - newYTranslation;
    161                 state.clipTopAmount = (int) overlapAmount;
    162             } else {
    163                 state.clipTopAmount = 0;
    164             }
    165 
    166             if (!child.isTransparent()) {
    167                 // Only update the previous values if we are not transparent,
    168                 // otherwise we would clip to a transparent view.
    169                 previousNotificationEnd = newNotificationEnd;
    170                 previousNotificationStart = newYTranslation;
    171             }
    172         }
    173     }
    174 
    175     public static boolean canChildBeDismissed(View v) {
    176         if (v instanceof ExpandableNotificationRow) {
    177             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    178             if (row.areGutsExposed()) {
    179                 return false;
    180             }
    181         }
    182         final View veto = v.findViewById(R.id.veto);
    183         return (veto != null && veto.getVisibility() != View.GONE);
    184     }
    185 
    186     /**
    187      * Updates the dimmed, activated and hiding sensitive states of the children.
    188      */
    189     private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
    190             StackScrollState resultState, StackScrollAlgorithmState algorithmState) {
    191         boolean dimmed = ambientState.isDimmed();
    192         boolean dark = ambientState.isDark();
    193         boolean hideSensitive = ambientState.isHideSensitive();
    194         View activatedChild = ambientState.getActivatedChild();
    195         int childCount = algorithmState.visibleChildren.size();
    196         for (int i = 0; i < childCount; i++) {
    197             View child = algorithmState.visibleChildren.get(i);
    198             StackViewState childViewState = resultState.getViewStateForView(child);
    199             childViewState.dimmed = dimmed;
    200             childViewState.dark = dark;
    201             childViewState.hideSensitive = hideSensitive;
    202             boolean isActivatedChild = activatedChild == child;
    203             if (dimmed && isActivatedChild) {
    204                 childViewState.zTranslation += 2.0f * mZDistanceBetweenElements;
    205             }
    206         }
    207     }
    208 
    209     /**
    210      * Handle the special state when views are being dragged
    211      */
    212     private void handleDraggedViews(AmbientState ambientState, StackScrollState resultState,
    213             StackScrollAlgorithmState algorithmState) {
    214         ArrayList<View> draggedViews = ambientState.getDraggedViews();
    215         for (View draggedView : draggedViews) {
    216             int childIndex = algorithmState.visibleChildren.indexOf(draggedView);
    217             if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) {
    218                 View nextChild = algorithmState.visibleChildren.get(childIndex + 1);
    219                 if (!draggedViews.contains(nextChild)) {
    220                     // only if the view is not dragged itself we modify its state to be fully
    221                     // visible
    222                     StackViewState viewState = resultState.getViewStateForView(
    223                             nextChild);
    224                     // The child below the dragged one must be fully visible
    225                     if (ambientState.isShadeExpanded()) {
    226                         viewState.shadowAlpha = 1;
    227                         viewState.hidden = false;
    228                     }
    229                 }
    230 
    231                 // Lets set the alpha to the one it currently has, as its currently being dragged
    232                 StackViewState viewState = resultState.getViewStateForView(draggedView);
    233                 // The dragged child should keep the set alpha
    234                 viewState.alpha = draggedView.getAlpha();
    235             }
    236         }
    237     }
    238 
    239     /**
    240      * Initialize the algorithm state like updating the visible children.
    241      */
    242     private void initAlgorithmState(StackScrollState resultState, StackScrollAlgorithmState state,
    243             AmbientState ambientState) {
    244         state.itemsInBottomStack = 0.0f;
    245         state.partialInBottom = 0.0f;
    246         float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
    247 
    248         int scrollY = ambientState.getScrollY();
    249 
    250         // Due to the overScroller, the stackscroller can have negative scroll state. This is
    251         // already accounted for by the top padding and doesn't need an additional adaption
    252         scrollY = Math.max(0, scrollY);
    253         state.scrollY = (int) (scrollY + bottomOverScroll);
    254 
    255         //now init the visible children and update paddings
    256         ViewGroup hostView = resultState.getHostView();
    257         int childCount = hostView.getChildCount();
    258         state.visibleChildren.clear();
    259         state.visibleChildren.ensureCapacity(childCount);
    260         state.increasedPaddingMap.clear();
    261         int notGoneIndex = 0;
    262         ExpandableView lastView = null;
    263         for (int i = 0; i < childCount; i++) {
    264             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
    265             if (v.getVisibility() != View.GONE) {
    266                 notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
    267                 float increasedPadding = v.getIncreasedPaddingAmount();
    268                 if (increasedPadding != 0.0f) {
    269                     state.increasedPaddingMap.put(v, increasedPadding);
    270                     if (lastView != null) {
    271                         Float prevValue = state.increasedPaddingMap.get(lastView);
    272                         float newValue = prevValue != null
    273                                 ? Math.max(prevValue, increasedPadding)
    274                                 : increasedPadding;
    275                         state.increasedPaddingMap.put(lastView, newValue);
    276                     }
    277                 }
    278                 if (v instanceof ExpandableNotificationRow) {
    279                     ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    280 
    281                     // handle the notgoneIndex for the children as well
    282                     List<ExpandableNotificationRow> children =
    283                             row.getNotificationChildren();
    284                     if (row.isSummaryWithChildren() && children != null) {
    285                         for (ExpandableNotificationRow childRow : children) {
    286                             if (childRow.getVisibility() != View.GONE) {
    287                                 StackViewState childState
    288                                         = resultState.getViewStateForView(childRow);
    289                                 childState.notGoneIndex = notGoneIndex;
    290                                 notGoneIndex++;
    291                             }
    292                         }
    293                     }
    294                 }
    295                 lastView = v;
    296             }
    297         }
    298     }
    299 
    300     private int updateNotGoneIndex(StackScrollState resultState,
    301             StackScrollAlgorithmState state, int notGoneIndex,
    302             ExpandableView v) {
    303         StackViewState viewState = resultState.getViewStateForView(v);
    304         viewState.notGoneIndex = notGoneIndex;
    305         state.visibleChildren.add(v);
    306         notGoneIndex++;
    307         return notGoneIndex;
    308     }
    309 
    310     /**
    311      * Determine the positions for the views. This is the main part of the algorithm.
    312      *
    313      * @param resultState The result state to update if a change to the properties of a child occurs
    314      * @param algorithmState The state in which the current pass of the algorithm is currently in
    315      * @param ambientState The current ambient state
    316      */
    317     private void updatePositionsForState(StackScrollState resultState,
    318             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
    319 
    320         // The starting position of the bottom stack peek
    321         float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize;
    322 
    323         // The position where the bottom stack starts.
    324         float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength;
    325 
    326         // The y coordinate of the current child.
    327         float currentYPosition = -algorithmState.scrollY;
    328 
    329         int childCount = algorithmState.visibleChildren.size();
    330         int paddingAfterChild;
    331         for (int i = 0; i < childCount; i++) {
    332             ExpandableView child = algorithmState.visibleChildren.get(i);
    333             StackViewState childViewState = resultState.getViewStateForView(child);
    334             childViewState.location = StackViewState.LOCATION_UNKNOWN;
    335             paddingAfterChild = getPaddingAfterChild(algorithmState, child);
    336             int childHeight = getMaxAllowedChildHeight(child);
    337             int collapsedHeight = child.getCollapsedHeight();
    338             childViewState.yTranslation = currentYPosition;
    339             if (i == 0) {
    340                 updateFirstChildHeight(child, childViewState, childHeight, ambientState);
    341             }
    342 
    343             // The y position after this element
    344             float nextYPosition = currentYPosition + childHeight +
    345                     paddingAfterChild;
    346             if (nextYPosition >= bottomStackStart) {
    347                 // Case 1:
    348                 // We are in the bottom stack.
    349                 if (currentYPosition >= bottomStackStart) {
    350                     // According to the regular scroll view we are fully translated out of the
    351                     // bottom of the screen so we are fully in the bottom stack
    352                     updateStateForChildFullyInBottomStack(algorithmState,
    353                             bottomStackStart, childViewState, collapsedHeight, ambientState, child);
    354                 } else {
    355                     // According to the regular scroll view we are currently translating out of /
    356                     // into the bottom of the screen
    357                     updateStateForChildTransitioningInBottom(algorithmState,
    358                             bottomStackStart, child, currentYPosition,
    359                             childViewState, childHeight);
    360                 }
    361             } else {
    362                 // Case 2:
    363                 // We are in the regular scroll area.
    364                 childViewState.location = StackViewState.LOCATION_MAIN_AREA;
    365                 clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
    366                         ambientState);
    367             }
    368 
    369             if (i == 0 && ambientState.getScrollY() <= 0) {
    370                 // The first card can get into the bottom stack if it's the only one
    371                 // on the lockscreen which pushes it up. Let's make sure that doesn't happen and
    372                 // it stays at the top
    373                 childViewState.yTranslation = Math.max(0, childViewState.yTranslation);
    374             }
    375             currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
    376             if (currentYPosition <= 0) {
    377                 childViewState.location = StackViewState.LOCATION_HIDDEN_TOP;
    378             }
    379             if (childViewState.location == StackViewState.LOCATION_UNKNOWN) {
    380                 Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
    381             }
    382 
    383             childViewState.yTranslation += ambientState.getTopPadding()
    384                     + ambientState.getStackTranslation();
    385         }
    386     }
    387 
    388     private int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
    389             ExpandableView child) {
    390         Float paddingValue = algorithmState.increasedPaddingMap.get(child);
    391         return paddingValue == null
    392                 ? mPaddingBetweenElements
    393                 : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
    394                         mIncreasedPaddingBetweenElements,
    395                         paddingValue);
    396     }
    397 
    398     private void updateHeadsUpStates(StackScrollState resultState,
    399             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
    400         int childCount = algorithmState.visibleChildren.size();
    401         ExpandableNotificationRow topHeadsUpEntry = null;
    402         for (int i = 0; i < childCount; i++) {
    403             View child = algorithmState.visibleChildren.get(i);
    404             if (!(child instanceof ExpandableNotificationRow)) {
    405                 break;
    406             }
    407             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
    408             if (!row.isHeadsUp()) {
    409                 break;
    410             }
    411             StackViewState childState = resultState.getViewStateForView(row);
    412             if (topHeadsUpEntry == null) {
    413                 topHeadsUpEntry = row;
    414                 childState.location = StackViewState.LOCATION_FIRST_HUN;
    415             }
    416             boolean isTopEntry = topHeadsUpEntry == row;
    417             float unmodifiedEndLocation = childState.yTranslation + childState.height;
    418             if (mIsExpanded) {
    419                 // Ensure that the heads up is always visible even when scrolled off
    420                 clampHunToTop(ambientState, row, childState);
    421                 clampHunToMaxTranslation(ambientState, row, childState);
    422             }
    423             if (row.isPinned()) {
    424                 childState.yTranslation = Math.max(childState.yTranslation, 0);
    425                 childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
    426                 StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
    427                 if (!isTopEntry && (!mIsExpanded
    428                         || unmodifiedEndLocation < topState.yTranslation + topState.height)) {
    429                     // Ensure that a headsUp doesn't vertically extend further than the heads-up at
    430                     // the top most z-position
    431                     childState.height = row.getIntrinsicHeight();
    432                     childState.yTranslation = topState.yTranslation + topState.height
    433                             - childState.height;
    434                 }
    435             }
    436         }
    437     }
    438 
    439     private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
    440             StackViewState childState) {
    441         float newTranslation = Math.max(ambientState.getTopPadding()
    442                 + ambientState.getStackTranslation(), childState.yTranslation);
    443         childState.height = (int) Math.max(childState.height - (newTranslation
    444                 - childState.yTranslation), row.getCollapsedHeight());
    445         childState.yTranslation = newTranslation;
    446     }
    447 
    448     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
    449             StackViewState childState) {
    450         float newTranslation;
    451         float bottomPosition = ambientState.getMaxHeadsUpTranslation() - row.getCollapsedHeight();
    452         newTranslation = Math.min(childState.yTranslation, bottomPosition);
    453         childState.height = (int) Math.max(childState.height
    454                 - (childState.yTranslation - newTranslation), row.getCollapsedHeight());
    455         childState.yTranslation = newTranslation;
    456     }
    457 
    458     /**
    459      * Clamp the yTranslation of the child down such that its end is at most on the beginning of
    460      * the bottom stack.
    461      *
    462      * @param childViewState the view state of the child
    463      * @param childHeight the height of this child
    464      * @param minHeight the minumum Height of the View
    465      */
    466     private void clampPositionToBottomStackStart(StackViewState childViewState,
    467             int childHeight, int minHeight, AmbientState ambientState) {
    468 
    469         int bottomStackStart = ambientState.getInnerHeight()
    470                 - mBottomStackPeekSize - mBottomStackSlowDownLength;
    471         int childStart = bottomStackStart - childHeight;
    472         if (childStart < childViewState.yTranslation) {
    473             float newHeight = bottomStackStart - childViewState.yTranslation;
    474             if (newHeight < minHeight) {
    475                 newHeight = minHeight;
    476                 childViewState.yTranslation = bottomStackStart - minHeight;
    477             }
    478             childViewState.height = (int) newHeight;
    479         }
    480     }
    481 
    482     private int getMaxAllowedChildHeight(View child) {
    483         if (child instanceof ExpandableView) {
    484             ExpandableView expandableView = (ExpandableView) child;
    485             return expandableView.getIntrinsicHeight();
    486         }
    487         return child == null? mCollapsedSize : child.getHeight();
    488     }
    489 
    490     private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
    491             float transitioningPositionStart, ExpandableView child, float currentYPosition,
    492             StackViewState childViewState, int childHeight) {
    493 
    494         // This is the transitioning element on top of bottom stack, calculate how far we are in.
    495         algorithmState.partialInBottom = 1.0f - (
    496                 (transitioningPositionStart - currentYPosition) / (childHeight +
    497                         getPaddingAfterChild(algorithmState, child)));
    498 
    499         // the offset starting at the transitionPosition of the bottom stack
    500         float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
    501         algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
    502         int newHeight = childHeight;
    503         if (childHeight > child.getCollapsedHeight()) {
    504             newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset -
    505                     getPaddingAfterChild(algorithmState, child) - currentYPosition, childHeight),
    506                     child.getCollapsedHeight());
    507             childViewState.height = newHeight;
    508         }
    509         childViewState.yTranslation = transitioningPositionStart + offset - newHeight
    510                 - getPaddingAfterChild(algorithmState, child);
    511         childViewState.location = StackViewState.LOCATION_MAIN_AREA;
    512     }
    513 
    514     private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
    515             float transitioningPositionStart, StackViewState childViewState,
    516             int collapsedHeight, AmbientState ambientState, ExpandableView child) {
    517         float currentYPosition;
    518         algorithmState.itemsInBottomStack += 1.0f;
    519         if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
    520             // We are visually entering the bottom stack
    521             currentYPosition = transitioningPositionStart
    522                     + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack)
    523                     - getPaddingAfterChild(algorithmState, child);
    524             childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING;
    525         } else {
    526             // we are fully inside the stack
    527             if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) {
    528                 childViewState.hidden = true;
    529                 childViewState.shadowAlpha = 0.0f;
    530             } else if (algorithmState.itemsInBottomStack
    531                     > MAX_ITEMS_IN_BOTTOM_STACK + 1) {
    532                 childViewState.shadowAlpha = 1.0f - algorithmState.partialInBottom;
    533             }
    534             childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN;
    535             currentYPosition = ambientState.getInnerHeight();
    536         }
    537         childViewState.height = collapsedHeight;
    538         childViewState.yTranslation = currentYPosition - collapsedHeight;
    539     }
    540 
    541 
    542     /**
    543      * Update the height of the first child i.e clamp it to the bottom stack
    544      *
    545      * @param child the child to update
    546      * @param childViewState the viewstate of the child
    547      * @param childHeight the height of the child
    548      * @param ambientState The ambient state of the algorithm
    549      */
    550     private void updateFirstChildHeight(ExpandableView child, StackViewState childViewState,
    551             int childHeight, AmbientState ambientState) {
    552 
    553             // The starting position of the bottom stack peek
    554             int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
    555                     mBottomStackSlowDownLength + ambientState.getScrollY();
    556             // Collapse and expand the first child while the shade is being expanded
    557         childViewState.height = (int) Math.max(Math.min(bottomPeekStart, (float) childHeight),
    558                     child.getCollapsedHeight());
    559     }
    560 
    561     /**
    562      * Calculate the Z positions for all children based on the number of items in both stacks and
    563      * save it in the resultState
    564      *  @param resultState The result state to update the zTranslation values
    565      * @param algorithmState The state in which the current pass of the algorithm is currently in
    566      * @param ambientState The ambient state of the algorithm
    567      */
    568     private void updateZValuesForState(StackScrollState resultState,
    569             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
    570         int childCount = algorithmState.visibleChildren.size();
    571         float childrenOnTop = 0.0f;
    572         for (int i = childCount - 1; i >= 0; i--) {
    573             ExpandableView child = algorithmState.visibleChildren.get(i);
    574             StackViewState childViewState = resultState.getViewStateForView(child);
    575             if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
    576                 // We are in the bottom stack
    577                 float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
    578                 float zSubtraction;
    579                 if (numItemsAbove <= 1.0f) {
    580                     float factor = 0.2f;
    581                     // Lets fade in slower to the threshold to make the shadow fade in look nicer
    582                     if (numItemsAbove <= factor) {
    583                         zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
    584                                 * numItemsAbove * (1.0f / factor);
    585                     } else {
    586                         zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
    587                                 + (numItemsAbove - factor) * (1.0f / (1.0f - factor))
    588                                         * (mZDistanceBetweenElements
    589                                                 - FakeShadowView.SHADOW_SIBLING_TRESHOLD);
    590                     }
    591                 } else {
    592                     zSubtraction = numItemsAbove * mZDistanceBetweenElements;
    593                 }
    594                 childViewState.zTranslation = mZBasicHeight - zSubtraction;
    595             } else if (child.mustStayOnScreen()
    596                     && childViewState.yTranslation < ambientState.getTopPadding()
    597                     + ambientState.getStackTranslation()) {
    598                 if (childrenOnTop != 0.0f) {
    599                     childrenOnTop++;
    600                 } else {
    601                     float overlap = ambientState.getTopPadding()
    602                             + ambientState.getStackTranslation() - childViewState.yTranslation;
    603                     childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
    604                 }
    605                 childViewState.zTranslation = mZBasicHeight
    606                         + childrenOnTop * mZDistanceBetweenElements;
    607             } else {
    608                 childViewState.zTranslation = mZBasicHeight;
    609             }
    610         }
    611     }
    612 
    613     private boolean isMaxSizeInitialized(ExpandableView child) {
    614         if (child instanceof ExpandableNotificationRow) {
    615             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
    616             return row.isMaxExpandHeightInitialized();
    617         }
    618         return child == null || child.getWidth() != 0;
    619     }
    620 
    621     private View findFirstVisibleChild(ViewGroup container) {
    622         int childCount = container.getChildCount();
    623         for (int i = 0; i < childCount; i++) {
    624             View child = container.getChildAt(i);
    625             if (child.getVisibility() != View.GONE) {
    626                 return child;
    627             }
    628         }
    629         return null;
    630     }
    631 
    632     public void setIsExpanded(boolean isExpanded) {
    633         this.mIsExpanded = isExpanded;
    634     }
    635 
    636     class StackScrollAlgorithmState {
    637 
    638         /**
    639          * The scroll position of the algorithm
    640          */
    641         public int scrollY;
    642 
    643         /**
    644          * The quantity of items which are in the bottom stack.
    645          */
    646         public float itemsInBottomStack;
    647 
    648         /**
    649          * how far in is the element currently transitioning into the bottom stack
    650          */
    651         public float partialInBottom;
    652 
    653         /**
    654          * The children from the host view which are not gone.
    655          */
    656         public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
    657 
    658         /**
    659          * The children from the host that need an increased padding after them. A value of 0 means
    660          * no increased padding, a value of 1 means full padding.
    661          */
    662         public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>();
    663     }
    664 
    665 }
    666