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 
17 package com.android.systemui.statusbar.stack;
18 
19 import android.graphics.Rect;
20 import android.util.Log;
21 import android.view.View;
22 import android.view.ViewGroup;
23 
24 import com.android.systemui.R;
25 import com.android.systemui.statusbar.DismissView;
26 import com.android.systemui.statusbar.EmptyShadeView;
27 import com.android.systemui.statusbar.ExpandableView;
28 import com.android.systemui.statusbar.SpeedBumpView;
29 
30 import java.util.HashMap;
31 import 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  */
37 public 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 
StackScrollState(ViewGroup hostView)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 
getHostView()53     public ViewGroup getHostView() {
54         return mHostView;
55     }
56 
resetViewStates()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 
getViewStateForView(View requestedView)74     public ViewState getViewStateForView(View requestedView) {
75         return mStateMap.get(requestedView);
76     }
77 
removeViewStateForView(View child)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      */
apply()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