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