• 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.app.Notification;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.graphics.drawable.ColorDrawable;
23 import android.service.notification.StatusBarNotification;
24 import android.util.AttributeSet;
25 import android.view.LayoutInflater;
26 import android.view.NotificationHeaderView;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.RemoteViews;
30 import android.widget.TextView;
31 
32 import com.android.systemui.R;
33 import com.android.systemui.ViewInvertHelper;
34 import com.android.systemui.statusbar.CrossFadeHelper;
35 import com.android.systemui.statusbar.ExpandableNotificationRow;
36 import com.android.systemui.statusbar.NotificationHeaderUtil;
37 import com.android.systemui.statusbar.notification.HybridGroupManager;
38 import com.android.systemui.statusbar.notification.HybridNotificationView;
39 import com.android.systemui.statusbar.notification.NotificationUtils;
40 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
41 import com.android.systemui.statusbar.phone.NotificationPanelView;
42 
43 import java.util.ArrayList;
44 import java.util.List;
45 
46 /**
47  * A container containing child notifications
48  */
49 public class NotificationChildrenContainer extends ViewGroup {
50 
51     private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
52     private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
53     private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
54 
55     private final List<View> mDividers = new ArrayList<>();
56     private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
57     private final HybridGroupManager mHybridGroupManager;
58     private int mChildPadding;
59     private int mDividerHeight;
60     private int mMaxNotificationHeight;
61     private int mNotificationHeaderMargin;
62     private int mNotificatonTopPadding;
63     private float mCollapsedBottompadding;
64     private ViewInvertHelper mOverflowInvertHelper;
65     private boolean mChildrenExpanded;
66     private ExpandableNotificationRow mNotificationParent;
67     private TextView mOverflowNumber;
68     private ViewState mGroupOverFlowState;
69     private int mRealHeight;
70     private boolean mUserLocked;
71     private int mActualHeight;
72     private boolean mNeverAppliedGroupState;
73     private int mHeaderHeight;
74 
75     private NotificationHeaderView mNotificationHeader;
76     private NotificationViewWrapper mNotificationHeaderWrapper;
77     private NotificationHeaderUtil mHeaderUtil;
78     private ViewState mHeaderViewState;
79 
NotificationChildrenContainer(Context context)80     public NotificationChildrenContainer(Context context) {
81         this(context, null);
82     }
83 
NotificationChildrenContainer(Context context, AttributeSet attrs)84     public NotificationChildrenContainer(Context context, AttributeSet attrs) {
85         this(context, attrs, 0);
86     }
87 
NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr)88     public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
89         this(context, attrs, defStyleAttr, 0);
90     }
91 
NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)92     public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
93             int defStyleRes) {
94         super(context, attrs, defStyleAttr, defStyleRes);
95         initDimens();
96         mHybridGroupManager = new HybridGroupManager(getContext(), this);
97     }
98 
initDimens()99     private void initDimens() {
100         mChildPadding = getResources().getDimensionPixelSize(
101                 R.dimen.notification_children_padding);
102         mDividerHeight = Math.max(1, getResources().getDimensionPixelSize(
103                 R.dimen.notification_divider_height));
104         mHeaderHeight = getResources().getDimensionPixelSize(R.dimen.notification_header_height);
105         mMaxNotificationHeight = getResources().getDimensionPixelSize(
106                 R.dimen.notification_max_height);
107         mNotificationHeaderMargin = getResources().getDimensionPixelSize(
108                 com.android.internal.R.dimen.notification_content_margin_top);
109         mNotificatonTopPadding = getResources().getDimensionPixelSize(
110                 R.dimen.notification_children_container_top_padding);
111         mCollapsedBottompadding = getResources().getDimensionPixelSize(
112                 com.android.internal.R.dimen.notification_content_margin_bottom);
113     }
114 
115     @Override
onLayout(boolean changed, int l, int t, int r, int b)116     protected void onLayout(boolean changed, int l, int t, int r, int b) {
117         int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
118         for (int i = 0; i < childCount; i++) {
119             View child = mChildren.get(i);
120             // We need to layout all children even the GONE ones, such that the heights are
121             // calculated correctly as they are used to calculate how many we can fit on the screen
122             child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
123             mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight);
124         }
125         if (mOverflowNumber != null) {
126             mOverflowNumber.layout(getWidth() - mOverflowNumber.getMeasuredWidth(), 0, getWidth(),
127                     mOverflowNumber.getMeasuredHeight());
128         }
129         if (mNotificationHeader != null) {
130             mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(),
131                     mNotificationHeader.getMeasuredHeight());
132         }
133     }
134 
135     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)136     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
137         int ownMaxHeight = mMaxNotificationHeight;
138         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
139         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
140         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
141         int size = MeasureSpec.getSize(heightMeasureSpec);
142         if (hasFixedHeight || isHeightLimited) {
143             ownMaxHeight = Math.min(ownMaxHeight, size);
144         }
145         int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
146         int width = MeasureSpec.getSize(widthMeasureSpec);
147         if (mOverflowNumber != null) {
148             mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
149                     newHeightSpec);
150         }
151         int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
152         int height = mNotificationHeaderMargin + mNotificatonTopPadding;
153         int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
154         int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
155         int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1;
156         for (int i = 0; i < childCount; i++) {
157             ExpandableNotificationRow child = mChildren.get(i);
158             // We need to measure all children even the GONE ones, such that the heights are
159             // calculated correctly as they are used to calculate how many we can fit on the screen.
160             boolean isOverflow = i == overflowIndex;
161             child.setSingleLineWidthIndention(isOverflow && mOverflowNumber != null
162                     ? mOverflowNumber.getMeasuredWidth()
163                     : 0);
164             child.measure(widthMeasureSpec, newHeightSpec);
165             // layout the divider
166             View divider = mDividers.get(i);
167             divider.measure(widthMeasureSpec, dividerHeightSpec);
168             if (child.getVisibility() != GONE) {
169                 height += child.getMeasuredHeight() + mDividerHeight;
170             }
171         }
172         mRealHeight = height;
173         if (heightMode != MeasureSpec.UNSPECIFIED) {
174             height = Math.min(height, size);
175         }
176 
177         if (mNotificationHeader != null) {
178             int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
179             mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec);
180         }
181 
182         setMeasuredDimension(width, height);
183     }
184 
185     @Override
pointInView(float localX, float localY, float slop)186     public boolean pointInView(float localX, float localY, float slop) {
187         return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
188                 localY < (mRealHeight + slop);
189     }
190 
191     /**
192      * Add a child notification to this view.
193      *
194      * @param row the row to add
195      * @param childIndex the index to add it at, if -1 it will be added at the end
196      */
addNotification(ExpandableNotificationRow row, int childIndex)197     public void addNotification(ExpandableNotificationRow row, int childIndex) {
198         int newIndex = childIndex < 0 ? mChildren.size() : childIndex;
199         mChildren.add(newIndex, row);
200         addView(row);
201         row.setUserLocked(mUserLocked);
202 
203         View divider = inflateDivider();
204         addView(divider);
205         mDividers.add(newIndex, divider);
206 
207         updateGroupOverflow();
208     }
209 
210     public void removeNotification(ExpandableNotificationRow row) {
211         int childIndex = mChildren.indexOf(row);
212         mChildren.remove(row);
213         removeView(row);
214 
215         final View divider = mDividers.remove(childIndex);
216         removeView(divider);
217         getOverlay().add(divider);
218         CrossFadeHelper.fadeOut(divider, new Runnable() {
219             @Override
220             public void run() {
221                 getOverlay().remove(divider);
222             }
223         });
224 
225         row.setSystemChildExpanded(false);
226         row.setUserLocked(false);
227         updateGroupOverflow();
228         if (!row.isRemoved()) {
229             mHeaderUtil.restoreNotificationHeader(row);
230         }
231     }
232 
233     /**
234      * @return The number of notification children in the container.
235      */
236     public int getNotificationChildCount() {
237         return mChildren.size();
238     }
239 
240     public void recreateNotificationHeader(OnClickListener listener, StatusBarNotification notification) {
241         final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
242                 mNotificationParent.getStatusBarNotification().getNotification());
243         final RemoteViews header = builder.makeNotificationHeader();
244         if (mNotificationHeader == null) {
245             mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
246             final View expandButton = mNotificationHeader.findViewById(
247                     com.android.internal.R.id.expand_button);
248             expandButton.setVisibility(VISIBLE);
249             mNotificationHeader.setOnClickListener(listener);
250             mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
251                     mNotificationHeader, mNotificationParent);
252             addView(mNotificationHeader, 0);
253             invalidate();
254         } else {
255             header.reapply(getContext(), mNotificationHeader);
256             mNotificationHeaderWrapper.notifyContentUpdated(notification);
257         }
258         updateChildrenHeaderAppearance();
259     }
260 
261     public void updateChildrenHeaderAppearance() {
262         mHeaderUtil.updateChildrenHeaderAppearance();
263     }
264 
265     public void updateGroupOverflow() {
266         int childCount = mChildren.size();
267         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
268         if (childCount > maxAllowedVisibleChildren) {
269             mOverflowNumber = mHybridGroupManager.bindOverflowNumber(
270                     mOverflowNumber, childCount - maxAllowedVisibleChildren);
271             if (mOverflowInvertHelper == null) {
272                 mOverflowInvertHelper = new ViewInvertHelper(mOverflowNumber,
273                         NotificationPanelView.DOZE_ANIMATION_DURATION);
274             }
275             if (mGroupOverFlowState == null) {
276                 mGroupOverFlowState = new ViewState();
277                 mNeverAppliedGroupState = true;
278             }
279         } else if (mOverflowNumber != null) {
280             removeView(mOverflowNumber);
281             if (isShown()) {
282                 final View removedOverflowNumber = mOverflowNumber;
283                 addTransientView(removedOverflowNumber, getTransientViewCount());
284                 CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() {
285                     @Override
286                     public void run() {
287                         removeTransientView(removedOverflowNumber);
288                     }
289                 });
290             }
291             mOverflowNumber = null;
292             mOverflowInvertHelper = null;
293             mGroupOverFlowState = null;
294         }
295     }
296 
297     @Override
298     protected void onConfigurationChanged(Configuration newConfig) {
299         super.onConfigurationChanged(newConfig);
300         updateGroupOverflow();
301     }
302 
303     private View inflateDivider() {
304         return LayoutInflater.from(mContext).inflate(
305                 R.layout.notification_children_divider, this, false);
306     }
307 
308     public List<ExpandableNotificationRow> getNotificationChildren() {
309         return mChildren;
310     }
311 
312     /**
313      * Apply the order given in the list to the children.
314      *
315      * @param childOrder the new list order
316      * @return whether the list order has changed
317      */
318     public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
319         if (childOrder == null) {
320             return false;
321         }
322         boolean result = false;
323         for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) {
324             ExpandableNotificationRow child = mChildren.get(i);
325             ExpandableNotificationRow desiredChild = childOrder.get(i);
326             if (child != desiredChild) {
327                 mChildren.remove(desiredChild);
328                 mChildren.add(i, desiredChild);
329                 result = true;
330             }
331         }
332         updateExpansionStates();
333         return result;
334     }
335 
336     private void updateExpansionStates() {
337         if (mChildrenExpanded || mUserLocked) {
338             // we don't modify it the group is expanded or if we are expanding it
339             return;
340         }
341         int size = mChildren.size();
342         for (int i = 0; i < size; i++) {
343             ExpandableNotificationRow child = mChildren.get(i);
344             child.setSystemChildExpanded(i == 0 && size == 1);
345         }
346     }
347 
348     /**
349      *
350      * @return the intrinsic size of this children container, i.e the natural fully expanded state
351      */
352     public int getIntrinsicHeight() {
353         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
354         return getIntrinsicHeight(maxAllowedVisibleChildren);
355     }
356 
357     /**
358      * @return the intrinsic height with a number of children given
359      *         in @param maxAllowedVisibleChildren
360      */
361     private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
362         int intrinsicHeight = mNotificationHeaderMargin;
363         int visibleChildren = 0;
364         int childCount = mChildren.size();
365         boolean firstChild = true;
366         float expandFactor = 0;
367         if (mUserLocked) {
368             expandFactor = getGroupExpandFraction();
369         }
370         for (int i = 0; i < childCount; i++) {
371             if (visibleChildren >= maxAllowedVisibleChildren) {
372                 break;
373             }
374             if (!firstChild) {
375                 if (mUserLocked) {
376                     intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
377                             expandFactor);
378                 } else {
379                     intrinsicHeight += mChildrenExpanded ? mDividerHeight : mChildPadding;
380                 }
381             } else {
382                 if (mUserLocked) {
383                     intrinsicHeight += NotificationUtils.interpolate(
384                             0,
385                             mNotificatonTopPadding + mDividerHeight,
386                             expandFactor);
387                 } else {
388                     intrinsicHeight += mChildrenExpanded
389                             ? mNotificatonTopPadding + mDividerHeight
390                             : 0;
391                 }
392                 firstChild = false;
393             }
394             ExpandableNotificationRow child = mChildren.get(i);
395             intrinsicHeight += child.getIntrinsicHeight();
396             visibleChildren++;
397         }
398         if (mUserLocked) {
399             intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
400                     expandFactor);
401         } else if (!mChildrenExpanded) {
402             intrinsicHeight += mCollapsedBottompadding;
403         }
404         return intrinsicHeight;
405     }
406 
407     /**
408      * Update the state of all its children based on a linear layout algorithm.
409      *
410      * @param resultState the state to update
411      * @param parentState the state of the parent
412      */
413     public void getState(StackScrollState resultState, StackViewState parentState) {
414         int childCount = mChildren.size();
415         int yPosition = mNotificationHeaderMargin;
416         boolean firstChild = true;
417         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
418         int lastVisibleIndex = maxAllowedVisibleChildren - 1;
419         int firstOverflowIndex = lastVisibleIndex + 1;
420         float expandFactor = 0;
421         if (mUserLocked) {
422             expandFactor = getGroupExpandFraction();
423             firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
424         }
425 
426         boolean childrenExpanded = !mNotificationParent.isGroupExpansionChanging()
427                 && mChildrenExpanded;
428         int parentHeight = parentState.height;
429         for (int i = 0; i < childCount; i++) {
430             ExpandableNotificationRow child = mChildren.get(i);
431             if (!firstChild) {
432                 if (mUserLocked) {
433                     yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
434                             expandFactor);
435                 } else {
436                     yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
437                 }
438             } else {
439                 if (mUserLocked) {
440                     yPosition += NotificationUtils.interpolate(
441                             0,
442                             mNotificatonTopPadding + mDividerHeight,
443                             expandFactor);
444                 } else {
445                     yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
446                 }
447                 firstChild = false;
448             }
449 
450             StackViewState childState = resultState.getViewStateForView(child);
451             int intrinsicHeight = child.getIntrinsicHeight();
452             if (childrenExpanded) {
453                 // When a group is expanded and moving into bottom stack, the bottom visible child
454                 // adjusts its height to move into it. Children after it are hidden.
455                 if (updateChildStateForExpandedGroup(child, parentHeight, childState, yPosition)) {
456                     // Clipping might be deactivated if the view is transforming, however, clipping
457                     // the child into the bottom stack should take precedent over this.
458                     childState.isBottomClipped = true;
459                 }
460             } else {
461                 childState.hidden = false;
462                 childState.height = intrinsicHeight;
463                 childState.isBottomClipped = false;
464             }
465             childState.yTranslation = yPosition;
466             // When the group is expanded, the children cast the shadows rather than the parent
467             // so use the parent's elevation here.
468             childState.zTranslation = childrenExpanded
469                     ? mNotificationParent.getTranslationZ()
470                     : 0;
471             childState.dimmed = parentState.dimmed;
472             childState.dark = parentState.dark;
473             childState.hideSensitive = parentState.hideSensitive;
474             childState.belowSpeedBump = parentState.belowSpeedBump;
475             childState.clipTopAmount = 0;
476             childState.alpha = 0;
477             if (i < firstOverflowIndex) {
478                 childState.alpha = 1;
479             } else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
480                 childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
481                 childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
482             }
483             childState.location = parentState.location;
484             yPosition += intrinsicHeight;
485         }
486         if (mOverflowNumber != null) {
487             ExpandableNotificationRow overflowView = mChildren.get(Math.min(
488                     getMaxAllowedVisibleChildren(true /* likeCollpased */), childCount) - 1);
489             mGroupOverFlowState.copyFrom(resultState.getViewStateForView(overflowView));
490             if (!mChildrenExpanded) {
491                 if (mUserLocked) {
492                     HybridNotificationView singleLineView = overflowView.getSingleLineView();
493                     View mirrorView = singleLineView.getTextView();
494                     if (mirrorView.getVisibility() == GONE) {
495                         mirrorView = singleLineView.getTitleView();
496                     }
497                     if (mirrorView.getVisibility() == GONE) {
498                         mirrorView = singleLineView;
499                     }
500                     mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
501                             mirrorView, overflowView);
502                     mGroupOverFlowState.alpha = mirrorView.getAlpha();
503                 }
504             } else {
505                 mGroupOverFlowState.yTranslation += mNotificationHeaderMargin;
506                 mGroupOverFlowState.alpha = 0.0f;
507             }
508         }
509         if (mNotificationHeader != null) {
510             if (mHeaderViewState == null) {
511                 mHeaderViewState = new ViewState();
512             }
513             mHeaderViewState.initFrom(mNotificationHeader);
514             mHeaderViewState.zTranslation = childrenExpanded
515                     ? mNotificationParent.getTranslationZ()
516                     : 0;
517         }
518     }
519 
520     /**
521      * When moving into the bottom stack, the bottom visible child in an expanded group adjusts its
522      * height, children in the group after this are gone.
523      *
524      * @param child the child who's height to adjust.
525      * @param parentHeight the height of the parent.
526      * @param childState the state to update.
527      * @param yPosition the yPosition of the view.
528      * @return true if children after this one should be hidden.
529      */
530     private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
531             int parentHeight, StackViewState childState, int yPosition) {
532         final int top = yPosition + child.getClipTopAmount();
533         final int intrinsicHeight = child.getIntrinsicHeight();
534         final int bottom = top + intrinsicHeight;
535         int newHeight = intrinsicHeight;
536         if (bottom >= parentHeight) {
537             // Child is either clipped or gone
538             newHeight = Math.max((parentHeight - top), 0);
539         }
540         childState.hidden = newHeight == 0;
541         childState.height = newHeight;
542         return childState.height != intrinsicHeight && !childState.hidden;
543     }
544 
545     private int getMaxAllowedVisibleChildren() {
546         return getMaxAllowedVisibleChildren(false /* likeCollapsed */);
547     }
548 
549     private int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
550         if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) {
551             return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
552         }
553         if (!mNotificationParent.isOnKeyguard()
554                 && (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp())) {
555             return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
556         }
557         return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
558     }
559 
560     public void applyState(StackScrollState state) {
561         int childCount = mChildren.size();
562         ViewState tmpState = new ViewState();
563         float expandFraction = 0.0f;
564         if (mUserLocked) {
565             expandFraction = getGroupExpandFraction();
566         }
567         final boolean dividersVisible = mUserLocked
568                 || mNotificationParent.isGroupExpansionChanging();
569         for (int i = 0; i < childCount; i++) {
570             ExpandableNotificationRow child = mChildren.get(i);
571             StackViewState viewState = state.getViewStateForView(child);
572             state.applyState(child, viewState);
573 
574             // layout the divider
575             View divider = mDividers.get(i);
576             tmpState.initFrom(divider);
577             tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
578             float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
579             if (mUserLocked && viewState.alpha != 0) {
580                 alpha = NotificationUtils.interpolate(0, 0.5f,
581                         Math.min(viewState.alpha, expandFraction));
582             }
583             tmpState.hidden = !dividersVisible;
584             tmpState.alpha = alpha;
585             state.applyViewState(divider, tmpState);
586             // There is no fake shadow to be drawn on the children
587             child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
588         }
589         if (mOverflowNumber != null) {
590             state.applyViewState(mOverflowNumber, mGroupOverFlowState);
591             mNeverAppliedGroupState = false;
592         }
593         if (mNotificationHeader != null) {
594             state.applyViewState(mNotificationHeader, mHeaderViewState);
595         }
596     }
597 
598     /**
599      * This is called when the children expansion has changed and positions the children properly
600      * for an appear animation.
601      *
602      * @param state the new state we animate to
603      */
604     public void prepareExpansionChanged(StackScrollState state) {
605         // TODO: do something that makes sense, like placing the invisible views correctly
606         return;
607     }
608 
609     public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator,
610             long baseDelay, long duration) {
611         int childCount = mChildren.size();
612         ViewState tmpState = new ViewState();
613         float expandFraction = getGroupExpandFraction();
614         final boolean dividersVisible = mUserLocked
615                 || mNotificationParent.isGroupExpansionChanging();
616         for (int i = childCount - 1; i >= 0; i--) {
617             ExpandableNotificationRow child = mChildren.get(i);
618             StackViewState viewState = state.getViewStateForView(child);
619             stateAnimator.startStackAnimations(child, viewState, state, -1, baseDelay);
620 
621             // layout the divider
622             View divider = mDividers.get(i);
623             tmpState.initFrom(divider);
624             tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
625             float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
626             if (mUserLocked && viewState.alpha != 0) {
627                 alpha = NotificationUtils.interpolate(0, 0.5f,
628                         Math.min(viewState.alpha, expandFraction));
629             }
630             tmpState.hidden = !dividersVisible;
631             tmpState.alpha = alpha;
632             stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration);
633             // There is no fake shadow to be drawn on the children
634             child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
635         }
636         if (mOverflowNumber != null) {
637             if (mNeverAppliedGroupState) {
638                 float alpha = mGroupOverFlowState.alpha;
639                 mGroupOverFlowState.alpha = 0;
640                 state.applyViewState(mOverflowNumber, mGroupOverFlowState);
641                 mGroupOverFlowState.alpha = alpha;
642                 mNeverAppliedGroupState = false;
643             }
644             stateAnimator.startViewAnimations(mOverflowNumber, mGroupOverFlowState,
645                     baseDelay, duration);
646         }
647         if (mNotificationHeader != null) {
648             state.applyViewState(mNotificationHeader, mHeaderViewState);
649         }
650     }
651 
652     public ExpandableNotificationRow getViewAtPosition(float y) {
653         // find the view under the pointer, accounting for GONE views
654         final int count = mChildren.size();
655         for (int childIdx = 0; childIdx < count; childIdx++) {
656             ExpandableNotificationRow slidingChild = mChildren.get(childIdx);
657             float childTop = slidingChild.getTranslationY();
658             float top = childTop + slidingChild.getClipTopAmount();
659             float bottom = childTop + slidingChild.getActualHeight();
660             if (y >= top && y <= bottom) {
661                 return slidingChild;
662             }
663         }
664         return null;
665     }
666 
667     public void setChildrenExpanded(boolean childrenExpanded) {
668         mChildrenExpanded = childrenExpanded;
669         updateExpansionStates();
670         if (mNotificationHeader != null) {
671             mNotificationHeader.setExpanded(childrenExpanded);
672         }
673         final int count = mChildren.size();
674         for (int childIdx = 0; childIdx < count; childIdx++) {
675             ExpandableNotificationRow child = mChildren.get(childIdx);
676             child.setChildrenExpanded(childrenExpanded, false);
677         }
678     }
679 
680     public void setNotificationParent(ExpandableNotificationRow parent) {
681         mNotificationParent = parent;
682         mHeaderUtil = new NotificationHeaderUtil(mNotificationParent);
683     }
684 
685     public ExpandableNotificationRow getNotificationParent() {
686         return mNotificationParent;
687     }
688 
689     public NotificationHeaderView getHeaderView() {
690         return mNotificationHeader;
691     }
692 
693     public void updateHeaderVisibility(int visiblity) {
694         if (mNotificationHeader != null) {
695             mNotificationHeader.setVisibility(visiblity);
696         }
697     }
698 
699     /**
700      * Called when a groups expansion changes to adjust the background of the header view.
701      *
702      * @param expanded whether the group is expanded.
703      */
704     public void updateHeaderForExpansion(boolean expanded) {
705         if (mNotificationHeader != null) {
706             if (expanded) {
707                 ColorDrawable cd = new ColorDrawable();
708                 cd.setColor(mNotificationParent.calculateBgColor());
709                 mNotificationHeader.setHeaderBackgroundDrawable(cd);
710             } else {
711                 mNotificationHeader.setHeaderBackgroundDrawable(null);
712             }
713         }
714     }
715 
716     public int getMaxContentHeight() {
717         int maxContentHeight = mNotificationHeaderMargin + mNotificatonTopPadding;
718         int visibleChildren = 0;
719         int childCount = mChildren.size();
720         for (int i = 0; i < childCount; i++) {
721             if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
722                 break;
723             }
724             ExpandableNotificationRow child = mChildren.get(i);
725             float childHeight = child.isExpanded(true /* allowOnKeyguard */)
726                     ? child.getMaxExpandHeight()
727                     : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
728             maxContentHeight += childHeight;
729             visibleChildren++;
730         }
731         if (visibleChildren > 0) {
732             maxContentHeight += visibleChildren * mDividerHeight;
733         }
734         return maxContentHeight;
735     }
736 
737     public void setActualHeight(int actualHeight) {
738         if (!mUserLocked) {
739             return;
740         }
741         mActualHeight = actualHeight;
742         float fraction = getGroupExpandFraction();
743         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
744         int childCount = mChildren.size();
745         for (int i = 0; i < childCount; i++) {
746             ExpandableNotificationRow child = mChildren.get(i);
747             float childHeight = child.isExpanded(true /* allowOnKeyguard */)
748                     ? child.getMaxExpandHeight()
749                     : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
750             if (i < maxAllowedVisibleChildren) {
751                 float singleLineHeight = child.getShowingLayout().getMinHeight(
752                         false /* likeGroupExpanded */);
753                 child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight,
754                         childHeight, fraction), false);
755             } else {
756                 child.setActualHeight((int) childHeight, false);
757             }
758         }
759     }
760 
761     public float getGroupExpandFraction() {
762         int visibleChildrenExpandedHeight = getVisibleChildrenExpandHeight();
763         int minExpandHeight = getCollapsedHeight();
764         float factor = (mActualHeight - minExpandHeight)
765                 / (float) (visibleChildrenExpandedHeight - minExpandHeight);
766         return Math.max(0.0f, Math.min(1.0f, factor));
767     }
768 
769     private int getVisibleChildrenExpandHeight() {
770         int intrinsicHeight = mNotificationHeaderMargin + mNotificatonTopPadding + mDividerHeight;
771         int visibleChildren = 0;
772         int childCount = mChildren.size();
773         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
774         for (int i = 0; i < childCount; i++) {
775             if (visibleChildren >= maxAllowedVisibleChildren) {
776                 break;
777             }
778             ExpandableNotificationRow child = mChildren.get(i);
779             float childHeight = child.isExpanded(true /* allowOnKeyguard */)
780                     ? child.getMaxExpandHeight()
781                     : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
782             intrinsicHeight += childHeight;
783             visibleChildren++;
784         }
785         return intrinsicHeight;
786     }
787 
788     public int getMinHeight() {
789         return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED);
790     }
791 
792     public int getCollapsedHeight() {
793         return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */));
794     }
795 
796     private int getMinHeight(int maxAllowedVisibleChildren) {
797         int minExpandHeight = mNotificationHeaderMargin;
798         int visibleChildren = 0;
799         boolean firstChild = true;
800         int childCount = mChildren.size();
801         for (int i = 0; i < childCount; i++) {
802             if (visibleChildren >= maxAllowedVisibleChildren) {
803                 break;
804             }
805             if (!firstChild) {
806                 minExpandHeight += mChildPadding;
807             } else {
808                 firstChild = false;
809             }
810             ExpandableNotificationRow child = mChildren.get(i);
811             minExpandHeight += child.getSingleLineView().getHeight();
812             visibleChildren++;
813         }
814         minExpandHeight += mCollapsedBottompadding;
815         return minExpandHeight;
816     }
817 
818     public void setDark(boolean dark, boolean fade, long delay) {
819         if (mOverflowNumber != null) {
820             mOverflowInvertHelper.setInverted(dark, fade, delay);
821         }
822         mNotificationHeaderWrapper.setDark(dark, fade, delay);
823     }
824 
825     public void reInflateViews(OnClickListener listener, StatusBarNotification notification) {
826         removeView(mNotificationHeader);
827         mNotificationHeader = null;
828         recreateNotificationHeader(listener, notification);
829         initDimens();
830         for (int i = 0; i < mDividers.size(); i++) {
831             View prevDivider = mDividers.get(i);
832             int index = indexOfChild(prevDivider);
833             removeView(prevDivider);
834             View divider = inflateDivider();
835             addView(divider, index);
836             mDividers.set(i, divider);
837         }
838         removeView(mOverflowNumber);
839         mOverflowNumber = null;
840         mOverflowInvertHelper = null;
841         mGroupOverFlowState = null;
842         updateGroupOverflow();
843     }
844 
845     public void setUserLocked(boolean userLocked) {
846         mUserLocked = userLocked;
847         int childCount = mChildren.size();
848         for (int i = 0; i < childCount; i++) {
849             ExpandableNotificationRow child = mChildren.get(i);
850             child.setUserLocked(userLocked);
851         }
852     }
853 
854     public void onNotificationUpdated() {
855         mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
856                 mNotificationParent.getNotificationColor());
857     }
858 
859     public int getPositionInLinearLayout(View childInGroup) {
860         int position = mNotificationHeaderMargin + mNotificatonTopPadding;
861 
862         for (int i = 0; i < mChildren.size(); i++) {
863             ExpandableNotificationRow child = mChildren.get(i);
864             boolean notGone = child.getVisibility() != View.GONE;
865             if (notGone) {
866                 position += mDividerHeight;
867             }
868             if (child == childInGroup) {
869                 return position;
870             }
871             if (notGone) {
872                 position += child.getIntrinsicHeight();
873             }
874         }
875         return 0;
876     }
877 }
878