167b2260093774f5866f781aede52830440f4ed0eSelim Cinek/*
267b2260093774f5866f781aede52830440f4ed0eSelim Cinek * Copyright (C) 2014 The Android Open Source Project
367b2260093774f5866f781aede52830440f4ed0eSelim Cinek *
467b2260093774f5866f781aede52830440f4ed0eSelim Cinek * Licensed under the Apache License, Version 2.0 (the "License");
567b2260093774f5866f781aede52830440f4ed0eSelim Cinek * you may not use this file except in compliance with the License.
667b2260093774f5866f781aede52830440f4ed0eSelim Cinek * You may obtain a copy of the License at
767b2260093774f5866f781aede52830440f4ed0eSelim Cinek *
867b2260093774f5866f781aede52830440f4ed0eSelim Cinek *      http://www.apache.org/licenses/LICENSE-2.0
967b2260093774f5866f781aede52830440f4ed0eSelim Cinek *
1067b2260093774f5866f781aede52830440f4ed0eSelim Cinek * Unless required by applicable law or agreed to in writing, software
1167b2260093774f5866f781aede52830440f4ed0eSelim Cinek * distributed under the License is distributed on an "AS IS" BASIS,
1267b2260093774f5866f781aede52830440f4ed0eSelim Cinek * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1367b2260093774f5866f781aede52830440f4ed0eSelim Cinek * See the License for the specific language governing permissions and
1467b2260093774f5866f781aede52830440f4ed0eSelim Cinek * limitations under the License
1567b2260093774f5866f781aede52830440f4ed0eSelim Cinek */
1667b2260093774f5866f781aede52830440f4ed0eSelim Cinek
1767b2260093774f5866f781aede52830440f4ed0eSelim Cinekpackage com.android.systemui.statusbar.stack;
1867b2260093774f5866f781aede52830440f4ed0eSelim Cinek
1967b2260093774f5866f781aede52830440f4ed0eSelim Cinekimport android.content.Context;
20d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggiimport android.util.DisplayMetrics;
216e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studerimport android.util.Log;
2267b2260093774f5866f781aede52830440f4ed0eSelim Cinekimport android.view.View;
2367b2260093774f5866f781aede52830440f4ed0eSelim Cinekimport android.view.ViewGroup;
24343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
2567b2260093774f5866f781aede52830440f4ed0eSelim Cinekimport com.android.systemui.R;
261685e634fb0b14033bd436af8d7174436699ffecSelim Cinekimport com.android.systemui.statusbar.ExpandableNotificationRow;
27be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggiimport com.android.systemui.statusbar.ExpandableView;
2867b2260093774f5866f781aede52830440f4ed0eSelim Cinek
29d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggiimport java.util.ArrayList;
30d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi
3167b2260093774f5866f781aede52830440f4ed0eSelim Cinek/**
3267b2260093774f5866f781aede52830440f4ed0eSelim Cinek * The Algorithm of the {@link com.android.systemui.statusbar.stack
3367b2260093774f5866f781aede52830440f4ed0eSelim Cinek * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar
3467b2260093774f5866f781aede52830440f4ed0eSelim Cinek * .stack.StackScrollState}
3567b2260093774f5866f781aede52830440f4ed0eSelim Cinek */
3667b2260093774f5866f781aede52830440f4ed0eSelim Cinekpublic class StackScrollAlgorithm {
3767b2260093774f5866f781aede52830440f4ed0eSelim Cinek
386e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer    private static final String LOG_TAG = "StackScrollAlgorithm";
396e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer
4067b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
4167b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private static final int MAX_ITEMS_IN_TOP_STACK = 3;
4267b2260093774f5866f781aede52830440f4ed0eSelim Cinek
43362dd6d632f0eb63de4edf4a6eec281342391d9fJorim Jaggi    public static final float DIMMED_SCALE = 0.95f;
44d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
4567b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private int mPaddingBetweenElements;
4667b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private int mCollapsedSize;
4767b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private int mTopStackPeekSize;
4867b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private int mBottomStackPeekSize;
4967b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private int mZDistanceBetweenElements;
5067b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private int mZBasicHeight;
51708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    private int mRoundedRectCornerRadius;
5267b2260093774f5866f781aede52830440f4ed0eSelim Cinek
5367b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private StackIndentationFunctor mTopStackIndentationFunctor;
5467b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private StackIndentationFunctor mBottomStackIndentationFunctor;
5567b2260093774f5866f781aede52830440f4ed0eSelim Cinek
56343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    private int mLayoutHeight;
578c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi
588c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi    /** mLayoutHeight - mTopPadding */
598c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi    private int mInnerHeight;
608c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi    private int mTopPadding;
6167b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
621685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    private boolean mIsExpansionChanging;
631685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    private int mFirstChildMaxHeight;
641685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    private boolean mIsExpanded;
65be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi    private ExpandableView mFirstChildWhileExpanding;
661685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    private boolean mExpandedOnStart;
67343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    private int mTopStackTotalSize;
6834c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    private int mPaddingBetweenElementsDimmed;
6934c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    private int mPaddingBetweenElementsNormal;
7034c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    private int mBottomStackSlowDownLength;
71ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek    private int mTopStackSlowDownLength;
72d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek    private int mCollapseSecondCardPadding;
733afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek    private boolean mIsSmallScreen;
743afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek    private int mMaxNotificationHeight;
75d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggi    private boolean mScaleDimmed;
7667b2260093774f5866f781aede52830440f4ed0eSelim Cinek
7767b2260093774f5866f781aede52830440f4ed0eSelim Cinek    public StackScrollAlgorithm(Context context) {
7867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        initConstants(context);
7934c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        updatePadding(false);
8034c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    }
8134c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek
8234c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    private void updatePadding(boolean dimmed) {
83d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggi        mPaddingBetweenElements = dimmed && mScaleDimmed
8434c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                ? mPaddingBetweenElementsDimmed
8534c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                : mPaddingBetweenElementsNormal;
86ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek        mTopStackTotalSize = mTopStackSlowDownLength + mPaddingBetweenElements
87ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                + mTopStackPeekSize;
8834c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
8934c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                MAX_ITEMS_IN_TOP_STACK,
9034c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                mTopStackPeekSize,
91b96924ddbf5283954f6f0f3af599844be1da1384Selim Cinek                mTopStackTotalSize - mTopStackPeekSize,
9234c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                0.5f);
9334c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
9434c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                MAX_ITEMS_IN_BOTTOM_STACK,
9534c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                mBottomStackPeekSize,
9634c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                getBottomStackSlowDownLength(),
9734c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                0.5f);
9834c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    }
9934c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek
10034c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    public int getBottomStackSlowDownLength() {
10134c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        return mBottomStackSlowDownLength + mPaddingBetweenElements;
10267b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
10367b2260093774f5866f781aede52830440f4ed0eSelim Cinek
10467b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private void initConstants(Context context) {
10534c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        mPaddingBetweenElementsDimmed = context.getResources()
10634c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
10734c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        mPaddingBetweenElementsNormal = context.getResources()
108fe40f7d13bfc1faa35c9a131ce4be5104cb8f6b9Jorim Jaggi                .getDimensionPixelSize(R.dimen.notification_padding);
10967b2260093774f5866f781aede52830440f4ed0eSelim Cinek        mCollapsedSize = context.getResources()
110fe40f7d13bfc1faa35c9a131ce4be5104cb8f6b9Jorim Jaggi                .getDimensionPixelSize(R.dimen.notification_min_height);
1113afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek        mMaxNotificationHeight = context.getResources()
1123afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                .getDimensionPixelSize(R.dimen.notification_max_height);
11367b2260093774f5866f781aede52830440f4ed0eSelim Cinek        mTopStackPeekSize = context.getResources()
11467b2260093774f5866f781aede52830440f4ed0eSelim Cinek                .getDimensionPixelSize(R.dimen.top_stack_peek_amount);
11567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        mBottomStackPeekSize = context.getResources()
11667b2260093774f5866f781aede52830440f4ed0eSelim Cinek                .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
11767b2260093774f5866f781aede52830440f4ed0eSelim Cinek        mZDistanceBetweenElements = context.getResources()
11867b2260093774f5866f781aede52830440f4ed0eSelim Cinek                .getDimensionPixelSize(R.dimen.z_distance_between_notifications);
11967b2260093774f5866f781aede52830440f4ed0eSelim Cinek        mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
12034c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        mBottomStackSlowDownLength = context.getResources()
12134c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek                .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length);
122ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek        mTopStackSlowDownLength = context.getResources()
123ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                .getDimensionPixelSize(R.dimen.top_stack_slow_down_length);
124708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        mRoundedRectCornerRadius = context.getResources().getDimensionPixelSize(
125697178b1244533adb0ffb3325c0a27a1fde6eacaSelim Cinek                R.dimen.notification_material_rounded_rect_radius);
126d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek        mCollapseSecondCardPadding = context.getResources().getDimensionPixelSize(
127d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek                R.dimen.notification_collapse_second_card_padding);
128d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggi        mScaleDimmed = context.getResources().getDisplayMetrics().densityDpi
129d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggi                >= DisplayMetrics.DENSITY_XXHIGH;
13067b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
13167b2260093774f5866f781aede52830440f4ed0eSelim Cinek
132d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggi    public boolean shouldScaleDimmed() {
133d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggi        return mScaleDimmed;
134d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggi    }
13567b2260093774f5866f781aede52830440f4ed0eSelim Cinek
136d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
13767b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // The state of the local variables are saved in an algorithmState to easily subdivide it
13867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // into multiple phases.
13967b2260093774f5866f781aede52830440f4ed0eSelim Cinek        StackScrollAlgorithmState algorithmState = mTempAlgorithmState;
14067b2260093774f5866f781aede52830440f4ed0eSelim Cinek
14167b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // First we reset the view states to their default values.
14267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        resultState.resetViewStates();
14367b2260093774f5866f781aede52830440f4ed0eSelim Cinek
144343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        algorithmState.itemsInTopStack = 0.0f;
14567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        algorithmState.partialInTop = 0.0f;
14667b2260093774f5866f781aede52830440f4ed0eSelim Cinek        algorithmState.lastTopStackIndex = 0;
147343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        algorithmState.scrolledPixelsTop = 0;
14867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        algorithmState.itemsInBottomStack = 0.0f;
149343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        algorithmState.partialInBottom = 0.0f;
1508d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek        float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
1511408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek
1521408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        int scrollY = ambientState.getScrollY();
1531408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek
1541408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        // Due to the overScroller, the stackscroller can have negative scroll state. This is
1551408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        // already accounted for by the top padding and doesn't need an additional adaption
1561408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        scrollY = Math.max(0, scrollY);
1571408eb5a58d669933c701e347fd3498ceab70f3cSelim Cinek        algorithmState.scrollY = (int) (scrollY + mCollapsedSize + bottomOverScroll);
158343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
159d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        updateVisibleChildren(resultState, algorithmState);
16067b2260093774f5866f781aede52830440f4ed0eSelim Cinek
16167b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // Phase 1:
16267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState);
16367b2260093774f5866f781aede52830440f4ed0eSelim Cinek
16467b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // Phase 2:
16567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        updatePositionsForState(resultState, algorithmState);
16667b2260093774f5866f781aede52830440f4ed0eSelim Cinek
16767b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // Phase 3:
16867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        updateZValuesForState(resultState, algorithmState);
169eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
170d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        handleDraggedViews(ambientState, resultState, algorithmState);
171ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi        updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
172708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        updateClipping(resultState, algorithmState);
1733d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
1743d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek    }
1753d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek
1763d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek    private void updateSpeedBumpState(StackScrollState resultState,
1773d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek            StackScrollAlgorithmState algorithmState, int speedBumpIndex) {
1783d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        int childCount = algorithmState.visibleChildren.size();
1793d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        for (int i = 0; i < childCount; i++) {
1803d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek            View child = algorithmState.visibleChildren.get(i);
1813d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
1823107cfacd34ded2508ab03c896e1ce894de0c795Selim Cinek
1833107cfacd34ded2508ab03c896e1ce894de0c795Selim Cinek            // The speed bump can also be gone, so equality needs to be taken when comparing
1843107cfacd34ded2508ab03c896e1ce894de0c795Selim Cinek            // indices.
1853107cfacd34ded2508ab03c896e1ce894de0c795Selim Cinek            childViewState.belowSpeedBump = speedBumpIndex != -1 && i >= speedBumpIndex;
1863d2b94bf8e32640e57573ebb17911b1db9440231Selim Cinek        }
187f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek    }
188f54090e9bb23e9ed1b4d9e500d856f80d2fbe775Selim Cinek
189708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    private void updateClipping(StackScrollState resultState,
190708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            StackScrollAlgorithmState algorithmState) {
191708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        float previousNotificationEnd = 0;
192708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        float previousNotificationStart = 0;
193708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        boolean previousNotificationIsSwiped = false;
194708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        int childCount = algorithmState.visibleChildren.size();
195708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        for (int i = 0; i < childCount; i++) {
196708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            ExpandableView child = algorithmState.visibleChildren.get(i);
197708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            StackScrollState.ViewState state = resultState.getViewStateForView(child);
1982e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi            float newYTranslation = state.yTranslation + state.height * (1f - state.scale) / 2f;
1992e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi            float newHeight = state.height * state.scale;
200708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            // apply clipping and shadow
201708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            float newNotificationEnd = newYTranslation + newHeight;
202708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
203c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek            float clipHeight;
204c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek            if (previousNotificationIsSwiped) {
205c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                // When the previous notification is swiped, we don't clip the content to the
206c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                // bottom of it.
207c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                clipHeight = newHeight;
208c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek            } else {
209c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                clipHeight = newNotificationEnd - previousNotificationEnd;
210c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                clipHeight = Math.max(0.0f, clipHeight);
211c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                if (clipHeight != 0.0f) {
212c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek
213c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                    // In the unlocked shade we have to clip a little bit higher because of the rounded
214c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                    // corners of the notifications, but only if we are not fully overlapped by
215c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                    // the top card.
216c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                    float clippingCorrection = state.dimmed
217c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                            ? 0
218c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                            : mRoundedRectCornerRadius * state.scale;
219c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                    clipHeight += clippingCorrection;
220c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                }
221c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek            }
222708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
223708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            updateChildClippingAndBackground(state, newHeight, clipHeight,
2242e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi                    newHeight - (previousNotificationStart - newYTranslation));
225708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
226708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            if (!child.isTransparent()) {
227708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // Only update the previous values if we are not transparent,
228708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                // otherwise we would clip to a transparent view.
2292e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi                previousNotificationStart = newYTranslation + state.clipTopAmount * state.scale;
230708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                previousNotificationEnd = newNotificationEnd;
231708a6c120da6750d281195ef15a240a5627efed4Selim Cinek                previousNotificationIsSwiped = child.getTranslationX() != 0;
232708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            }
233708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
234708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    }
235708a6c120da6750d281195ef15a240a5627efed4Selim Cinek
236708a6c120da6750d281195ef15a240a5627efed4Selim Cinek    /**
237708a6c120da6750d281195ef15a240a5627efed4Selim Cinek     * Updates the shadow outline and the clipping for a view.
238708a6c120da6750d281195ef15a240a5627efed4Selim Cinek     *
239708a6c120da6750d281195ef15a240a5627efed4Selim Cinek     * @param state the viewState to update
240708a6c120da6750d281195ef15a240a5627efed4Selim Cinek     * @param realHeight the currently applied height of the view
241708a6c120da6750d281195ef15a240a5627efed4Selim Cinek     * @param clipHeight the desired clip height, the rest of the view will be clipped from the top
242708a6c120da6750d281195ef15a240a5627efed4Selim Cinek     * @param backgroundHeight the desired background height. The shadows of the view will be
243708a6c120da6750d281195ef15a240a5627efed4Selim Cinek     *                         based on this height and the content will be clipped from the top
244708a6c120da6750d281195ef15a240a5627efed4Selim Cinek     */
2452e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi    private void updateChildClippingAndBackground(StackScrollState.ViewState state,
2462e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi            float realHeight, float clipHeight, float backgroundHeight) {
247708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (realHeight > clipHeight) {
2482e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi            // Rather overlap than create a hole.
2492e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi            state.topOverLap = (int) Math.floor((realHeight - clipHeight) / state.scale);
250708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        } else {
251708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            state.topOverLap = 0;
252708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
253708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        if (realHeight > backgroundHeight) {
2542e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi            // Rather overlap than create a hole.
2552e34ec3cbbe595b646bd7f319fb369a37191847fJorim Jaggi            state.clipTopAmount = (int) Math.floor((realHeight - backgroundHeight) / state.scale);
256708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        } else {
257708a6c120da6750d281195ef15a240a5627efed4Selim Cinek            state.clipTopAmount = 0;
258708a6c120da6750d281195ef15a240a5627efed4Selim Cinek        }
259d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    }
260d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi
261d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    /**
262ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi     * Updates the dimmed, activated and hiding sensitive states of the children.
263d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi     */
264ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi    private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
265ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi            StackScrollState resultState, StackScrollAlgorithmState algorithmState) {
266d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        boolean dimmed = ambientState.isDimmed();
267bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock        boolean dark = ambientState.isDark();
268ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi        boolean hideSensitive = ambientState.isHideSensitive();
269d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        View activatedChild = ambientState.getActivatedChild();
270d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        int childCount = algorithmState.visibleChildren.size();
271d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        for (int i = 0; i < childCount; i++) {
272d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            View child = algorithmState.visibleChildren.get(i);
273d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
274d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            childViewState.dimmed = dimmed;
275bf370992508c55d1f2493923bdc1834a0710e4baJohn Spurlock            childViewState.dark = dark;
276ae44128776410abd11bd06ae700db9cc4606a773Jorim Jaggi            childViewState.hideSensitive = hideSensitive;
277b89de4ec490c2c47a7aebf2caa4c9cdd5ed1010bSelim Cinek            boolean isActivatedChild = activatedChild == child;
278d7c1fae12ef0b31c225ef130e6b06445b5af53a9Jorim Jaggi            childViewState.scale = !mScaleDimmed || !dimmed || isActivatedChild
279d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                    ? 1.0f
280d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                    : DIMMED_SCALE;
2814538cee46d3aa91c85a453049d2b2284b799c66aJorim Jaggi            if (dimmed && isActivatedChild) {
2824538cee46d3aa91c85a453049d2b2284b799c66aJorim Jaggi                childViewState.zTranslation += 2.0f * mZDistanceBetweenElements;
283d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi            }
284d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        }
285eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    }
286eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
287eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek    /**
288eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     * Handle the special state when views are being dragged
289eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek     */
290d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi    private void handleDraggedViews(AmbientState ambientState, StackScrollState resultState,
291eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            StackScrollAlgorithmState algorithmState) {
292d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        ArrayList<View> draggedViews = ambientState.getDraggedViews();
293d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi        for (View draggedView : draggedViews) {
294eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            int childIndex = algorithmState.visibleChildren.indexOf(draggedView);
295eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) {
296eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                View nextChild = algorithmState.visibleChildren.get(childIndex + 1);
297d552d9d8e964c102e6832610be46cf2c041e8829Jorim Jaggi                if (!draggedViews.contains(nextChild)) {
298eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    // only if the view is not dragged itself we modify its state to be fully
299eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    // visible
300eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    StackScrollState.ViewState viewState = resultState.getViewStateForView(
301eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                            nextChild);
302eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    // The child below the dragged one must be fully visible
303eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                    viewState.alpha = 1;
304eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                }
305eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek
306eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                // Lets set the alpha to the one it currently has, as its currently being dragged
307eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                StackScrollState.ViewState viewState = resultState.getViewStateForView(draggedView);
308eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                // The dragged child should keep the set alpha
309eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek                viewState.alpha = draggedView.getAlpha();
310eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek            }
311eb973565f3efc6417ca35363e4d6c642947775d8Selim Cinek        }
312343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    }
313343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
314343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    /**
315d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi     * Update the visible children on the state.
316d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi     */
317d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi    private void updateVisibleChildren(StackScrollState resultState,
318d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi            StackScrollAlgorithmState state) {
319d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        ViewGroup hostView = resultState.getHostView();
320d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        int childCount = hostView.getChildCount();
321d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        state.visibleChildren.clear();
322d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        state.visibleChildren.ensureCapacity(childCount);
323d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        for (int i = 0; i < childCount; i++) {
324be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            ExpandableView v = (ExpandableView) hostView.getChildAt(i);
325d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi            if (v.getVisibility() != View.GONE) {
3268efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                StackScrollState.ViewState viewState = resultState.getViewStateForView(v);
3278efa6dde2b4f2cdbf046b87b7366404c3cc46219Selim Cinek                viewState.notGoneIndex = state.visibleChildren.size();
328d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi                state.visibleChildren.add(v);
329d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi            }
330d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        }
331d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi    }
332d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi
333d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi    /**
33467b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * Determine the positions for the views. This is the main part of the algorithm.
33567b2260093774f5866f781aede52830440f4ed0eSelim Cinek     *
33667b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * @param resultState The result state to update if a change to the properties of a child occurs
33767b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * @param algorithmState The state in which the current pass of the algorithm is currently in
33867b2260093774f5866f781aede52830440f4ed0eSelim Cinek     */
33967b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private void updatePositionsForState(StackScrollState resultState,
34067b2260093774f5866f781aede52830440f4ed0eSelim Cinek            StackScrollAlgorithmState algorithmState) {
34167b2260093774f5866f781aede52830440f4ed0eSelim Cinek
3421685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        // The starting position of the bottom stack peek
3438c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi        float bottomPeekStart = mInnerHeight - mBottomStackPeekSize;
3441685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
34567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // The position where the bottom stack starts.
34634c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength;
34767b2260093774f5866f781aede52830440f4ed0eSelim Cinek
34867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // The y coordinate of the current child.
34967b2260093774f5866f781aede52830440f4ed0eSelim Cinek        float currentYPosition = 0.0f;
35067b2260093774f5866f781aede52830440f4ed0eSelim Cinek
35167b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // How far in is the element currently transitioning into the bottom stack.
35267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        float yPositionInScrollView = 0.0f;
35367b2260093774f5866f781aede52830440f4ed0eSelim Cinek
354d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        int childCount = algorithmState.visibleChildren.size();
35567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
35667b2260093774f5866f781aede52830440f4ed0eSelim Cinek        for (int i = 0; i < childCount; i++) {
357be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            ExpandableView child = algorithmState.visibleChildren.get(i);
35867b2260093774f5866f781aede52830440f4ed0eSelim Cinek            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
3596e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN;
360be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            int childHeight = getMaxAllowedChildHeight(child);
36167b2260093774f5866f781aede52830440f4ed0eSelim Cinek            float yPositionInScrollViewAfterElement = yPositionInScrollView
36267b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    + childHeight
36367b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    + mPaddingBetweenElements;
364343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            float scrollOffset = yPositionInScrollView - algorithmState.scrollY + mCollapsedSize;
365343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
366343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            if (i == algorithmState.lastTopStackIndex + 1) {
367343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // Normally the position of this child is the position in the regular scrollview,
368343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // but if the two stacks are very close to each other,
369343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // then have have to push it even more upwards to the position of the bottom
370343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // stack start.
371343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                currentYPosition = Math.min(scrollOffset, bottomStackStart);
372343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            }
373343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            childViewState.yTranslation = currentYPosition;
374343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
375343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            // The y position after this element
376343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            float nextYPosition = currentYPosition + childHeight +
377343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    mPaddingBetweenElements;
378343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
379343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            if (i <= algorithmState.lastTopStackIndex) {
38067b2260093774f5866f781aede52830440f4ed0eSelim Cinek                // Case 1:
38167b2260093774f5866f781aede52830440f4ed0eSelim Cinek                // We are in the top Stack
382343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                updateStateForTopStackChild(algorithmState,
383343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                        numberOfElementsCompletelyIn, i, childHeight, childViewState, scrollOffset);
3843afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                clampPositionToTopStackEnd(childViewState, childHeight);
3853afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek
386343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // check if we are overlapping with the bottom stack
387343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                if (childViewState.yTranslation + childHeight + mPaddingBetweenElements
3884581cf820637301dffa7dab0d59d92929b19e9c1Selim Cinek                        >= bottomStackStart && !mIsExpansionChanging && i != 0 && mIsSmallScreen) {
3893afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                    // we just collapse this element slightly
3903afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                    int newSize = (int) Math.max(bottomStackStart - mPaddingBetweenElements -
3913afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                            childViewState.yTranslation, mCollapsedSize);
3923afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                    childViewState.height = newSize;
3934581cf820637301dffa7dab0d59d92929b19e9c1Selim Cinek                    updateStateForChildTransitioningInBottom(algorithmState, bottomStackStart,
3944581cf820637301dffa7dab0d59d92929b19e9c1Selim Cinek                            bottomPeekStart, childViewState.yTranslation, childViewState,
3954581cf820637301dffa7dab0d59d92929b19e9c1Selim Cinek                            childHeight);
396343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                }
3973afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                clampPositionToBottomStackStart(childViewState, childViewState.height);
398343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            } else if (nextYPosition >= bottomStackStart) {
39967b2260093774f5866f781aede52830440f4ed0eSelim Cinek                // Case 2:
400343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // We are in the bottom stack.
401343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                if (currentYPosition >= bottomStackStart) {
40267b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    // According to the regular scroll view we are fully translated out of the
40367b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    // bottom of the screen so we are fully in the bottom stack
404343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    updateStateForChildFullyInBottomStack(algorithmState,
405343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                            bottomStackStart, childViewState, childHeight);
40667b2260093774f5866f781aede52830440f4ed0eSelim Cinek                } else {
40767b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    // According to the regular scroll view we are currently translating out of /
40867b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    // into the bottom of the screen
409343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    updateStateForChildTransitioningInBottom(algorithmState,
410343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                            bottomStackStart, bottomPeekStart, currentYPosition,
411343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                            childViewState, childHeight);
41267b2260093774f5866f781aede52830440f4ed0eSelim Cinek                }
4136e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            } else {
414343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // Case 3:
415343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // We are in the regular scroll area.
4166e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer                childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
417343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                clampYTranslation(childViewState, childHeight);
4186e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            }
419343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
4206e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            // The first card is always rendered.
4216e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            if (i == 0) {
4226e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer                childViewState.alpha = 1.0f;
4238d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                childViewState.yTranslation = Math.max(mCollapsedSize - algorithmState.scrollY, 0);
424d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek                if (childViewState.yTranslation + childViewState.height
425d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek                        > bottomPeekStart - mCollapseSecondCardPadding) {
4264fe3e475eaeb88ec8f0bb580ee765ceefe13bcbcSelim Cinek                    childViewState.height = (int) Math.max(
427d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek                            bottomPeekStart - mCollapseSecondCardPadding
428d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek                                    - childViewState.yTranslation, mCollapsedSize);
4294fe3e475eaeb88ec8f0bb580ee765ceefe13bcbcSelim Cinek                }
4306e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer                childViewState.location = StackScrollState.ViewState.LOCATION_FIRST_CARD;
4316e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            }
4326e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            if (childViewState.location == StackScrollState.ViewState.LOCATION_UNKNOWN) {
4336e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer                Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
43467b2260093774f5866f781aede52830440f4ed0eSelim Cinek            }
435343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
43667b2260093774f5866f781aede52830440f4ed0eSelim Cinek            yPositionInScrollView = yPositionInScrollViewAfterElement;
4378c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi
4388c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi            childViewState.yTranslation += mTopPadding;
43967b2260093774f5866f781aede52830440f4ed0eSelim Cinek        }
44067b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
44167b2260093774f5866f781aede52830440f4ed0eSelim Cinek
4421685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    /**
443343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * Clamp the yTranslation both up and down to valid positions.
4441685e634fb0b14033bd436af8d7174436699ffecSelim Cinek     *
4451685e634fb0b14033bd436af8d7174436699ffecSelim Cinek     * @param childViewState the view state of the child
446343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * @param childHeight the height of this child
4471685e634fb0b14033bd436af8d7174436699ffecSelim Cinek     */
448343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    private void clampYTranslation(StackScrollState.ViewState childViewState, int childHeight) {
449343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        clampPositionToBottomStackStart(childViewState, childHeight);
450343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        clampPositionToTopStackEnd(childViewState, childHeight);
451343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    }
452343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
453343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    /**
454343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * Clamp the yTranslation of the child down such that its end is at most on the beginning of
455343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * the bottom stack.
456343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     *
457343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * @param childViewState the view state of the child
458343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * @param childHeight the height of this child
459343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     */
460343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    private void clampPositionToBottomStackStart(StackScrollState.ViewState childViewState,
461343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            int childHeight) {
462343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        childViewState.yTranslation = Math.min(childViewState.yTranslation,
463d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek                mInnerHeight - mBottomStackPeekSize - mCollapseSecondCardPadding - childHeight);
464343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    }
465343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
466343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    /**
467343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * Clamp the yTranslation of the child up such that its end is at lest on the end of the top
468be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi     * stack.get
469343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     *
470343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * @param childViewState the view state of the child
471343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     * @param childHeight the height of this child
472343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek     */
473343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    private void clampPositionToTopStackEnd(StackScrollState.ViewState childViewState,
474343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            int childHeight) {
475343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        childViewState.yTranslation = Math.max(childViewState.yTranslation,
476343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                mCollapsedSize - childHeight);
4771685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    }
4781685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
4791685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    private int getMaxAllowedChildHeight(View child) {
4801685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        if (child instanceof ExpandableNotificationRow) {
4811685e634fb0b14033bd436af8d7174436699ffecSelim Cinek            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4829cbadd3c08a7d7dd3412743dd04aecb16c5a1595Jorim Jaggi            return row.getIntrinsicHeight();
483be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        } else if (child instanceof ExpandableView) {
484be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            ExpandableView expandableView = (ExpandableView) child;
485be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            return expandableView.getActualHeight();
4861685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        }
487be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        return child == null? mCollapsedSize : child.getHeight();
4881685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    }
4891685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
490343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
491343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
492343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            StackScrollState.ViewState childViewState, int childHeight) {
493343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
494343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        // This is the transitioning element on top of bottom stack, calculate how far we are in.
49567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        algorithmState.partialInBottom = 1.0f - (
496343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                (transitioningPositionStart - currentYPosition) / (childHeight +
497343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                        mPaddingBetweenElements));
498343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
499343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        // the offset starting at the transitionPosition of the bottom stack
500343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
50167b2260093774f5866f781aede52830440f4ed0eSelim Cinek        algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
5023afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek        int newHeight = childHeight;
5033afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek        if (childHeight > mCollapsedSize && mIsSmallScreen) {
5043afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek            newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset -
5053afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                    mPaddingBetweenElements - currentYPosition, childHeight), mCollapsedSize);
5063afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek            childViewState.height = newHeight;
5073afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek        }
5083afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek        childViewState.yTranslation = transitioningPositionStart + offset - newHeight
509fe40f7d13bfc1faa35c9a131ce4be5104cb8f6b9Jorim Jaggi                - mPaddingBetweenElements;
5103afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek
511343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        // We want at least to be at the end of the top stack when collapsing
5123afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek        clampPositionToTopStackEnd(childViewState, newHeight);
513343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        childViewState.location = StackScrollState.ViewState.LOCATION_MAIN_AREA;
51467b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
51567b2260093774f5866f781aede52830440f4ed0eSelim Cinek
516343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
51767b2260093774f5866f781aede52830440f4ed0eSelim Cinek            float transitioningPositionStart, StackScrollState.ViewState childViewState,
51867b2260093774f5866f781aede52830440f4ed0eSelim Cinek            int childHeight) {
51967b2260093774f5866f781aede52830440f4ed0eSelim Cinek
520343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        float currentYPosition;
52167b2260093774f5866f781aede52830440f4ed0eSelim Cinek        algorithmState.itemsInBottomStack += 1.0f;
52267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
52367b2260093774f5866f781aede52830440f4ed0eSelim Cinek            // We are visually entering the bottom stack
524343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            currentYPosition = transitioningPositionStart
525fe40f7d13bfc1faa35c9a131ce4be5104cb8f6b9Jorim Jaggi                    + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack)
526fe40f7d13bfc1faa35c9a131ce4be5104cb8f6b9Jorim Jaggi                    - mPaddingBetweenElements;
5276e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_PEEKING;
52867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        } else {
52967b2260093774f5866f781aede52830440f4ed0eSelim Cinek            // we are fully inside the stack
53067b2260093774f5866f781aede52830440f4ed0eSelim Cinek            if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) {
53167b2260093774f5866f781aede52830440f4ed0eSelim Cinek                childViewState.alpha = 0.0f;
53267b2260093774f5866f781aede52830440f4ed0eSelim Cinek            } else if (algorithmState.itemsInBottomStack
53367b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    > MAX_ITEMS_IN_BOTTOM_STACK + 1) {
53467b2260093774f5866f781aede52830440f4ed0eSelim Cinek                childViewState.alpha = 1.0f - algorithmState.partialInBottom;
53567b2260093774f5866f781aede52830440f4ed0eSelim Cinek            }
5366e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            childViewState.location = StackScrollState.ViewState.LOCATION_BOTTOM_STACK_HIDDEN;
5378c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi            currentYPosition = mInnerHeight;
53867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        }
539343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        childViewState.yTranslation = currentYPosition - childHeight;
540343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        clampPositionToTopStackEnd(childViewState, childHeight);
54167b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
54267b2260093774f5866f781aede52830440f4ed0eSelim Cinek
543343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
544343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            int numberOfElementsCompletelyIn, int i, int childHeight,
545343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            StackScrollState.ViewState childViewState, float scrollOffset) {
54667b2260093774f5866f781aede52830440f4ed0eSelim Cinek
54767b2260093774f5866f781aede52830440f4ed0eSelim Cinek
54867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // First we calculate the index relative to the current stack window of size at most
54967b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // {@link #MAX_ITEMS_IN_TOP_STACK}
550343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        int paddedIndex = i - 1
55167b2260093774f5866f781aede52830440f4ed0eSelim Cinek                - Math.max(numberOfElementsCompletelyIn - MAX_ITEMS_IN_TOP_STACK, 0);
55267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        if (paddedIndex >= 0) {
553343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
55467b2260093774f5866f781aede52830440f4ed0eSelim Cinek            // We are currently visually entering the top stack
555ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek            float distanceToStack = (childHeight + mPaddingBetweenElements)
556ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                    - algorithmState.scrolledPixelsTop;
557ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek            if (i == algorithmState.lastTopStackIndex
558ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                    && distanceToStack > (mTopStackTotalSize + mPaddingBetweenElements)) {
559343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
560343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // Child is currently translating into stack but not yet inside slow down zone.
561343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // Handle it like the regular scrollview.
562343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                childViewState.yTranslation = scrollOffset;
5636e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            } else {
564343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // Apply stacking logic.
565343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                float numItemsBefore;
566343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                if (i == algorithmState.lastTopStackIndex) {
567ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                    numItemsBefore = 1.0f
568ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                            - (distanceToStack / (mTopStackTotalSize + mPaddingBetweenElements));
569343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                } else {
570343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    numItemsBefore = algorithmState.itemsInTopStack - i;
571343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                }
572343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // The end position of the current child
573ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                float currentChildEndY = mCollapsedSize + mTopStackTotalSize
574ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                        - mTopStackIndentationFunctor.getValue(numItemsBefore);
575343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                childViewState.yTranslation = currentChildEndY - childHeight;
57667b2260093774f5866f781aede52830440f4ed0eSelim Cinek            }
577343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
57867b2260093774f5866f781aede52830440f4ed0eSelim Cinek        } else {
579343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            if (paddedIndex == -1) {
580343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                childViewState.alpha = 1.0f - algorithmState.partialInTop;
581343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            } else {
582343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                // We are hidden behind the top card and faded out, so we can hide ourselves.
583343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                childViewState.alpha = 0.0f;
584343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            }
585343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek            childViewState.yTranslation = mCollapsedSize - childHeight;
5866e3ecebcec1b82fd81f6d78b8deb5c4189b6026eChristoph Studer            childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_HIDDEN;
58767b2260093774f5866f781aede52830440f4ed0eSelim Cinek        }
588343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
589343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
59067b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
59167b2260093774f5866f781aede52830440f4ed0eSelim Cinek
59267b2260093774f5866f781aede52830440f4ed0eSelim Cinek    /**
59367b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * Find the number of items in the top stack and update the result state if needed.
59467b2260093774f5866f781aede52830440f4ed0eSelim Cinek     *
59567b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * @param resultState The result state to update if a height change of an child occurs
59667b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * @param algorithmState The state in which the current pass of the algorithm is currently in
59767b2260093774f5866f781aede52830440f4ed0eSelim Cinek     */
59867b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState,
59967b2260093774f5866f781aede52830440f4ed0eSelim Cinek            StackScrollAlgorithmState algorithmState) {
60067b2260093774f5866f781aede52830440f4ed0eSelim Cinek
60167b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // The y Position if the element would be in a regular scrollView
60267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        float yPositionInScrollView = 0.0f;
603d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        int childCount = algorithmState.visibleChildren.size();
60467b2260093774f5866f781aede52830440f4ed0eSelim Cinek
60567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        // find the number of elements in the top stack.
60667b2260093774f5866f781aede52830440f4ed0eSelim Cinek        for (int i = 0; i < childCount; i++) {
607be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            ExpandableView child = algorithmState.visibleChildren.get(i);
60867b2260093774f5866f781aede52830440f4ed0eSelim Cinek            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
609be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi            int childHeight = getMaxAllowedChildHeight(child);
61067b2260093774f5866f781aede52830440f4ed0eSelim Cinek            float yPositionInScrollViewAfterElement = yPositionInScrollView
61167b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    + childHeight
61267b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    + mPaddingBetweenElements;
61367b2260093774f5866f781aede52830440f4ed0eSelim Cinek            if (yPositionInScrollView < algorithmState.scrollY) {
6148d9ff9c2c66bc1d3b92eb6992d58599ff80ed6dcSelim Cinek                if (i == 0 && algorithmState.scrollY <= mCollapsedSize) {
615343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
616343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    // The starting position of the bottom stack peek
617d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek                    int bottomPeekStart = mInnerHeight - mBottomStackPeekSize -
618d83771ee46076d74fa7284a5a5867bc9b0ce20beSelim Cinek                            mCollapseSecondCardPadding;
619343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    // Collapse and expand the first child while the shade is being expanded
620343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
621343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                            ? mFirstChildMaxHeight
622343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                            : childHeight;
623343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
624343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                            mCollapsedSize);
625343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    algorithmState.itemsInTopStack = 1.0f;
626343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
627343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                } else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) {
62867b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    // According to the regular scroll view we are fully off screen
62967b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    algorithmState.itemsInTopStack += 1.0f;
630343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    if (i == 0) {
63167b2260093774f5866f781aede52830440f4ed0eSelim Cinek                        childViewState.height = mCollapsedSize;
63267b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    }
63367b2260093774f5866f781aede52830440f4ed0eSelim Cinek                } else {
63467b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    // According to the regular scroll view we are partially off screen
635343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
63667b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    // How much did we scroll into this child
637ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                    algorithmState.scrolledPixelsTop = algorithmState.scrollY
638ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                            - yPositionInScrollView;
639343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    algorithmState.partialInTop = (algorithmState.scrolledPixelsTop) / (childHeight
64067b2260093774f5866f781aede52830440f4ed0eSelim Cinek                            + mPaddingBetweenElements);
64167b2260093774f5866f781aede52830440f4ed0eSelim Cinek
64267b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    // Our element can be expanded, so this can get negative
64367b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    algorithmState.partialInTop = Math.max(0.0f, algorithmState.partialInTop);
64467b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    algorithmState.itemsInTopStack += algorithmState.partialInTop;
645ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek
646343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    if (i == 0) {
647ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                        // If it is expanded we have to collapse it to a new size
648ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                        float newSize = yPositionInScrollViewAfterElement
649ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                                - mPaddingBetweenElements
650ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                                - algorithmState.scrollY + mCollapsedSize;
651ad3e5afdd2dad5ae9144db1cdadd968ba6f306b1Selim Cinek                        newSize = Math.max(mCollapsedSize, newSize);
6524e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                        algorithmState.itemsInTopStack = 1.0f;
65367b2260093774f5866f781aede52830440f4ed0eSelim Cinek                        childViewState.height = (int) newSize;
65467b2260093774f5866f781aede52830440f4ed0eSelim Cinek                    }
655343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    algorithmState.lastTopStackIndex = i;
656343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                    break;
65767b2260093774f5866f781aede52830440f4ed0eSelim Cinek                }
65867b2260093774f5866f781aede52830440f4ed0eSelim Cinek            } else {
659343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek                algorithmState.lastTopStackIndex = i - 1;
66067b2260093774f5866f781aede52830440f4ed0eSelim Cinek                // We are already past the stack so we can end the loop
66167b2260093774f5866f781aede52830440f4ed0eSelim Cinek                break;
66267b2260093774f5866f781aede52830440f4ed0eSelim Cinek            }
66367b2260093774f5866f781aede52830440f4ed0eSelim Cinek            yPositionInScrollView = yPositionInScrollViewAfterElement;
66467b2260093774f5866f781aede52830440f4ed0eSelim Cinek        }
66567b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
66667b2260093774f5866f781aede52830440f4ed0eSelim Cinek
66767b2260093774f5866f781aede52830440f4ed0eSelim Cinek    /**
66867b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * Calculate the Z positions for all children based on the number of items in both stacks and
66967b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * save it in the resultState
67067b2260093774f5866f781aede52830440f4ed0eSelim Cinek     *
67167b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * @param resultState The result state to update the zTranslation values
67267b2260093774f5866f781aede52830440f4ed0eSelim Cinek     * @param algorithmState The state in which the current pass of the algorithm is currently in
67367b2260093774f5866f781aede52830440f4ed0eSelim Cinek     */
67467b2260093774f5866f781aede52830440f4ed0eSelim Cinek    private void updateZValuesForState(StackScrollState resultState,
67567b2260093774f5866f781aede52830440f4ed0eSelim Cinek            StackScrollAlgorithmState algorithmState) {
676d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        int childCount = algorithmState.visibleChildren.size();
67767b2260093774f5866f781aede52830440f4ed0eSelim Cinek        for (int i = 0; i < childCount; i++) {
678d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi            View child = algorithmState.visibleChildren.get(i);
67967b2260093774f5866f781aede52830440f4ed0eSelim Cinek            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
68067b2260093774f5866f781aede52830440f4ed0eSelim Cinek            if (i < algorithmState.itemsInTopStack) {
68167b2260093774f5866f781aede52830440f4ed0eSelim Cinek                float stackIndex = algorithmState.itemsInTopStack - i;
682c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek
683c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                // Ensure that the topmost item is a little bit higher than the rest when fully
684c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                // scrolled, to avoid drawing errors when swiping it out
685c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                float max = MAX_ITEMS_IN_TOP_STACK + (i == 0 ? 2.5f : 2);
686c5baa3eb0893cb764e7810f8c68e89b04653df86Selim Cinek                stackIndex = Math.min(stackIndex, max);
6874e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                if (i == 0 && algorithmState.itemsInTopStack < 2.0f) {
6884e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek
6894e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                    // We only have the top item and an additional item in the top stack,
6904e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                    // Interpolate the index from 0 to 2 while the second item is
6914e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                    // translating in.
6924e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                    stackIndex -= 1.0f;
6934e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                    if (algorithmState.scrollY > mCollapsedSize) {
6944e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek
6954e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                        // Since there is a shadow treshhold, we cant just interpolate from 0 to
6964e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                        // 2 but we interpolate from 0.1f to 2.0f when scrolled in. The jump in
6974e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                        // height will not be noticable since we have padding in between.
6984e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                        stackIndex = 0.1f + stackIndex * 1.9f;
6994e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                    }
7004e456bee15e56e7cc09b62591613f0a794f077e3Selim Cinek                }
70167b2260093774f5866f781aede52830440f4ed0eSelim Cinek                childViewState.zTranslation = mZBasicHeight
70267b2260093774f5866f781aede52830440f4ed0eSelim Cinek                        + stackIndex * mZDistanceBetweenElements;
70367b2260093774f5866f781aede52830440f4ed0eSelim Cinek            } else if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
70467b2260093774f5866f781aede52830440f4ed0eSelim Cinek                float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
70567b2260093774f5866f781aede52830440f4ed0eSelim Cinek                float translationZ = mZBasicHeight
70667b2260093774f5866f781aede52830440f4ed0eSelim Cinek                        - numItemsAbove * mZDistanceBetweenElements;
70767b2260093774f5866f781aede52830440f4ed0eSelim Cinek                childViewState.zTranslation = translationZ;
70867b2260093774f5866f781aede52830440f4ed0eSelim Cinek            } else {
70967b2260093774f5866f781aede52830440f4ed0eSelim Cinek                childViewState.zTranslation = mZBasicHeight;
71067b2260093774f5866f781aede52830440f4ed0eSelim Cinek            }
71167b2260093774f5866f781aede52830440f4ed0eSelim Cinek        }
71267b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
71367b2260093774f5866f781aede52830440f4ed0eSelim Cinek
714343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek    public void setLayoutHeight(int layoutHeight) {
71567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        this.mLayoutHeight = layoutHeight;
7168c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi        updateInnerHeight();
7178c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi    }
7188c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi
7198c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi    public void setTopPadding(int topPadding) {
7208c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi        mTopPadding = topPadding;
7218c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi        updateInnerHeight();
7228c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi    }
7238c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi
7248c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi    private void updateInnerHeight() {
7258c1a44b62f82c956cbe4aa0809cbdf255d0fae1fJorim Jaggi        mInnerHeight = mLayoutHeight - mTopPadding;
72667b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
72767b2260093774f5866f781aede52830440f4ed0eSelim Cinek
7283afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek
7293afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek    /**
7303afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek     * Update whether the device is very small, i.e. Notifications can be in both the top and the
7313afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek     * bottom stack at the same time
7323afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek     *
7333afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek     * @param panelHeight The normal height of the panel when it's open
7343afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek     */
7353afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek    public void updateIsSmallScreen(int panelHeight) {
7363afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek        mIsSmallScreen = panelHeight <
7373afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                mCollapsedSize  /* top stack */
7383afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                + mBottomStackSlowDownLength + mBottomStackPeekSize /* bottom stack */
7393afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek                + mMaxNotificationHeight; /* max notification height */
7403afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek    }
7413afd00e9f2b55a21ca378d6e520d24283dbb62e0Selim Cinek
7421685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    public void onExpansionStarted(StackScrollState currentState) {
7431685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        mIsExpansionChanging = true;
7441685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        mExpandedOnStart = mIsExpanded;
7451685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        ViewGroup hostView = currentState.getHostView();
7461685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        updateFirstChildHeightWhileExpanding(hostView);
7471685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    }
7481685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
7491685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    private void updateFirstChildHeightWhileExpanding(ViewGroup hostView) {
750be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        mFirstChildWhileExpanding = (ExpandableView) findFirstVisibleChild(hostView);
7519f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi        if (mFirstChildWhileExpanding != null) {
7521685e634fb0b14033bd436af8d7174436699ffecSelim Cinek            if (mExpandedOnStart) {
7531685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
7541685e634fb0b14033bd436af8d7174436699ffecSelim Cinek                // We are collapsing the shade, so the first child can get as most as high as the
75502af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek                // current height or the end value of the animation.
75602af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek                mFirstChildMaxHeight = StackStateAnimator.getFinalActualHeight(
75702af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek                        mFirstChildWhileExpanding);
7581685e634fb0b14033bd436af8d7174436699ffecSelim Cinek            } else {
75931094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                updateFirstChildMaxSizeToMaxHeight();
7601685e634fb0b14033bd436af8d7174436699ffecSelim Cinek            }
7611685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        } else {
7621685e634fb0b14033bd436af8d7174436699ffecSelim Cinek            mFirstChildMaxHeight = 0;
7631685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        }
7641685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    }
7651685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
76631094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek    private void updateFirstChildMaxSizeToMaxHeight() {
76731094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek        // We are expanding the shade, expand it to its full height.
76831094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek        if (!isMaxSizeInitialized(mFirstChildWhileExpanding)) {
76931094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek
77031094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek            // This child was not layouted yet, wait for a layout pass
77131094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek            mFirstChildWhileExpanding
77231094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                    .addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
77331094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                        @Override
77431094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                        public void onLayoutChange(View v, int left, int top, int right,
77531094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                                int bottom, int oldLeft, int oldTop, int oldRight,
77631094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                                int oldBottom) {
77731094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                            if (mFirstChildWhileExpanding != null) {
77831094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                                mFirstChildMaxHeight = getMaxAllowedChildHeight(
77931094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                                        mFirstChildWhileExpanding);
78031094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                            } else {
78131094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                                mFirstChildMaxHeight = 0;
78231094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                            }
78331094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                            v.removeOnLayoutChangeListener(this);
78431094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                        }
78531094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek                    });
78631094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek        } else {
78731094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek            mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
78831094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek        }
78931094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek    }
79031094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek
7917d447726e2cb9fae80db417012039828daab8fe7Selim Cinek    private boolean isMaxSizeInitialized(ExpandableView child) {
7927d447726e2cb9fae80db417012039828daab8fe7Selim Cinek        if (child instanceof ExpandableNotificationRow) {
7937d447726e2cb9fae80db417012039828daab8fe7Selim Cinek            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
79431094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek            return row.isMaxExpandHeightInitialized();
7957d447726e2cb9fae80db417012039828daab8fe7Selim Cinek        }
7967d447726e2cb9fae80db417012039828daab8fe7Selim Cinek        return child == null || child.getWidth() != 0;
7977d447726e2cb9fae80db417012039828daab8fe7Selim Cinek    }
7987d447726e2cb9fae80db417012039828daab8fe7Selim Cinek
7999f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi    private View findFirstVisibleChild(ViewGroup container) {
8009f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi        int childCount = container.getChildCount();
8019f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi        for (int i = 0; i < childCount; i++) {
8029f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi            View child = container.getChildAt(i);
8039f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi            if (child.getVisibility() != View.GONE) {
8049f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi                return child;
8059f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi            }
8069f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi        }
8079f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi        return null;
8089f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi    }
8099f347ae27c9c9051f5130ac27fffb0e4fbef01a3Jorim Jaggi
8101685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    public void onExpansionStopped() {
8111685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        mIsExpansionChanging = false;
8121685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        mFirstChildWhileExpanding = null;
8131685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    }
8141685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
8151685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    public void setIsExpanded(boolean isExpanded) {
8161685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        this.mIsExpanded = isExpanded;
8171685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    }
8181685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
81902af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek    public void notifyChildrenChanged(final ViewGroup hostView) {
8201685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        if (mIsExpansionChanging) {
82102af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek            hostView.post(new Runnable() {
82202af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek                @Override
82302af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek                public void run() {
82402af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek                    updateFirstChildHeightWhileExpanding(hostView);
82502af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek                }
82602af41efe54eb2cc8fde7311e4cf5f0e5ff2373cSelim Cinek            });
8271685e634fb0b14033bd436af8d7174436699ffecSelim Cinek        }
8281685e634fb0b14033bd436af8d7174436699ffecSelim Cinek    }
8291685e634fb0b14033bd436af8d7174436699ffecSelim Cinek
83034c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    public void setDimmed(boolean dimmed) {
83134c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek        updatePadding(dimmed);
83234c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek    }
83334c0a8d72aee1867cf7b6d04531c7faec76ab473Selim Cinek
83431094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek    public void onReset(ExpandableView view) {
83531094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek        if (view.equals(mFirstChildWhileExpanding)) {
83631094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek            updateFirstChildMaxSizeToMaxHeight();
83731094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek        }
83831094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek    }
83931094df5c6e3cb3a4a4faacb091e35eea1f6a5deSelim Cinek
84067b2260093774f5866f781aede52830440f4ed0eSelim Cinek    class StackScrollAlgorithmState {
84167b2260093774f5866f781aede52830440f4ed0eSelim Cinek
84267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        /**
84367b2260093774f5866f781aede52830440f4ed0eSelim Cinek         * The scroll position of the algorithm
84467b2260093774f5866f781aede52830440f4ed0eSelim Cinek         */
84567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        public int scrollY;
84667b2260093774f5866f781aede52830440f4ed0eSelim Cinek
84767b2260093774f5866f781aede52830440f4ed0eSelim Cinek        /**
84867b2260093774f5866f781aede52830440f4ed0eSelim Cinek         *  The quantity of items which are in the top stack.
84967b2260093774f5866f781aede52830440f4ed0eSelim Cinek         */
85067b2260093774f5866f781aede52830440f4ed0eSelim Cinek        public float itemsInTopStack;
85167b2260093774f5866f781aede52830440f4ed0eSelim Cinek
85267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        /**
85367b2260093774f5866f781aede52830440f4ed0eSelim Cinek         * how far in is the element currently transitioning into the top stack
85467b2260093774f5866f781aede52830440f4ed0eSelim Cinek         */
85567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        public float partialInTop;
85667b2260093774f5866f781aede52830440f4ed0eSelim Cinek
85767b2260093774f5866f781aede52830440f4ed0eSelim Cinek        /**
858343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek         * The number of pixels the last child in the top stack has scrolled in to the stack
859343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek         */
860343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        public float scrolledPixelsTop;
861343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek
862343e6e258ab6a9f647eabebaed05ce3acafd2ff1Selim Cinek        /**
86367b2260093774f5866f781aede52830440f4ed0eSelim Cinek         * The last item index which is in the top stack.
86467b2260093774f5866f781aede52830440f4ed0eSelim Cinek         */
86567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        public int lastTopStackIndex;
86667b2260093774f5866f781aede52830440f4ed0eSelim Cinek
86767b2260093774f5866f781aede52830440f4ed0eSelim Cinek        /**
86867b2260093774f5866f781aede52830440f4ed0eSelim Cinek         * The quantity of items which are in the bottom stack.
86967b2260093774f5866f781aede52830440f4ed0eSelim Cinek         */
87067b2260093774f5866f781aede52830440f4ed0eSelim Cinek        public float itemsInBottomStack;
87167b2260093774f5866f781aede52830440f4ed0eSelim Cinek
87267b2260093774f5866f781aede52830440f4ed0eSelim Cinek        /**
87367b2260093774f5866f781aede52830440f4ed0eSelim Cinek         * how far in is the element currently transitioning into the bottom stack
87467b2260093774f5866f781aede52830440f4ed0eSelim Cinek         */
87567b2260093774f5866f781aede52830440f4ed0eSelim Cinek        public float partialInBottom;
876d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi
877d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi        /**
878d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi         * The children from the host view which are not gone.
879d4a57440ca5fc8461959176475b0fcd8a6e05871Jorim Jaggi         */
880be565dfc1c17b7ddafa9753851b8f82849fd3f42Jorim Jaggi        public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
88167b2260093774f5866f781aede52830440f4ed0eSelim Cinek    }
88267b2260093774f5866f781aede52830440f4ed0eSelim Cinek
88367b2260093774f5866f781aede52830440f4ed0eSelim Cinek}
884