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
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            return false;
178        }
179        ExpandableNotificationRow row = (ExpandableNotificationRow) v;
180        if (row.areGutsExposed()) {
181            return false;
182        }
183        return row.canViewBeDismissed();
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