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
17package com.android.systemui.statusbar.stack;
18
19import android.app.Notification;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.graphics.drawable.ColorDrawable;
23import android.service.notification.StatusBarNotification;
24import android.util.AttributeSet;
25import android.view.LayoutInflater;
26import android.view.NotificationHeaderView;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.RemoteViews;
30import android.widget.TextView;
31
32import com.android.internal.annotations.VisibleForTesting;
33import com.android.systemui.R;
34import com.android.systemui.statusbar.CrossFadeHelper;
35import com.android.systemui.statusbar.ExpandableNotificationRow;
36import com.android.systemui.statusbar.NotificationHeaderUtil;
37import com.android.systemui.statusbar.notification.HybridGroupManager;
38import com.android.systemui.statusbar.notification.HybridNotificationView;
39import com.android.systemui.statusbar.notification.NotificationUtils;
40import com.android.systemui.statusbar.notification.NotificationViewWrapper;
41import com.android.systemui.statusbar.notification.VisualStabilityManager;
42
43import java.util.ArrayList;
44import java.util.List;
45
46/**
47 * A container containing child notifications
48 */
49public 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
96    public NotificationChildrenContainer(Context context) {
97        this(context, null);
98    }
99
100    public NotificationChildrenContainer(Context context, AttributeSet attrs) {
101        this(context, attrs, 0);
102    }
103
104    public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
105        this(context, attrs, defStyleAttr, 0);
106    }
107
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
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
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
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
221    public boolean hasOverlappingRendering() {
222        return false;
223    }
224
225    @Override
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     */
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