OSDN Git Service

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