StackScrollAlgorithm.java revision 0cd8685fc0efe8c5750f68d6ba5c8cf1f9d46458
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.notification.FakeShadowView;
28import com.android.systemui.statusbar.notification.NotificationUtils;
29
30import java.util.ArrayList;
31import java.util.HashMap;
32import 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 */
39public 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 && ambientState.isShadeExpanded()
158                    && !isHeadsUp) {
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        int childCount = algorithmState.visibleChildren.size();
329        for (int i = 0; i < childCount; i++) {
330            currentYPosition = updateChild(i, resultState, algorithmState, ambientState,
331                    currentYPosition, bottomStackStart);
332        }
333    }
334
335    protected float updateChild(int i, StackScrollState resultState,
336            StackScrollAlgorithmState algorithmState, AmbientState ambientState,
337            float currentYPosition, float bottomStackStart) {
338        ExpandableView child = algorithmState.visibleChildren.get(i);
339        StackViewState childViewState = resultState.getViewStateForView(child);
340        childViewState.location = StackViewState.LOCATION_UNKNOWN;
341        int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
342        int childHeight = getMaxAllowedChildHeight(child);
343        int collapsedHeight = child.getCollapsedHeight();
344        childViewState.yTranslation = currentYPosition;
345        if (i == 0) {
346            updateFirstChildHeight(child, childViewState, childHeight, ambientState);
347        }
348
349        // The y position after this element
350        float nextYPosition = currentYPosition + childHeight +
351                paddingAfterChild;
352        if (nextYPosition >= bottomStackStart) {
353            // Case 1:
354            // We are in the bottom stack.
355            if (currentYPosition >= bottomStackStart) {
356                // According to the regular scroll view we are fully translated out of the
357                // bottom of the screen so we are fully in the bottom stack
358                updateStateForChildFullyInBottomStack(algorithmState,
359                        bottomStackStart, childViewState, collapsedHeight, ambientState, child);
360            } else {
361                // According to the regular scroll view we are currently translating out of /
362                // into the bottom of the screen
363                updateStateForChildTransitioningInBottom(algorithmState,
364                        bottomStackStart, child, currentYPosition,
365                        childViewState, childHeight);
366            }
367        } else {
368            // Case 2:
369            // We are in the regular scroll area.
370            childViewState.location = StackViewState.LOCATION_MAIN_AREA;
371            clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
372                    ambientState);
373        }
374
375        if (i == 0 && ambientState.getScrollY() <= 0) {
376            // The first card can get into the bottom stack if it's the only one
377            // on the lockscreen which pushes it up. Let's make sure that doesn't happen and
378            // it stays at the top
379            childViewState.yTranslation = Math.max(0, childViewState.yTranslation);
380        }
381        currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
382        if (currentYPosition <= 0) {
383            childViewState.location = StackViewState.LOCATION_HIDDEN_TOP;
384        }
385        if (childViewState.location == StackViewState.LOCATION_UNKNOWN) {
386            Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
387        }
388
389        childViewState.yTranslation += ambientState.getTopPadding()
390                + ambientState.getStackTranslation();
391        return currentYPosition;
392    }
393
394    protected int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
395            ExpandableView child) {
396        Float paddingValue = algorithmState.increasedPaddingMap.get(child);
397        return paddingValue == null
398                ? mPaddingBetweenElements
399                : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
400                        mIncreasedPaddingBetweenElements,
401                        paddingValue);
402    }
403
404    private void updateHeadsUpStates(StackScrollState resultState,
405            StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
406        int childCount = algorithmState.visibleChildren.size();
407        ExpandableNotificationRow topHeadsUpEntry = null;
408        for (int i = 0; i < childCount; i++) {
409            View child = algorithmState.visibleChildren.get(i);
410            if (!(child instanceof ExpandableNotificationRow)) {
411                break;
412            }
413            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
414            if (!row.isHeadsUp()) {
415                break;
416            }
417            StackViewState childState = resultState.getViewStateForView(row);
418            if (topHeadsUpEntry == null) {
419                topHeadsUpEntry = row;
420                childState.location = StackViewState.LOCATION_FIRST_HUN;
421            }
422            boolean isTopEntry = topHeadsUpEntry == row;
423            float unmodifiedEndLocation = childState.yTranslation + childState.height;
424            if (mIsExpanded) {
425                // Ensure that the heads up is always visible even when scrolled off
426                clampHunToTop(ambientState, row, childState);
427                clampHunToMaxTranslation(ambientState, row, childState);
428            }
429            if (row.isPinned()) {
430                childState.yTranslation = Math.max(childState.yTranslation, 0);
431                childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
432                StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
433                if (!isTopEntry && (!mIsExpanded
434                        || unmodifiedEndLocation < topState.yTranslation + topState.height)) {
435                    // Ensure that a headsUp doesn't vertically extend further than the heads-up at
436                    // the top most z-position
437                    childState.height = row.getIntrinsicHeight();
438                    childState.yTranslation = topState.yTranslation + topState.height
439                            - childState.height;
440                }
441            }
442        }
443    }
444
445    private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
446            StackViewState childState) {
447        float newTranslation = Math.max(ambientState.getTopPadding()
448                + ambientState.getStackTranslation(), childState.yTranslation);
449        childState.height = (int) Math.max(childState.height - (newTranslation
450                - childState.yTranslation), row.getCollapsedHeight());
451        childState.yTranslation = newTranslation;
452    }
453
454    private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
455            StackViewState childState) {
456        float newTranslation;
457        float bottomPosition = ambientState.getMaxHeadsUpTranslation() - row.getCollapsedHeight();
458        newTranslation = Math.min(childState.yTranslation, bottomPosition);
459        childState.height = (int) Math.max(childState.height
460                - (childState.yTranslation - newTranslation), row.getCollapsedHeight());
461        childState.yTranslation = newTranslation;
462    }
463
464    /**
465     * Clamp the yTranslation of the child down such that its end is at most on the beginning of
466     * the bottom stack.
467     *
468     * @param childViewState the view state of the child
469     * @param childHeight the height of this child
470     * @param minHeight the minumum Height of the View
471     */
472    private void clampPositionToBottomStackStart(StackViewState childViewState,
473            int childHeight, int minHeight, AmbientState ambientState) {
474
475        int bottomStackStart = ambientState.getInnerHeight()
476                - mBottomStackPeekSize - mBottomStackSlowDownLength;
477        int childStart = bottomStackStart - childHeight;
478        if (childStart < childViewState.yTranslation) {
479            float newHeight = bottomStackStart - childViewState.yTranslation;
480            if (newHeight < minHeight) {
481                newHeight = minHeight;
482                childViewState.yTranslation = bottomStackStart - minHeight;
483            }
484            childViewState.height = (int) newHeight;
485        }
486    }
487
488    protected int getMaxAllowedChildHeight(View child) {
489        if (child instanceof ExpandableView) {
490            ExpandableView expandableView = (ExpandableView) child;
491            return expandableView.getIntrinsicHeight();
492        }
493        return child == null? mCollapsedSize : child.getHeight();
494    }
495
496    private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
497            float transitioningPositionStart, ExpandableView child, float currentYPosition,
498            StackViewState childViewState, int childHeight) {
499
500        // This is the transitioning element on top of bottom stack, calculate how far we are in.
501        algorithmState.partialInBottom = 1.0f - (
502                (transitioningPositionStart - currentYPosition) / (childHeight +
503                        getPaddingAfterChild(algorithmState, child)));
504
505        // the offset starting at the transitionPosition of the bottom stack
506        float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
507        algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
508        int newHeight = childHeight;
509        if (childHeight > child.getCollapsedHeight()) {
510            newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset -
511                    getPaddingAfterChild(algorithmState, child) - currentYPosition, childHeight),
512                    child.getCollapsedHeight());
513            childViewState.height = newHeight;
514        }
515        childViewState.yTranslation = transitioningPositionStart + offset - newHeight
516                - getPaddingAfterChild(algorithmState, child);
517        childViewState.location = StackViewState.LOCATION_MAIN_AREA;
518    }
519
520    private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
521            float transitioningPositionStart, StackViewState childViewState,
522            int collapsedHeight, AmbientState ambientState, ExpandableView child) {
523        float currentYPosition;
524        algorithmState.itemsInBottomStack += 1.0f;
525        if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
526            // We are visually entering the bottom stack
527            currentYPosition = transitioningPositionStart
528                    + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack)
529                    - getPaddingAfterChild(algorithmState, child);
530            childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING;
531        } else {
532            // we are fully inside the stack
533            if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) {
534                childViewState.hidden = true;
535                childViewState.shadowAlpha = 0.0f;
536            } else if (algorithmState.itemsInBottomStack
537                    > MAX_ITEMS_IN_BOTTOM_STACK + 1) {
538                childViewState.shadowAlpha = 1.0f - algorithmState.partialInBottom;
539            }
540            childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN;
541            currentYPosition = ambientState.getInnerHeight();
542        }
543        childViewState.height = collapsedHeight;
544        childViewState.yTranslation = currentYPosition - collapsedHeight;
545    }
546
547
548    /**
549     * Update the height of the first child i.e clamp it to the bottom stack
550     *
551     * @param child the child to update
552     * @param childViewState the viewstate of the child
553     * @param childHeight the height of the child
554     * @param ambientState The ambient state of the algorithm
555     */
556    protected void updateFirstChildHeight(ExpandableView child, StackViewState childViewState,
557                                          int childHeight, AmbientState ambientState) {
558
559            // The starting position of the bottom stack peek
560            int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
561                    mBottomStackSlowDownLength + ambientState.getScrollY();
562            // Collapse and expand the first child while the shade is being expanded
563        childViewState.height = (int) Math.max(Math.min(bottomPeekStart, (float) childHeight),
564                    child.getCollapsedHeight());
565    }
566
567    /**
568     * Calculate the Z positions for all children based on the number of items in both stacks and
569     * save it in the resultState
570     *  @param resultState The result state to update the zTranslation values
571     * @param algorithmState The state in which the current pass of the algorithm is currently in
572     * @param ambientState The ambient state of the algorithm
573     */
574    private void updateZValuesForState(StackScrollState resultState,
575            StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
576        int childCount = algorithmState.visibleChildren.size();
577        float childrenOnTop = 0.0f;
578        for (int i = childCount - 1; i >= 0; i--) {
579            updateChildZValue(i, childCount, childrenOnTop,
580                    resultState, algorithmState, ambientState);
581        }
582    }
583
584    protected void updateChildZValue(int i, int childCount, float childrenOnTop,
585            StackScrollState resultState, StackScrollAlgorithmState algorithmState,
586            AmbientState ambientState) {
587        ExpandableView child = algorithmState.visibleChildren.get(i);
588        StackViewState childViewState = resultState.getViewStateForView(child);
589        if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
590            // We are in the bottom stack
591            float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
592            float zSubtraction;
593            if (numItemsAbove <= 1.0f) {
594                float factor = 0.2f;
595                // Lets fade in slower to the threshold to make the shadow fade in look nicer
596                if (numItemsAbove <= factor) {
597                    zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
598                            * numItemsAbove * (1.0f / factor);
599                } else {
600                    zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
601                            + (numItemsAbove - factor) * (1.0f / (1.0f - factor))
602                            * (mZDistanceBetweenElements
603                            - FakeShadowView.SHADOW_SIBLING_TRESHOLD);
604                }
605            } else {
606                zSubtraction = numItemsAbove * mZDistanceBetweenElements;
607            }
608            childViewState.zTranslation = mZBasicHeight - zSubtraction;
609        } else if (child.mustStayOnScreen()
610                && childViewState.yTranslation < ambientState.getTopPadding()
611                + ambientState.getStackTranslation()) {
612            if (childrenOnTop != 0.0f) {
613                childrenOnTop++;
614            } else {
615                float overlap = ambientState.getTopPadding()
616                        + ambientState.getStackTranslation() - childViewState.yTranslation;
617                childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
618            }
619            childViewState.zTranslation = mZBasicHeight
620                    + childrenOnTop * mZDistanceBetweenElements;
621        } else {
622            childViewState.zTranslation = mZBasicHeight;
623        }
624    }
625
626    private boolean isMaxSizeInitialized(ExpandableView child) {
627        if (child instanceof ExpandableNotificationRow) {
628            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
629            return row.isMaxExpandHeightInitialized();
630        }
631        return child == null || child.getWidth() != 0;
632    }
633
634    private View findFirstVisibleChild(ViewGroup container) {
635        int childCount = container.getChildCount();
636        for (int i = 0; i < childCount; i++) {
637            View child = container.getChildAt(i);
638            if (child.getVisibility() != View.GONE) {
639                return child;
640            }
641        }
642        return null;
643    }
644
645    public void setIsExpanded(boolean isExpanded) {
646        this.mIsExpanded = isExpanded;
647    }
648
649    protected class StackScrollAlgorithmState {
650
651        /**
652         * The scroll position of the algorithm
653         */
654        public int scrollY;
655
656        /**
657         * The quantity of items which are in the bottom stack.
658         */
659        public float itemsInBottomStack;
660
661        /**
662         * how far in is the element currently transitioning into the bottom stack
663         */
664        public float partialInBottom;
665
666        /**
667         * The children from the host view which are not gone.
668         */
669        public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
670
671        /**
672         * The children from the host that need an increased padding after them. A value of 0 means
673         * no increased padding, a value of 1 means full padding.
674         */
675        public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>();
676    }
677
678}
679