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.notification.stack;
18 
19 import android.app.Notification;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.content.res.Resources;
23 import android.graphics.drawable.ColorDrawable;
24 import android.service.notification.StatusBarNotification;
25 import android.util.ArraySet;
26 import android.util.AttributeSet;
27 import android.view.LayoutInflater;
28 import android.view.NotificationHeaderView;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.RemoteViews;
32 import android.widget.TextView;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.systemui.R;
36 import com.android.systemui.statusbar.CrossFadeHelper;
37 import com.android.systemui.statusbar.NotificationHeaderUtil;
38 import com.android.systemui.statusbar.notification.NotificationUtils;
39 import com.android.systemui.statusbar.notification.VisualStabilityManager;
40 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
41 import com.android.systemui.statusbar.notification.row.HybridGroupManager;
42 import com.android.systemui.statusbar.notification.row.HybridNotificationView;
43 import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
44 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 /**
50  * A container containing child notifications
51  */
52 public class NotificationChildrenContainer extends ViewGroup {
53 
54     @VisibleForTesting
55     static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
56     @VisibleForTesting
57     static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
58     public static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
59     private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
60         private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
61 
62         @Override
63         public AnimationFilter getAnimationFilter() {
64             return mAnimationFilter;
65         }
66     }.setDuration(200);
67 
68     private final List<View> mDividers = new ArrayList<>();
69     private final List<ExpandableNotificationRow> mAttachedChildren = new ArrayList<>();
70     private final HybridGroupManager mHybridGroupManager;
71     private int mChildPadding;
72     private int mDividerHeight;
73     private float mDividerAlpha;
74     private int mNotificationHeaderMargin;
75 
76     private int mNotificatonTopPadding;
77     private float mCollapsedBottompadding;
78     private boolean mChildrenExpanded;
79     private ExpandableNotificationRow mContainingNotification;
80     private TextView mOverflowNumber;
81     private ViewState mGroupOverFlowState;
82     private int mRealHeight;
83     private boolean mUserLocked;
84     private int mActualHeight;
85     private boolean mNeverAppliedGroupState;
86     private int mHeaderHeight;
87 
88     /**
89      * Whether or not individual notifications that are part of this container will have shadows.
90      */
91     private boolean mEnableShadowOnChildNotifications;
92 
93     private NotificationHeaderView mNotificationHeader;
94     private NotificationViewWrapper mNotificationHeaderWrapper;
95     private NotificationHeaderView mNotificationHeaderLowPriority;
96     private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
97     private NotificationHeaderUtil mHeaderUtil;
98     private ViewState mHeaderViewState;
99     private int mClipBottomAmount;
100     private boolean mIsLowPriority;
101     private OnClickListener mHeaderClickListener;
102     private ViewGroup mCurrentHeader;
103     private boolean mIsConversation;
104 
105     private boolean mShowDividersWhenExpanded;
106     private boolean mHideDividersDuringExpand;
107     private int mTranslationForHeader;
108     private int mCurrentHeaderTranslation = 0;
109     private float mHeaderVisibleAmount = 1.0f;
110     private int mUntruncatedChildCount;
111 
NotificationChildrenContainer(Context context)112     public NotificationChildrenContainer(Context context) {
113         this(context, null);
114     }
115 
NotificationChildrenContainer(Context context, AttributeSet attrs)116     public NotificationChildrenContainer(Context context, AttributeSet attrs) {
117         this(context, attrs, 0);
118     }
119 
NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr)120     public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
121         this(context, attrs, defStyleAttr, 0);
122     }
123 
NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)124     public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
125             int defStyleRes) {
126         super(context, attrs, defStyleAttr, defStyleRes);
127         mHybridGroupManager = new HybridGroupManager(getContext());
128         initDimens();
129         setClipChildren(false);
130     }
131 
initDimens()132     private void initDimens() {
133         Resources res = getResources();
134         mChildPadding = res.getDimensionPixelSize(R.dimen.notification_children_padding);
135         mDividerHeight = res.getDimensionPixelSize(
136                 R.dimen.notification_children_container_divider_height);
137         mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha);
138         mNotificationHeaderMargin = res.getDimensionPixelSize(
139                 R.dimen.notification_children_container_margin_top);
140         mNotificatonTopPadding = res.getDimensionPixelSize(
141                 R.dimen.notification_children_container_top_padding);
142         mHeaderHeight = mNotificationHeaderMargin + mNotificatonTopPadding;
143         mCollapsedBottompadding = res.getDimensionPixelSize(
144                 com.android.internal.R.dimen.notification_content_margin);
145         mEnableShadowOnChildNotifications =
146                 res.getBoolean(R.bool.config_enableShadowOnChildNotifications);
147         mShowDividersWhenExpanded =
148                 res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded);
149         mHideDividersDuringExpand =
150                 res.getBoolean(R.bool.config_hideDividersDuringExpand);
151         mTranslationForHeader = res.getDimensionPixelSize(
152                 com.android.internal.R.dimen.notification_content_margin)
153                 - mNotificationHeaderMargin;
154         mHybridGroupManager.initDimens();
155     }
156 
157     @Override
onLayout(boolean changed, int l, int t, int r, int b)158     protected void onLayout(boolean changed, int l, int t, int r, int b) {
159         int childCount =
160                 Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
161         for (int i = 0; i < childCount; i++) {
162             View child = mAttachedChildren.get(i);
163             // We need to layout all children even the GONE ones, such that the heights are
164             // calculated correctly as they are used to calculate how many we can fit on the screen
165             child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
166             mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight);
167         }
168         if (mOverflowNumber != null) {
169             boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
170             int left = (isRtl ? 0 : getWidth() - mOverflowNumber.getMeasuredWidth());
171             int right = left + mOverflowNumber.getMeasuredWidth();
172             mOverflowNumber.layout(left, 0, right, mOverflowNumber.getMeasuredHeight());
173         }
174         if (mNotificationHeader != null) {
175             mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(),
176                     mNotificationHeader.getMeasuredHeight());
177         }
178         if (mNotificationHeaderLowPriority != null) {
179             mNotificationHeaderLowPriority.layout(0, 0,
180                     mNotificationHeaderLowPriority.getMeasuredWidth(),
181                     mNotificationHeaderLowPriority.getMeasuredHeight());
182         }
183     }
184 
185     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)186     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
187         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
188         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
189         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
190         int size = MeasureSpec.getSize(heightMeasureSpec);
191         int newHeightSpec = heightMeasureSpec;
192         if (hasFixedHeight || isHeightLimited) {
193             newHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
194         }
195         int width = MeasureSpec.getSize(widthMeasureSpec);
196         if (mOverflowNumber != null) {
197             mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
198                     newHeightSpec);
199         }
200         int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
201         int height = mNotificationHeaderMargin + mNotificatonTopPadding;
202         int childCount =
203                 Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
204         int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
205         int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1;
206         for (int i = 0; i < childCount; i++) {
207             ExpandableNotificationRow child = mAttachedChildren.get(i);
208             // We need to measure all children even the GONE ones, such that the heights are
209             // calculated correctly as they are used to calculate how many we can fit on the screen.
210             boolean isOverflow = i == overflowIndex;
211             child.setSingleLineWidthIndention(isOverflow && mOverflowNumber != null
212                     ? mOverflowNumber.getMeasuredWidth() : 0);
213             child.measure(widthMeasureSpec, newHeightSpec);
214             // layout the divider
215             View divider = mDividers.get(i);
216             divider.measure(widthMeasureSpec, dividerHeightSpec);
217             if (child.getVisibility() != GONE) {
218                 height += child.getMeasuredHeight() + mDividerHeight;
219             }
220         }
221         mRealHeight = height;
222         if (heightMode != MeasureSpec.UNSPECIFIED) {
223             height = Math.min(height, size);
224         }
225 
226         int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
227         if (mNotificationHeader != null) {
228             mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec);
229         }
230         if (mNotificationHeaderLowPriority != null) {
231             headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
232             mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec);
233         }
234 
235         setMeasuredDimension(width, height);
236     }
237 
238     @Override
hasOverlappingRendering()239     public boolean hasOverlappingRendering() {
240         return false;
241     }
242 
243     @Override
pointInView(float localX, float localY, float slop)244     public boolean pointInView(float localX, float localY, float slop) {
245         return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
246                 localY < (mRealHeight + slop);
247     }
248 
249     /**
250      * Set the untruncated number of children in the group so that the view can update the UI
251      * appropriately. Note that this may differ from the number of views attached as truncated
252      * children will not have views.
253      */
setUntruncatedChildCount(int childCount)254     public void setUntruncatedChildCount(int childCount) {
255         mUntruncatedChildCount = childCount;
256         updateGroupOverflow();
257     }
258 
259     /**
260      * Add a child notification to this view.
261      *
262      * @param row the row to add
263      * @param childIndex the index to add it at, if -1 it will be added at the end
264      */
addNotification(ExpandableNotificationRow row, int childIndex)265     public void addNotification(ExpandableNotificationRow row, int childIndex) {
266         int newIndex = childIndex < 0 ? mAttachedChildren.size() : childIndex;
267         mAttachedChildren.add(newIndex, row);
268         addView(row);
269         row.setUserLocked(mUserLocked);
270 
271         View divider = inflateDivider();
272         addView(divider);
273         mDividers.add(newIndex, divider);
274 
275         row.setContentTransformationAmount(0, false /* isLastChild */);
276         // It doesn't make sense to keep old animations around, lets cancel them!
277         ExpandableViewState viewState = row.getViewState();
278         if (viewState != null) {
279             viewState.cancelAnimations(row);
280             row.cancelAppearDrawing();
281         }
282     }
283 
284     public void removeNotification(ExpandableNotificationRow row) {
285         int childIndex = mAttachedChildren.indexOf(row);
286         mAttachedChildren.remove(row);
287         removeView(row);
288 
289         final View divider = mDividers.remove(childIndex);
290         removeView(divider);
291         getOverlay().add(divider);
292         CrossFadeHelper.fadeOut(divider, new Runnable() {
293             @Override
294             public void run() {
295                 getOverlay().remove(divider);
296             }
297         });
298 
299         row.setSystemChildExpanded(false);
300         row.setUserLocked(false);
301         if (!row.isRemoved()) {
302             mHeaderUtil.restoreNotificationHeader(row);
303         }
304     }
305 
306     /**
307      * @return The number of notification children in the container.
308      */
309     public int getNotificationChildCount() {
310         return mAttachedChildren.size();
311     }
312 
313     public void recreateNotificationHeader(OnClickListener listener, boolean isConversation) {
314         mHeaderClickListener = listener;
315         mIsConversation = isConversation;
316         StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
317         final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
318                 notification.getNotification());
319         RemoteViews header = builder.makeNotificationHeader();
320         if (mNotificationHeader == null) {
321             mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
322             final View expandButton = mNotificationHeader.findViewById(
323                     com.android.internal.R.id.expand_button);
324             expandButton.setVisibility(VISIBLE);
325             mNotificationHeader.setOnClickListener(mHeaderClickListener);
326             mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
327                     mNotificationHeader, mContainingNotification);
328             addView(mNotificationHeader, 0);
329             invalidate();
330         } else {
331             header.reapply(getContext(), mNotificationHeader);
332         }
333         mNotificationHeaderWrapper.onContentUpdated(mContainingNotification);
334         if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
335             NotificationHeaderViewWrapper headerWrapper =
336                     (NotificationHeaderViewWrapper) mNotificationHeaderWrapper;
337             if (isConversation) {
338                 headerWrapper.applyConversationSkin();
339             } else {
340                 headerWrapper.clearConversationSkin();
341             }
342         }
343         recreateLowPriorityHeader(builder, isConversation);
344         updateHeaderVisibility(false /* animate */);
345         updateChildrenHeaderAppearance();
346     }
347 
348     /**
349      * Recreate the low-priority header.
350      *
351      * @param builder a builder to reuse. Otherwise the builder will be recovered.
352      */
353     private void recreateLowPriorityHeader(Notification.Builder builder, boolean isConversation) {
354         RemoteViews header;
355         StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
356         if (mIsLowPriority) {
357             if (builder == null) {
358                 builder = Notification.Builder.recoverBuilder(getContext(),
359                         notification.getNotification());
360             }
361             header = builder.makeLowPriorityContentView(true /* useRegularSubtext */);
362             if (mNotificationHeaderLowPriority == null) {
363                 mNotificationHeaderLowPriority = (NotificationHeaderView) header.apply(getContext(),
364                         this);
365                 final View expandButton = mNotificationHeaderLowPriority.findViewById(
366                         com.android.internal.R.id.expand_button);
367                 expandButton.setVisibility(VISIBLE);
368                 mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
369                 mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(),
370                         mNotificationHeaderLowPriority, mContainingNotification);
371                 addView(mNotificationHeaderLowPriority, 0);
372                 invalidate();
373             } else {
374                 header.reapply(getContext(), mNotificationHeaderLowPriority);
375             }
376             mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification);
377             if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
378                 NotificationHeaderViewWrapper headerWrapper =
379                         (NotificationHeaderViewWrapper) mNotificationHeaderWrapper;
380                 if (isConversation) {
381                     headerWrapper.applyConversationSkin();
382                 } else {
383                     headerWrapper.clearConversationSkin();
384                 }
385             }
386             resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader());
387         } else {
388             removeView(mNotificationHeaderLowPriority);
389             mNotificationHeaderLowPriority = null;
390             mNotificationHeaderWrapperLowPriority = null;
391         }
392     }
393 
394     public void updateChildrenHeaderAppearance() {
395         mHeaderUtil.updateChildrenHeaderAppearance();
396     }
397 
398     public void updateGroupOverflow() {
399         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
400         if (mUntruncatedChildCount > maxAllowedVisibleChildren) {
401             int number = mUntruncatedChildCount - maxAllowedVisibleChildren;
402             mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number, this);
403             if (mGroupOverFlowState == null) {
404                 mGroupOverFlowState = new ViewState();
405                 mNeverAppliedGroupState = true;
406             }
407         } else if (mOverflowNumber != null) {
408             removeView(mOverflowNumber);
409             if (isShown() && isAttachedToWindow()) {
410                 final View removedOverflowNumber = mOverflowNumber;
411                 addTransientView(removedOverflowNumber, getTransientViewCount());
412                 CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() {
413                     @Override
414                     public void run() {
415                         removeTransientView(removedOverflowNumber);
416                     }
417                 });
418             }
419             mOverflowNumber = null;
420             mGroupOverFlowState = null;
421         }
422     }
423 
424     @Override
425     protected void onConfigurationChanged(Configuration newConfig) {
426         super.onConfigurationChanged(newConfig);
427         updateGroupOverflow();
428     }
429 
430     private View inflateDivider() {
431         return LayoutInflater.from(mContext).inflate(
432                 R.layout.notification_children_divider, this, false);
433     }
434 
435     /**
436      * Get notification children that are attached currently.
437      */
438     public List<ExpandableNotificationRow> getAttachedChildren() {
439         return mAttachedChildren;
440     }
441 
442     /**
443      * Apply the order given in the list to the children.
444      *
445      * @param childOrder the new list order
446      * @param visualStabilityManager
447      * @param callback
448      * @return whether the list order has changed
449      */
450     public boolean applyChildOrder(List<? extends NotificationListItem> childOrder,
451             VisualStabilityManager visualStabilityManager,
452             VisualStabilityManager.Callback callback) {
453         if (childOrder == null) {
454             return false;
455         }
456         boolean result = false;
457         for (int i = 0; i < mAttachedChildren.size() && i < childOrder.size(); i++) {
458             ExpandableNotificationRow child = mAttachedChildren.get(i);
459             ExpandableNotificationRow desiredChild = (ExpandableNotificationRow) childOrder.get(i);
460             if (child != desiredChild) {
461                 if (visualStabilityManager.canReorderNotification(desiredChild)) {
462                     mAttachedChildren.remove(desiredChild);
463                     mAttachedChildren.add(i, desiredChild);
464                     result = true;
465                 } else {
466                     visualStabilityManager.addReorderingAllowedCallback(callback,
467                             false /* persistent */);
468                 }
469             }
470         }
471         updateExpansionStates();
472         return result;
473     }
474 
475     private void updateExpansionStates() {
476         if (mChildrenExpanded || mUserLocked) {
477             // we don't modify it the group is expanded or if we are expanding it
478             return;
479         }
480         int size = mAttachedChildren.size();
481         for (int i = 0; i < size; i++) {
482             ExpandableNotificationRow child = mAttachedChildren.get(i);
483             child.setSystemChildExpanded(i == 0 && size == 1);
484         }
485     }
486 
487     /**
488      *
489      * @return the intrinsic size of this children container, i.e the natural fully expanded state
490      */
491     public int getIntrinsicHeight() {
492         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
493         return getIntrinsicHeight(maxAllowedVisibleChildren);
494     }
495 
496     /**
497      * @return the intrinsic height with a number of children given
498      *         in @param maxAllowedVisibleChildren
499      */
500     private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
501         if (showingAsLowPriority()) {
502             return mNotificationHeaderLowPriority.getHeight();
503         }
504         int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation;
505         int visibleChildren = 0;
506         int childCount = mAttachedChildren.size();
507         boolean firstChild = true;
508         float expandFactor = 0;
509         if (mUserLocked) {
510             expandFactor = getGroupExpandFraction();
511         }
512         boolean childrenExpanded = mChildrenExpanded;
513         for (int i = 0; i < childCount; i++) {
514             if (visibleChildren >= maxAllowedVisibleChildren) {
515                 break;
516             }
517             if (!firstChild) {
518                 if (mUserLocked) {
519                     intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
520                             expandFactor);
521                 } else {
522                     intrinsicHeight += childrenExpanded ? mDividerHeight : mChildPadding;
523                 }
524             } else {
525                 if (mUserLocked) {
526                     intrinsicHeight += NotificationUtils.interpolate(
527                             0,
528                             mNotificatonTopPadding + mDividerHeight,
529                             expandFactor);
530                 } else {
531                     intrinsicHeight += childrenExpanded
532                             ? mNotificatonTopPadding + mDividerHeight
533                             : 0;
534                 }
535                 firstChild = false;
536             }
537             ExpandableNotificationRow child = mAttachedChildren.get(i);
538             intrinsicHeight += child.getIntrinsicHeight();
539             visibleChildren++;
540         }
541         if (mUserLocked) {
542             intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
543                     expandFactor);
544         } else if (!childrenExpanded) {
545             intrinsicHeight += mCollapsedBottompadding;
546         }
547         return intrinsicHeight;
548     }
549 
550     /**
551      * Update the state of all its children based on a linear layout algorithm.
552      * @param parentState the state of the parent
553      * @param ambientState the ambient state containing ambient information
554      */
555     public void updateState(ExpandableViewState parentState, AmbientState ambientState) {
556         int childCount = mAttachedChildren.size();
557         int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation;
558         boolean firstChild = true;
559         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
560         int lastVisibleIndex = maxAllowedVisibleChildren - 1;
561         int firstOverflowIndex = lastVisibleIndex + 1;
562         float expandFactor = 0;
563         boolean expandingToExpandedGroup = mUserLocked && !showingAsLowPriority();
564         if (mUserLocked) {
565             expandFactor = getGroupExpandFraction();
566             firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
567         }
568 
569         boolean childrenExpandedAndNotAnimating = mChildrenExpanded
570                 && !mContainingNotification.isGroupExpansionChanging();
571         int launchTransitionCompensation = 0;
572         for (int i = 0; i < childCount; i++) {
573             ExpandableNotificationRow child = mAttachedChildren.get(i);
574             if (!firstChild) {
575                 if (expandingToExpandedGroup) {
576                     yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
577                             expandFactor);
578                 } else {
579                     yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
580                 }
581             } else {
582                 if (expandingToExpandedGroup) {
583                     yPosition += NotificationUtils.interpolate(
584                             0,
585                             mNotificatonTopPadding + mDividerHeight,
586                             expandFactor);
587                 } else {
588                     yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
589                 }
590                 firstChild = false;
591             }
592 
593             ExpandableViewState childState = child.getViewState();
594             int intrinsicHeight = child.getIntrinsicHeight();
595             childState.height = intrinsicHeight;
596             childState.yTranslation = yPosition + launchTransitionCompensation;
597             childState.hidden = false;
598             // When the group is expanded, the children cast the shadows rather than the parent
599             // so use the parent's elevation here.
600             childState.zTranslation =
601                     (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications)
602                     ? parentState.zTranslation
603                     : 0;
604             childState.dimmed = parentState.dimmed;
605             childState.hideSensitive = parentState.hideSensitive;
606             childState.belowSpeedBump = parentState.belowSpeedBump;
607             childState.clipTopAmount = 0;
608             childState.alpha = 0;
609             if (i < firstOverflowIndex) {
610                 childState.alpha = showingAsLowPriority() ? expandFactor : 1.0f;
611             } else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
612                 childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
613                 childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
614             }
615             childState.location = parentState.location;
616             childState.inShelf = parentState.inShelf;
617             yPosition += intrinsicHeight;
618             if (child.isExpandAnimationRunning()) {
619                 launchTransitionCompensation = -ambientState.getExpandAnimationTopChange();
620             }
621 
622         }
623         if (mOverflowNumber != null) {
624             ExpandableNotificationRow overflowView = mAttachedChildren.get(Math.min(
625                     getMaxAllowedVisibleChildren(true /* likeCollapsed */), childCount) - 1);
626             mGroupOverFlowState.copyFrom(overflowView.getViewState());
627 
628             if (!mChildrenExpanded) {
629                 HybridNotificationView alignView = overflowView.getSingleLineView();
630                 if (alignView != null) {
631                     View mirrorView = alignView.getTextView();
632                     if (mirrorView.getVisibility() == GONE) {
633                         mirrorView = alignView.getTitleView();
634                     }
635                     if (mirrorView.getVisibility() == GONE) {
636                         mirrorView = alignView;
637                     }
638                     mGroupOverFlowState.alpha = mirrorView.getAlpha();
639                     mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
640                             mirrorView, overflowView);
641                 }
642             } else {
643                 mGroupOverFlowState.yTranslation += mNotificationHeaderMargin;
644                 mGroupOverFlowState.alpha = 0.0f;
645             }
646         }
647         if (mNotificationHeader != null) {
648             if (mHeaderViewState == null) {
649                 mHeaderViewState = new ViewState();
650             }
651             mHeaderViewState.initFrom(mNotificationHeader);
652             mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating
653                     ? parentState.zTranslation
654                     : 0;
655             mHeaderViewState.yTranslation = mCurrentHeaderTranslation;
656             mHeaderViewState.alpha = mHeaderVisibleAmount;
657             // The hiding is done automatically by the alpha, otherwise we'll pick it up again
658             // in the next frame with the initFrom call above and have an invisible header
659             mHeaderViewState.hidden = false;
660         }
661     }
662 
663     /**
664      * When moving into the bottom stack, the bottom visible child in an expanded group adjusts its
665      * height, children in the group after this are gone.
666      *
667      * @param child the child who's height to adjust.
668      * @param parentHeight the height of the parent.
669      * @param childState the state to update.
670      * @param yPosition the yPosition of the view.
671      * @return true if children after this one should be hidden.
672      */
673     private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
674             int parentHeight, ExpandableViewState childState, int yPosition) {
675         final int top = yPosition + child.getClipTopAmount();
676         final int intrinsicHeight = child.getIntrinsicHeight();
677         final int bottom = top + intrinsicHeight;
678         int newHeight = intrinsicHeight;
679         if (bottom >= parentHeight) {
680             // Child is either clipped or gone
681             newHeight = Math.max((parentHeight - top), 0);
682         }
683         childState.hidden = newHeight == 0;
684         childState.height = newHeight;
685         return childState.height != intrinsicHeight && !childState.hidden;
686     }
687 
688     @VisibleForTesting
689     int getMaxAllowedVisibleChildren() {
690         return getMaxAllowedVisibleChildren(false /* likeCollapsed */);
691     }
692 
693     @VisibleForTesting
694     int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
695         if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())
696                 && !showingAsLowPriority()) {
697             return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
698         }
699         if (mIsLowPriority
700                 || (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded())
701                 || (mContainingNotification.isHeadsUpState()
702                         && mContainingNotification.canShowHeadsUp())) {
703             return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
704         }
705         return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
706     }
707 
708     /** Applies state to children. */
709     public void applyState() {
710         int childCount = mAttachedChildren.size();
711         ViewState tmpState = new ViewState();
712         float expandFraction = 0.0f;
713         if (mUserLocked) {
714             expandFraction = getGroupExpandFraction();
715         }
716         final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
717                 || (mChildrenExpanded && mShowDividersWhenExpanded)
718                 || (mContainingNotification.isGroupExpansionChanging()
719                 && !mHideDividersDuringExpand);
720         for (int i = 0; i < childCount; i++) {
721             ExpandableNotificationRow child = mAttachedChildren.get(i);
722             ExpandableViewState viewState = child.getViewState();
723             viewState.applyToView(child);
724 
725             // layout the divider
726             View divider = mDividers.get(i);
727             tmpState.initFrom(divider);
728             tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
729             float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0;
730             if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
731                 alpha = NotificationUtils.interpolate(0, 0.5f,
732                         Math.min(viewState.alpha, expandFraction));
733             }
734             tmpState.hidden = !dividersVisible;
735             tmpState.alpha = alpha;
736             tmpState.applyToView(divider);
737             // There is no fake shadow to be drawn on the children
738             child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
739         }
740         if (mGroupOverFlowState != null) {
741             mGroupOverFlowState.applyToView(mOverflowNumber);
742             mNeverAppliedGroupState = false;
743         }
744         if (mHeaderViewState != null) {
745             mHeaderViewState.applyToView(mNotificationHeader);
746         }
747         updateChildrenClipping();
748     }
749 
750     private void updateChildrenClipping() {
751         if (mContainingNotification.hasExpandingChild()) {
752             return;
753         }
754         int childCount = mAttachedChildren.size();
755         int layoutEnd = mContainingNotification.getActualHeight() - mClipBottomAmount;
756         for (int i = 0; i < childCount; i++) {
757             ExpandableNotificationRow child = mAttachedChildren.get(i);
758             if (child.getVisibility() == GONE) {
759                 continue;
760             }
761             float childTop = child.getTranslationY();
762             float childBottom = childTop + child.getActualHeight();
763             boolean visible = true;
764             int clipBottomAmount = 0;
765             if (childTop > layoutEnd) {
766                 visible = false;
767             } else if (childBottom > layoutEnd) {
768                 clipBottomAmount = (int) (childBottom - layoutEnd);
769             }
770 
771             boolean isVisible = child.getVisibility() == VISIBLE;
772             if (visible != isVisible) {
773                 child.setVisibility(visible ? VISIBLE : INVISIBLE);
774             }
775 
776             child.setClipBottomAmount(clipBottomAmount);
777         }
778     }
779 
780     /**
781      * This is called when the children expansion has changed and positions the children properly
782      * for an appear animation.
783      *
784      */
785     public void prepareExpansionChanged() {
786         // TODO: do something that makes sense, like placing the invisible views correctly
787         return;
788     }
789 
790     /** Animate to a given state. */
791     public void startAnimationToState(AnimationProperties properties) {
792         int childCount = mAttachedChildren.size();
793         ViewState tmpState = new ViewState();
794         float expandFraction = getGroupExpandFraction();
795         final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
796                 || (mChildrenExpanded && mShowDividersWhenExpanded)
797                 || (mContainingNotification.isGroupExpansionChanging()
798                 && !mHideDividersDuringExpand);
799         for (int i = childCount - 1; i >= 0; i--) {
800             ExpandableNotificationRow child = mAttachedChildren.get(i);
801             ExpandableViewState viewState = child.getViewState();
802             viewState.animateTo(child, properties);
803 
804             // layout the divider
805             View divider = mDividers.get(i);
806             tmpState.initFrom(divider);
807             tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
808             float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
809             if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
810                 alpha = NotificationUtils.interpolate(0, 0.5f,
811                         Math.min(viewState.alpha, expandFraction));
812             }
813             tmpState.hidden = !dividersVisible;
814             tmpState.alpha = alpha;
815             tmpState.animateTo(divider, properties);
816             // There is no fake shadow to be drawn on the children
817             child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
818         }
819         if (mOverflowNumber != null) {
820             if (mNeverAppliedGroupState) {
821                 float alpha = mGroupOverFlowState.alpha;
822                 mGroupOverFlowState.alpha = 0;
823                 mGroupOverFlowState.applyToView(mOverflowNumber);
824                 mGroupOverFlowState.alpha = alpha;
825                 mNeverAppliedGroupState = false;
826             }
827             mGroupOverFlowState.animateTo(mOverflowNumber, properties);
828         }
829         if (mNotificationHeader != null) {
830             mHeaderViewState.applyToView(mNotificationHeader);
831         }
832         updateChildrenClipping();
833     }
834 
835     public ExpandableNotificationRow getViewAtPosition(float y) {
836         // find the view under the pointer, accounting for GONE views
837         final int count = mAttachedChildren.size();
838         for (int childIdx = 0; childIdx < count; childIdx++) {
839             ExpandableNotificationRow slidingChild = mAttachedChildren.get(childIdx);
840             float childTop = slidingChild.getTranslationY();
841             float top = childTop + slidingChild.getClipTopAmount();
842             float bottom = childTop + slidingChild.getActualHeight();
843             if (y >= top && y <= bottom) {
844                 return slidingChild;
845             }
846         }
847         return null;
848     }
849 
850     public void setChildrenExpanded(boolean childrenExpanded) {
851         mChildrenExpanded = childrenExpanded;
852         updateExpansionStates();
853         if (mNotificationHeader != null) {
854             mNotificationHeader.setExpanded(childrenExpanded);
855         }
856         final int count = mAttachedChildren.size();
857         for (int childIdx = 0; childIdx < count; childIdx++) {
858             ExpandableNotificationRow child = mAttachedChildren.get(childIdx);
859             child.setChildrenExpanded(childrenExpanded, false);
860         }
861         updateHeaderTouchability();
862     }
863 
864     public void setContainingNotification(ExpandableNotificationRow parent) {
865         mContainingNotification = parent;
866         mHeaderUtil = new NotificationHeaderUtil(mContainingNotification);
867     }
868 
869     public ExpandableNotificationRow getContainingNotification() {
870         return mContainingNotification;
871     }
872 
873     public NotificationHeaderView getHeaderView() {
874         return mNotificationHeader;
875     }
876 
877     public NotificationHeaderView getLowPriorityHeaderView() {
878         return mNotificationHeaderLowPriority;
879     }
880 
881     @VisibleForTesting
882     public ViewGroup getCurrentHeaderView() {
883         return mCurrentHeader;
884     }
885 
886     private void updateHeaderVisibility(boolean animate) {
887         ViewGroup desiredHeader;
888         ViewGroup currentHeader = mCurrentHeader;
889         desiredHeader = calculateDesiredHeader();
890 
891         if (currentHeader == desiredHeader) {
892             return;
893         }
894 
895         if (animate) {
896             if (desiredHeader != null && currentHeader != null) {
897                 currentHeader.setVisibility(VISIBLE);
898                 desiredHeader.setVisibility(VISIBLE);
899                 NotificationViewWrapper visibleWrapper = getWrapperForView(desiredHeader);
900                 NotificationViewWrapper hiddenWrapper = getWrapperForView(currentHeader);
901                 visibleWrapper.transformFrom(hiddenWrapper);
902                 hiddenWrapper.transformTo(visibleWrapper, () -> updateHeaderVisibility(false));
903                 startChildAlphaAnimations(desiredHeader == mNotificationHeader);
904             } else {
905                 animate = false;
906             }
907         }
908         if (!animate) {
909             if (desiredHeader != null) {
910                 getWrapperForView(desiredHeader).setVisible(true);
911                 desiredHeader.setVisibility(VISIBLE);
912             }
913             if (currentHeader != null) {
914                 // Wrapper can be null if we were a low priority notification
915                 // and just destroyed it by calling setIsLowPriority(false)
916                 NotificationViewWrapper wrapper = getWrapperForView(currentHeader);
917                 if (wrapper != null) {
918                     wrapper.setVisible(false);
919                 }
920                 currentHeader.setVisibility(INVISIBLE);
921             }
922         }
923 
924         resetHeaderVisibilityIfNeeded(mNotificationHeader, desiredHeader);
925         resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, desiredHeader);
926 
927         mCurrentHeader = desiredHeader;
928     }
929 
930     private void resetHeaderVisibilityIfNeeded(View header, View desiredHeader) {
931         if (header == null) {
932             return;
933         }
934         if (header != mCurrentHeader && header != desiredHeader) {
935             getWrapperForView(header).setVisible(false);
936             header.setVisibility(INVISIBLE);
937         }
938         if (header == desiredHeader && header.getVisibility() != VISIBLE) {
939             getWrapperForView(header).setVisible(true);
940             header.setVisibility(VISIBLE);
941         }
942     }
943 
944     private ViewGroup calculateDesiredHeader() {
945         ViewGroup desiredHeader;
946         if (showingAsLowPriority()) {
947             desiredHeader = mNotificationHeaderLowPriority;
948         } else {
949             desiredHeader = mNotificationHeader;
950         }
951         return desiredHeader;
952     }
953 
954     private void startChildAlphaAnimations(boolean toVisible) {
955         float target = toVisible ? 1.0f : 0.0f;
956         float start = 1.0f - target;
957         int childCount = mAttachedChildren.size();
958         for (int i = 0; i < childCount; i++) {
959             if (i >= NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED) {
960                 break;
961             }
962             ExpandableNotificationRow child = mAttachedChildren.get(i);
963             child.setAlpha(start);
964             ViewState viewState = new ViewState();
965             viewState.initFrom(child);
966             viewState.alpha = target;
967             ALPHA_FADE_IN.setDelay(i * 50);
968             viewState.animateTo(child, ALPHA_FADE_IN);
969         }
970     }
971 
972 
973     private void updateHeaderTransformation() {
974         if (mUserLocked && showingAsLowPriority()) {
975             float fraction = getGroupExpandFraction();
976             mNotificationHeaderWrapper.transformFrom(mNotificationHeaderWrapperLowPriority,
977                     fraction);
978             mNotificationHeader.setVisibility(VISIBLE);
979             mNotificationHeaderWrapperLowPriority.transformTo(mNotificationHeaderWrapper,
980                     fraction);
981         }
982 
983     }
984 
985     private NotificationViewWrapper getWrapperForView(View visibleHeader) {
986         if (visibleHeader == mNotificationHeader) {
987             return mNotificationHeaderWrapper;
988         }
989         return mNotificationHeaderWrapperLowPriority;
990     }
991 
992     /**
993      * Called when a groups expansion changes to adjust the background of the header view.
994      *
995      * @param expanded whether the group is expanded.
996      */
997     public void updateHeaderForExpansion(boolean expanded) {
998         if (mNotificationHeader != null) {
999             if (expanded) {
1000                 ColorDrawable cd = new ColorDrawable();
1001                 cd.setColor(mContainingNotification.calculateBgColor());
1002                 mNotificationHeader.setHeaderBackgroundDrawable(cd);
1003             } else {
1004                 mNotificationHeader.setHeaderBackgroundDrawable(null);
1005             }
1006         }
1007     }
1008 
1009     public int getMaxContentHeight() {
1010         if (showingAsLowPriority()) {
1011             return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true
1012                     /* likeHighPriority */);
1013         }
1014         int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
1015                 + mNotificatonTopPadding;
1016         int visibleChildren = 0;
1017         int childCount = mAttachedChildren.size();
1018         for (int i = 0; i < childCount; i++) {
1019             if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
1020                 break;
1021             }
1022             ExpandableNotificationRow child = mAttachedChildren.get(i);
1023             float childHeight = child.isExpanded(true /* allowOnKeyguard */)
1024                     ? child.getMaxExpandHeight()
1025                     : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
1026             maxContentHeight += childHeight;
1027             visibleChildren++;
1028         }
1029         if (visibleChildren > 0) {
1030             maxContentHeight += visibleChildren * mDividerHeight;
1031         }
1032         return maxContentHeight;
1033     }
1034 
1035     public void setActualHeight(int actualHeight) {
1036         if (!mUserLocked) {
1037             return;
1038         }
1039         mActualHeight = actualHeight;
1040         float fraction = getGroupExpandFraction();
1041         boolean showingLowPriority = showingAsLowPriority();
1042         updateHeaderTransformation();
1043         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
1044         int childCount = mAttachedChildren.size();
1045         for (int i = 0; i < childCount; i++) {
1046             ExpandableNotificationRow child = mAttachedChildren.get(i);
1047             float childHeight;
1048             if (showingLowPriority) {
1049                 childHeight = child.getShowingLayout().getMinHeight(false /* likeGroupExpanded */);
1050             } else if (child.isExpanded(true /* allowOnKeyguard */)) {
1051                 childHeight = child.getMaxExpandHeight();
1052             } else {
1053                 childHeight = child.getShowingLayout().getMinHeight(
1054                         true /* likeGroupExpanded */);
1055             }
1056             if (i < maxAllowedVisibleChildren) {
1057                 float singleLineHeight = child.getShowingLayout().getMinHeight(
1058                         false /* likeGroupExpanded */);
1059                 child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight,
1060                         childHeight, fraction), false);
1061             } else {
1062                 child.setActualHeight((int) childHeight, false);
1063             }
1064         }
1065     }
1066 
1067     public float getGroupExpandFraction() {
1068         int visibleChildrenExpandedHeight = showingAsLowPriority() ? getMaxContentHeight()
1069                 : getVisibleChildrenExpandHeight();
1070         int minExpandHeight = getCollapsedHeight();
1071         float factor = (mActualHeight - minExpandHeight)
1072                 / (float) (visibleChildrenExpandedHeight - minExpandHeight);
1073         return Math.max(0.0f, Math.min(1.0f, factor));
1074     }
1075 
1076     private int getVisibleChildrenExpandHeight() {
1077         int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
1078                 + mNotificatonTopPadding + mDividerHeight;
1079         int visibleChildren = 0;
1080         int childCount = mAttachedChildren.size();
1081         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
1082         for (int i = 0; i < childCount; i++) {
1083             if (visibleChildren >= maxAllowedVisibleChildren) {
1084                 break;
1085             }
1086             ExpandableNotificationRow child = mAttachedChildren.get(i);
1087             float childHeight = child.isExpanded(true /* allowOnKeyguard */)
1088                     ? child.getMaxExpandHeight()
1089                     : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
1090             intrinsicHeight += childHeight;
1091             visibleChildren++;
1092         }
1093         return intrinsicHeight;
1094     }
1095 
1096     public int getMinHeight() {
1097         return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
1098     }
1099 
1100     public int getCollapsedHeight() {
1101         return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */),
1102                 false /* likeHighPriority */);
1103     }
1104 
1105     public int getCollapsedHeightWithoutHeader() {
1106         return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */),
1107                 false /* likeHighPriority */, 0);
1108     }
1109 
1110     /**
1111      * Get the minimum Height for this group.
1112      *
1113      * @param maxAllowedVisibleChildren the number of children that should be visible
1114      * @param likeHighPriority if the height should be calculated as if it were not low priority
1115      */
1116     private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) {
1117         return getMinHeight(maxAllowedVisibleChildren, likeHighPriority, mCurrentHeaderTranslation);
1118     }
1119 
1120     /**
1121      * Get the minimum Height for this group.
1122      *
1123      * @param maxAllowedVisibleChildren the number of children that should be visible
1124      * @param likeHighPriority if the height should be calculated as if it were not low priority
1125      * @param headerTranslation the translation amount of the header
1126      */
1127     private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority,
1128             int headerTranslation) {
1129         if (!likeHighPriority && showingAsLowPriority()) {
1130             return mNotificationHeaderLowPriority.getHeight();
1131         }
1132         int minExpandHeight = mNotificationHeaderMargin + headerTranslation;
1133         int visibleChildren = 0;
1134         boolean firstChild = true;
1135         int childCount = mAttachedChildren.size();
1136         for (int i = 0; i < childCount; i++) {
1137             if (visibleChildren >= maxAllowedVisibleChildren) {
1138                 break;
1139             }
1140             if (!firstChild) {
1141                 minExpandHeight += mChildPadding;
1142             } else {
1143                 firstChild = false;
1144             }
1145             ExpandableNotificationRow child = mAttachedChildren.get(i);
1146             minExpandHeight += child.getSingleLineView().getHeight();
1147             visibleChildren++;
1148         }
1149         minExpandHeight += mCollapsedBottompadding;
1150         return minExpandHeight;
1151     }
1152 
1153     public boolean showingAsLowPriority() {
1154         return mIsLowPriority && !mContainingNotification.isExpanded();
1155     }
1156 
1157     public void reInflateViews(OnClickListener listener, StatusBarNotification notification) {
1158         if (mNotificationHeader != null) {
1159             removeView(mNotificationHeader);
1160             mNotificationHeader = null;
1161         }
1162         if (mNotificationHeaderLowPriority != null) {
1163             removeView(mNotificationHeaderLowPriority);
1164             mNotificationHeaderLowPriority = null;
1165         }
1166         recreateNotificationHeader(listener, mIsConversation);
1167         initDimens();
1168         for (int i = 0; i < mDividers.size(); i++) {
1169             View prevDivider = mDividers.get(i);
1170             int index = indexOfChild(prevDivider);
1171             removeView(prevDivider);
1172             View divider = inflateDivider();
1173             addView(divider, index);
1174             mDividers.set(i, divider);
1175         }
1176         removeView(mOverflowNumber);
1177         mOverflowNumber = null;
1178         mGroupOverFlowState = null;
1179         updateGroupOverflow();
1180     }
1181 
1182     public void setUserLocked(boolean userLocked) {
1183         mUserLocked = userLocked;
1184         if (!mUserLocked) {
1185             updateHeaderVisibility(false /* animate */);
1186         }
1187         int childCount = mAttachedChildren.size();
1188         for (int i = 0; i < childCount; i++) {
1189             ExpandableNotificationRow child = mAttachedChildren.get(i);
1190             child.setUserLocked(userLocked && !showingAsLowPriority());
1191         }
1192         updateHeaderTouchability();
1193     }
1194 
1195     private void updateHeaderTouchability() {
1196         if (mNotificationHeader != null) {
1197             mNotificationHeader.setAcceptAllTouches(mChildrenExpanded || mUserLocked);
1198         }
1199     }
1200 
1201     public void onNotificationUpdated() {
1202         mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
1203                 mContainingNotification.getNotificationColor());
1204     }
1205 
1206     public int getPositionInLinearLayout(View childInGroup) {
1207         int position = mNotificationHeaderMargin + mCurrentHeaderTranslation
1208                 + mNotificatonTopPadding;
1209 
1210         for (int i = 0; i < mAttachedChildren.size(); i++) {
1211             ExpandableNotificationRow child = mAttachedChildren.get(i);
1212             boolean notGone = child.getVisibility() != View.GONE;
1213             if (notGone) {
1214                 position += mDividerHeight;
1215             }
1216             if (child == childInGroup) {
1217                 return position;
1218             }
1219             if (notGone) {
1220                 position += child.getIntrinsicHeight();
1221             }
1222         }
1223         return 0;
1224     }
1225 
1226     public void setShelfIconVisible(boolean iconVisible) {
1227         if (mNotificationHeaderWrapper != null) {
1228             NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader();
1229             if (header != null) {
1230                 header.getIcon().setForceHidden(iconVisible);
1231             }
1232         }
1233         if (mNotificationHeaderWrapperLowPriority != null) {
1234             NotificationHeaderView header
1235                     = mNotificationHeaderWrapperLowPriority.getNotificationHeader();
1236             if (header != null) {
1237                 header.getIcon().setForceHidden(iconVisible);
1238             }
1239         }
1240     }
1241 
1242     public void setClipBottomAmount(int clipBottomAmount) {
1243         mClipBottomAmount = clipBottomAmount;
1244         updateChildrenClipping();
1245     }
1246 
1247     public void setIsLowPriority(boolean isLowPriority) {
1248         mIsLowPriority = isLowPriority;
1249         if (mContainingNotification != null) { /* we're not yet set up yet otherwise */
1250             recreateLowPriorityHeader(null /* existingBuilder */, mIsConversation);
1251             updateHeaderVisibility(false /* animate */);
1252         }
1253         if (mUserLocked) {
1254             setUserLocked(mUserLocked);
1255         }
1256     }
1257 
1258     public NotificationHeaderView getVisibleHeader() {
1259         NotificationHeaderView header = mNotificationHeader;
1260         if (showingAsLowPriority()) {
1261             header = mNotificationHeaderLowPriority;
1262         }
1263         return header;
1264     }
1265 
1266     public void onExpansionChanged() {
1267         if (mIsLowPriority) {
1268             if (mUserLocked) {
1269                 setUserLocked(mUserLocked);
1270             }
1271             updateHeaderVisibility(true /* animate */);
1272         }
1273     }
1274 
1275     public float getIncreasedPaddingAmount() {
1276         if (showingAsLowPriority()) {
1277             return 0.0f;
1278         }
1279         return getGroupExpandFraction();
1280     }
1281 
1282     @VisibleForTesting
1283     public boolean isUserLocked() {
1284         return mUserLocked;
1285     }
1286 
1287     public void setCurrentBottomRoundness(float currentBottomRoundness) {
1288         boolean last = true;
1289         for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
1290             ExpandableNotificationRow child = mAttachedChildren.get(i);
1291             if (child.getVisibility() == View.GONE) {
1292                 continue;
1293             }
1294             float bottomRoundness = last ? currentBottomRoundness : 0.0f;
1295             child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
1296             last = false;
1297         }
1298     }
1299 
1300     public void setHeaderVisibleAmount(float headerVisibleAmount) {
1301         mHeaderVisibleAmount = headerVisibleAmount;
1302         mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader);
1303     }
1304 
1305     /**
1306      * Show a set of app opp icons in the layout.
1307      *
1308      * @param appOps which app ops to show
1309      */
1310     public void showAppOpsIcons(ArraySet<Integer> appOps) {
1311         if (mNotificationHeaderWrapper != null) {
1312             mNotificationHeaderWrapper.showAppOpsIcons(appOps);
1313         }
1314         if (mNotificationHeaderWrapperLowPriority != null) {
1315             mNotificationHeaderWrapperLowPriority.showAppOpsIcons(appOps);
1316         }
1317     }
1318 
1319     public void setRecentlyAudiblyAlerted(boolean audiblyAlertedRecently) {
1320         if (mNotificationHeaderWrapper != null) {
1321             mNotificationHeaderWrapper.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
1322         }
1323         if (mNotificationHeaderWrapperLowPriority != null) {
1324             mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
1325         }
1326     }
1327 }
1328