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