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.graphics.Rect;
20import android.util.Log;
21import android.view.View;
22import android.view.ViewGroup;
23
24import com.android.systemui.R;
25import com.android.systemui.statusbar.DismissView;
26import com.android.systemui.statusbar.EmptyShadeView;
27import com.android.systemui.statusbar.ExpandableView;
28import com.android.systemui.statusbar.SpeedBumpView;
29
30import java.util.HashMap;
31import java.util.Map;
32
33/**
34 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which
35 * can be applied to a viewGroup.
36 */
37public class StackScrollState {
38
39    private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
40
41    private final ViewGroup mHostView;
42    private Map<ExpandableView, ViewState> mStateMap;
43    private final Rect mClipRect = new Rect();
44    private final int mClearAllTopPadding;
45
46    public StackScrollState(ViewGroup hostView) {
47        mHostView = hostView;
48        mStateMap = new HashMap<ExpandableView, ViewState>();
49        mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
50                R.dimen.clear_all_padding_top);
51    }
52
53    public ViewGroup getHostView() {
54        return mHostView;
55    }
56
57    public void resetViewStates() {
58        int numChildren = mHostView.getChildCount();
59        for (int i = 0; i < numChildren; i++) {
60            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
61            ViewState viewState = mStateMap.get(child);
62            if (viewState == null) {
63                viewState = new ViewState();
64                mStateMap.put(child, viewState);
65            }
66            // initialize with the default values of the view
67            viewState.height = child.getIntrinsicHeight();
68            viewState.gone = child.getVisibility() == View.GONE;
69            viewState.alpha = 1;
70            viewState.notGoneIndex = -1;
71        }
72    }
73
74    public ViewState getViewStateForView(View requestedView) {
75        return mStateMap.get(requestedView);
76    }
77
78    public void removeViewStateForView(View child) {
79        mStateMap.remove(child);
80    }
81
82    /**
83     * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}.
84     * The properties are only applied if they effectively changed.
85     */
86    public void apply() {
87        int numChildren = mHostView.getChildCount();
88        for (int i = 0; i < numChildren; i++) {
89            ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
90            ViewState state = mStateMap.get(child);
91            if (state == null) {
92                Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
93                        "to the hostView");
94                continue;
95            }
96            if (!state.gone) {
97                float alpha = child.getAlpha();
98                float yTranslation = child.getTranslationY();
99                float xTranslation = child.getTranslationX();
100                float zTranslation = child.getTranslationZ();
101                float scale = child.getScaleX();
102                int height = child.getActualHeight();
103                float newAlpha = state.alpha;
104                float newYTranslation = state.yTranslation;
105                float newZTranslation = state.zTranslation;
106                float newScale = state.scale;
107                int newHeight = state.height;
108                boolean becomesInvisible = newAlpha == 0.0f;
109                if (alpha != newAlpha && xTranslation == 0) {
110                    // apply layer type
111                    boolean becomesFullyVisible = newAlpha == 1.0f;
112                    boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible;
113                    int layerType = child.getLayerType();
114                    int newLayerType = newLayerTypeIsHardware
115                            ? View.LAYER_TYPE_HARDWARE
116                            : View.LAYER_TYPE_NONE;
117                    if (layerType != newLayerType) {
118                        child.setLayerType(newLayerType, null);
119                    }
120
121                    // apply alpha
122                    child.setAlpha(newAlpha);
123                }
124
125                // apply visibility
126                int oldVisibility = child.getVisibility();
127                int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
128                if (newVisibility != oldVisibility) {
129                    child.setVisibility(newVisibility);
130                }
131
132                // apply yTranslation
133                if (yTranslation != newYTranslation) {
134                    child.setTranslationY(newYTranslation);
135                }
136
137                // apply zTranslation
138                if (zTranslation != newZTranslation) {
139                    child.setTranslationZ(newZTranslation);
140                }
141
142                // apply scale
143                if (scale != newScale) {
144                    child.setScaleX(newScale);
145                    child.setScaleY(newScale);
146                }
147
148                // apply height
149                if (height != newHeight) {
150                    child.setActualHeight(newHeight, false /* notifyListeners */);
151                }
152
153                // apply dimming
154                child.setDimmed(state.dimmed, false /* animate */);
155
156                // apply dark
157                child.setDark(state.dark, false /* animate */, 0 /* delay */);
158
159                // apply hiding sensitive
160                child.setHideSensitive(
161                        state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
162
163                // apply speed bump state
164                child.setBelowSpeedBump(state.belowSpeedBump);
165
166                // apply clipping
167                float oldClipTopAmount = child.getClipTopAmount();
168                if (oldClipTopAmount != state.clipTopAmount) {
169                    child.setClipTopAmount(state.clipTopAmount);
170                }
171                updateChildClip(child, newHeight, state.topOverLap);
172
173                if(child instanceof SpeedBumpView) {
174                    performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0);
175                } else if (child instanceof DismissView) {
176                    DismissView dismissView = (DismissView) child;
177                    boolean visible = state.topOverLap < mClearAllTopPadding;
178                    dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
179                } else if (child instanceof EmptyShadeView) {
180                    EmptyShadeView emptyShadeView = (EmptyShadeView) child;
181                    boolean visible = state.topOverLap <= 0;
182                    emptyShadeView.performVisibilityAnimation(
183                            visible && !emptyShadeView.willBeGone());
184                }
185            }
186        }
187    }
188
189    /**
190     * Updates the clipping of a view
191     *
192     * @param child the view to update
193     * @param height the currently applied height of the view
194     * @param clipInset how much should this view be clipped from the top
195     */
196    private void updateChildClip(View child, int height, int clipInset) {
197        mClipRect.set(0,
198                clipInset,
199                child.getWidth(),
200                height);
201        child.setClipBounds(mClipRect);
202    }
203
204    public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, ViewState state,
205            long delay) {
206        View nextChild = getNextChildNotGone(i);
207        if (nextChild != null) {
208            float lineEnd = state.yTranslation + state.height / 2;
209            ViewState nextState = getViewStateForView(nextChild);
210            boolean startIsAboveNext = nextState.yTranslation > lineEnd;
211            speedBump.animateDivider(startIsAboveNext, delay, null /* onFinishedRunnable */);
212        }
213    }
214
215    private View getNextChildNotGone(int childIndex) {
216        int childCount = mHostView.getChildCount();
217        for (int i = childIndex + 1; i < childCount; i++) {
218            View child = mHostView.getChildAt(i);
219            if (child.getVisibility() != View.GONE) {
220                return child;
221            }
222        }
223        return null;
224    }
225
226    public static class ViewState {
227
228        // These are flags such that we can create masks for filtering.
229
230        public static final int LOCATION_UNKNOWN = 0x00;
231        public static final int LOCATION_FIRST_CARD = 0x01;
232        public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
233        public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
234        public static final int LOCATION_MAIN_AREA = 0x08;
235        public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
236        public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
237        /** The view isn't layouted at all. */
238        public static final int LOCATION_GONE = 0x40;
239
240        float alpha;
241        float yTranslation;
242        float zTranslation;
243        int height;
244        boolean gone;
245        float scale;
246        boolean dimmed;
247        boolean dark;
248        boolean hideSensitive;
249        boolean belowSpeedBump;
250
251        /**
252         * The amount which the view should be clipped from the top. This is calculated to
253         * perceive consistent shadows.
254         */
255        int clipTopAmount;
256
257        /**
258         * How much does the child overlap with the previous view on the top? Can be used for
259         * a clipping optimization
260         */
261        int topOverLap;
262
263        /**
264         * The index of the view, only accounting for views not equal to GONE
265         */
266        int notGoneIndex;
267
268        /**
269         * The location this view is currently rendered at.
270         *
271         * <p>See <code>LOCATION_</code> flags.</p>
272         */
273        int location;
274    }
275}
276