• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.content.Context;
20 import android.util.AttributeSet;
21 import android.view.LayoutInflater;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.widget.Button;
25 
26 import com.android.systemui.R;
27 import com.android.systemui.statusbar.ExpandableNotificationRow;
28 import com.android.systemui.statusbar.ExpandableView;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * A container containing child notifications
35  */
36 public class NotificationChildrenContainer extends ViewGroup {
37 
38     private final int mChildPadding;
39     private final int mDividerHeight;
40     private final int mMaxNotificationHeight;
41     private final List<View> mDividers = new ArrayList<>();
42     private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
43     private final View mCollapseButton;
44     private final View mCollapseDivider;
45     private final int mCollapseButtonHeight;
46     private final int mNotificationAppearDistance;
47 
NotificationChildrenContainer(Context context)48     public NotificationChildrenContainer(Context context) {
49         this(context, null);
50     }
51 
NotificationChildrenContainer(Context context, AttributeSet attrs)52     public NotificationChildrenContainer(Context context, AttributeSet attrs) {
53         this(context, attrs, 0);
54     }
55 
NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr)56     public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
57         this(context, attrs, defStyleAttr, 0);
58     }
59 
NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)60     public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
61             int defStyleRes) {
62         super(context, attrs, defStyleAttr, defStyleRes);
63         mChildPadding = getResources().getDimensionPixelSize(
64                 R.dimen.notification_children_padding);
65         mDividerHeight = getResources().getDimensionPixelSize(
66                 R.dimen.notification_children_divider_height);
67         mMaxNotificationHeight = getResources().getDimensionPixelSize(
68                 R.dimen.notification_max_height);
69         mNotificationAppearDistance = getResources().getDimensionPixelSize(
70                 R.dimen.notification_appear_distance);
71         LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
72         mCollapseButton = inflater.inflate(R.layout.notification_collapse_button, this,
73                 false);
74         mCollapseButtonHeight = getResources().getDimensionPixelSize(
75                 R.dimen.notification_bottom_decor_height);
76         addView(mCollapseButton);
77         mCollapseDivider = inflateDivider();
78         addView(mCollapseDivider);
79     }
80 
81     @Override
onLayout(boolean changed, int l, int t, int r, int b)82     protected void onLayout(boolean changed, int l, int t, int r, int b) {
83         int childCount = mChildren.size();
84         boolean firstChild = true;
85         for (int i = 0; i < childCount; i++) {
86             View child = mChildren.get(i);
87             boolean viewGone = child.getVisibility() == View.GONE;
88             if (i != 0) {
89                 View divider = mDividers.get(i - 1);
90                 int dividerVisibility = divider.getVisibility();
91                 int newVisibility = viewGone ? INVISIBLE : VISIBLE;
92                 if (dividerVisibility != newVisibility) {
93                     divider.setVisibility(newVisibility);
94                 }
95             }
96             if (viewGone) {
97                 continue;
98             }
99             child.layout(0, 0, getWidth(), child.getMeasuredHeight());
100             if (!firstChild) {
101                 mDividers.get(i - 1).layout(0, 0, getWidth(), mDividerHeight);
102             } else {
103                 firstChild = false;
104             }
105         }
106         mCollapseButton.layout(0, 0, getWidth(), mCollapseButtonHeight);
107         mCollapseDivider.layout(0, mCollapseButtonHeight - mDividerHeight, getWidth(),
108                 mCollapseButtonHeight);
109     }
110 
111     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)112     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
113         int ownMaxHeight = mMaxNotificationHeight;
114         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
115         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
116         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
117         if (hasFixedHeight || isHeightLimited) {
118             int size = MeasureSpec.getSize(heightMeasureSpec);
119             ownMaxHeight = Math.min(ownMaxHeight, size);
120         }
121         int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
122         int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
123         int collapseButtonHeightSpec = MeasureSpec.makeMeasureSpec(mCollapseButtonHeight,
124                 MeasureSpec.EXACTLY);
125         mCollapseButton.measure(widthMeasureSpec, collapseButtonHeightSpec);
126         mCollapseDivider.measure(widthMeasureSpec, dividerHeightSpec);
127         int height = mCollapseButtonHeight;
128         int childCount = mChildren.size();
129         boolean firstChild = true;
130         for (int i = 0; i < childCount; i++) {
131             View child = mChildren.get(i);
132             if (child.getVisibility() == View.GONE) {
133                 continue;
134             }
135             child.measure(widthMeasureSpec, newHeightSpec);
136             height += child.getMeasuredHeight();
137             if (!firstChild) {
138                 // layout the divider
139                 View divider = mDividers.get(i - 1);
140                 divider.measure(widthMeasureSpec, dividerHeightSpec);
141                 height += mChildPadding;
142             } else {
143                 firstChild = false;
144             }
145         }
146         int width = MeasureSpec.getSize(widthMeasureSpec);
147         height = hasFixedHeight ? ownMaxHeight
148                 : isHeightLimited ? Math.min(ownMaxHeight, height)
149                 : height;
150         setMeasuredDimension(width, height);
151     }
152 
153     /**
154      * Add a child notification to this view.
155      *
156      * @param row the row to add
157      * @param childIndex the index to add it at, if -1 it will be added at the end
158      */
addNotification(ExpandableNotificationRow row, int childIndex)159     public void addNotification(ExpandableNotificationRow row, int childIndex) {
160         int newIndex = childIndex < 0 ? mChildren.size() : childIndex;
161         mChildren.add(newIndex, row);
162         addView(row);
163         if (mChildren.size() != 1) {
164             View divider = inflateDivider();
165             addView(divider);
166             mDividers.add(Math.max(newIndex - 1, 0), divider);
167         }
168         // TODO: adapt background corners
169         // TODO: fix overdraw
170     }
171 
172     public void removeNotification(ExpandableNotificationRow row) {
173         int childIndex = mChildren.indexOf(row);
174         mChildren.remove(row);
175         removeView(row);
176         if (!mDividers.isEmpty()) {
177             View divider = mDividers.remove(Math.max(childIndex - 1, 0));
178             removeView(divider);
179         }
180         row.setSystemChildExpanded(false);
181         // TODO: adapt background corners
182     }
183 
184     private View inflateDivider() {
185         return LayoutInflater.from(mContext).inflate(
186                 R.layout.notification_children_divider, this, false);
187     }
188 
189     public List<ExpandableNotificationRow> getNotificationChildren() {
190         return mChildren;
191     }
192 
193     /**
194      * Apply the order given in the list to the children.
195      *
196      * @param childOrder the new list order
197      * @return whether the list order has changed
198      */
199     public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
200         if (childOrder == null) {
201             return false;
202         }
203         boolean result = false;
204         for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) {
205             ExpandableNotificationRow child = mChildren.get(i);
206             ExpandableNotificationRow desiredChild = childOrder.get(i);
207             if (child != desiredChild) {
208                 mChildren.remove(desiredChild);
209                 mChildren.add(i, desiredChild);
210                 result = true;
211             }
212         }
213 
214         // Let's make the first child expanded!
215         boolean first = true;
216         for (int i = 0; i < childOrder.size(); i++) {
217             ExpandableNotificationRow child = childOrder.get(i);
218             child.setSystemChildExpanded(first);
219             first = false;
220         }
221         return result;
222     }
223 
224     public int getIntrinsicHeight() {
225         int childCount = mChildren.size();
226         int intrinsicHeight = 0;
227         int visibleChildren = 0;
228         for (int i = 0; i < childCount; i++) {
229             ExpandableNotificationRow child = mChildren.get(i);
230             if (child.getVisibility() == View.GONE) {
231                 continue;
232             }
233             intrinsicHeight += child.getIntrinsicHeight();
234             visibleChildren++;
235         }
236         if (visibleChildren > 0) {
237             intrinsicHeight += (visibleChildren - 1) * mDividerHeight;
238         }
239         return intrinsicHeight;
240     }
241 
242     /**
243      * Update the state of all its children based on a linear layout algorithm.
244      *
245      * @param resultState the state to update
246      * @param parentState the state of the parent
247      */
248     public void getState(StackScrollState resultState, StackViewState parentState) {
249         int childCount = mChildren.size();
250         int yPosition = mCollapseButtonHeight;
251         boolean firstChild = true;
252         for (int i = 0; i < childCount; i++) {
253             ExpandableNotificationRow child = mChildren.get(i);
254             if (child.getVisibility() == View.GONE) {
255                 continue;
256             }
257             if (!firstChild) {
258                 // There's a divider
259                 yPosition += mChildPadding;
260             } else {
261                 firstChild = false;
262             }
263             StackViewState childState = resultState.getViewStateForView(child);
264             int intrinsicHeight = child.getIntrinsicHeight();
265             childState.yTranslation = yPosition;
266             childState.zTranslation = 0;
267             childState.height = intrinsicHeight;
268             childState.dimmed = parentState.dimmed;
269             childState.dark = parentState.dark;
270             childState.hideSensitive = parentState.hideSensitive;
271             childState.belowSpeedBump = parentState.belowSpeedBump;
272             childState.scale =  parentState.scale;
273             childState.clipTopAmount = 0;
274             childState.topOverLap = 0;
275             childState.location = parentState.location;
276             yPosition += intrinsicHeight;
277         }
278     }
279 
280     public void applyState(StackScrollState state) {
281         int childCount = mChildren.size();
282         boolean firstChild = true;
283         ViewState dividerState = new ViewState();
284         for (int i = 0; i < childCount; i++) {
285             ExpandableNotificationRow child = mChildren.get(i);
286             StackViewState viewState = state.getViewStateForView(child);
287             if (child.getVisibility() == View.GONE) {
288                 continue;
289             }
290             if (!firstChild) {
291                 // layout the divider
292                 View divider = mDividers.get(i - 1);
293                 dividerState.initFrom(divider);
294                 dividerState.yTranslation = (int) (viewState.yTranslation
295                         - (mChildPadding + mDividerHeight) / 2.0f);
296                 dividerState.alpha = 1;
297                 state.applyViewState(divider, dividerState);
298             } else {
299                 firstChild = false;
300             }
301             state.applyState(child, viewState);
302         }
303     }
304 
305     public void setCollapseClickListener(OnClickListener collapseClickListener) {
306         mCollapseButton.setOnClickListener(collapseClickListener);
307     }
308 
309     /**
310      * This is called when the children expansion has changed and positions the children properly
311      * for an appear animation.
312      *
313      * @param state the new state we animate to
314      */
315     public void prepareExpansionChanged(StackScrollState state) {
316         int childCount = mChildren.size();
317         boolean firstChild = true;
318         StackViewState sourceState = new StackViewState();
319         ViewState dividerState = new ViewState();
320         for (int i = 0; i < childCount; i++) {
321             ExpandableNotificationRow child = mChildren.get(i);
322             StackViewState viewState = state.getViewStateForView(child);
323             if (child.getVisibility() == View.GONE) {
324                 continue;
325             }
326             if (!firstChild) {
327                 // layout the divider
328                 View divider = mDividers.get(i - 1);
329                 dividerState.initFrom(divider);
330                 dividerState.yTranslation = viewState.yTranslation
331                         - (mChildPadding + mDividerHeight) / 2.0f + mNotificationAppearDistance;
332                 dividerState.alpha = 0;
333                 state.applyViewState(divider, dividerState);
334             } else {
335                 firstChild = false;
336             }
337             sourceState.copyFrom(viewState);
338             sourceState.alpha = 0;
339             sourceState.yTranslation += mNotificationAppearDistance;
340             state.applyState(child, sourceState);
341         }
342         mCollapseButton.setAlpha(0);
343         mCollapseDivider.setAlpha(0);
344         mCollapseDivider.setTranslationY(mNotificationAppearDistance / 4);
345     }
346 
347     public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator,
348             boolean withDelays, long baseDelay, long duration) {
349         int childCount = mChildren.size();
350         boolean firstChild = true;
351         ViewState dividerState = new ViewState();
352         int notGoneIndex = 0;
353         for (int i = 0; i < childCount; i++) {
354             ExpandableNotificationRow child = mChildren.get(i);
355             StackViewState viewState = state.getViewStateForView(child);
356             if (child.getVisibility() == View.GONE) {
357                 continue;
358             }
359             int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN,
360                     notGoneIndex + 1);
361             long delay = withDelays
362                     ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN
363                     : 0;
364             delay += baseDelay;
365             if (!firstChild) {
366                 // layout the divider
367                 View divider = mDividers.get(i - 1);
368                 dividerState.initFrom(divider);
369                 dividerState.yTranslation = viewState.yTranslation
370                         - (mChildPadding + mDividerHeight) / 2.0f;
371                 dividerState.alpha = 1;
372                 stateAnimator.startViewAnimations(divider, dividerState, delay, duration);
373             } else {
374                 firstChild = false;
375             }
376             stateAnimator.startStackAnimations(child, viewState, state, -1, delay);
377             notGoneIndex++;
378         }
379         dividerState.initFrom(mCollapseButton);
380         dividerState.alpha = 1.0f;
381         stateAnimator.startViewAnimations(mCollapseButton, dividerState, baseDelay, duration);
382         dividerState.initFrom(mCollapseDivider);
383         dividerState.alpha = 1.0f;
384         dividerState.yTranslation = 0.0f;
385         stateAnimator.startViewAnimations(mCollapseDivider, dividerState, baseDelay, duration);
386     }
387 
388     public ExpandableNotificationRow getViewAtPosition(float y) {
389         // find the view under the pointer, accounting for GONE views
390         final int count = mChildren.size();
391         for (int childIdx = 0; childIdx < count; childIdx++) {
392             ExpandableNotificationRow slidingChild = mChildren.get(childIdx);
393             float childTop = slidingChild.getTranslationY();
394             float top = childTop + slidingChild.getClipTopAmount();
395             float bottom = childTop + slidingChild.getActualHeight();
396             if (y >= top && y <= bottom) {
397                 return slidingChild;
398             }
399         }
400         return null;
401     }
402 
403     public void setTintColor(int color) {
404         ExpandableNotificationRow.applyTint(mCollapseDivider, color);
405     }
406 }
407