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