1/*
2 * Copyright (C) 2014 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;
18
19import android.app.Notification;
20import android.app.PendingIntent;
21import android.app.RemoteInput;
22import android.content.Context;
23import android.graphics.Rect;
24import android.os.Build;
25import android.service.notification.StatusBarNotification;
26import android.util.AttributeSet;
27import android.view.NotificationHeaderView;
28import android.view.View;
29import android.view.ViewGroup;
30import android.view.ViewTreeObserver;
31import android.widget.FrameLayout;
32import android.widget.ImageView;
33
34import com.android.internal.annotations.VisibleForTesting;
35import com.android.internal.util.NotificationColorUtil;
36import com.android.systemui.R;
37import com.android.systemui.statusbar.notification.HybridNotificationView;
38import com.android.systemui.statusbar.notification.HybridGroupManager;
39import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
40import com.android.systemui.statusbar.notification.NotificationUtils;
41import com.android.systemui.statusbar.notification.NotificationViewWrapper;
42import com.android.systemui.statusbar.phone.NotificationGroupManager;
43import com.android.systemui.statusbar.policy.RemoteInputView;
44
45/**
46 * A frame layout containing the actual payload of the notification, including the contracted,
47 * expanded and heads up layout. This class is responsible for clipping the content and and
48 * switching between the expanded, contracted and the heads up view depending on its clipped size.
49 */
50public class NotificationContentView extends FrameLayout {
51
52    public static final int VISIBLE_TYPE_CONTRACTED = 0;
53    public static final int VISIBLE_TYPE_EXPANDED = 1;
54    public static final int VISIBLE_TYPE_HEADSUP = 2;
55    private static final int VISIBLE_TYPE_SINGLELINE = 3;
56    public static final int VISIBLE_TYPE_AMBIENT = 4;
57    private static final int VISIBLE_TYPE_AMBIENT_SINGLELINE = 5;
58    public static final int UNDEFINED = -1;
59
60    private final Rect mClipBounds = new Rect();
61    private final int mMinContractedHeight;
62    private final int mNotificationContentMarginEnd;
63
64    private View mContractedChild;
65    private View mExpandedChild;
66    private View mHeadsUpChild;
67    private HybridNotificationView mSingleLineView;
68    private View mAmbientChild;
69    private HybridNotificationView mAmbientSingleLineChild;
70
71    private RemoteInputView mExpandedRemoteInput;
72    private RemoteInputView mHeadsUpRemoteInput;
73
74    private NotificationViewWrapper mContractedWrapper;
75    private NotificationViewWrapper mExpandedWrapper;
76    private NotificationViewWrapper mHeadsUpWrapper;
77    private NotificationViewWrapper mAmbientWrapper;
78    private HybridGroupManager mHybridGroupManager;
79    private int mClipTopAmount;
80    private int mContentHeight;
81    private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
82    private boolean mDark;
83    private boolean mAnimate;
84    private boolean mIsHeadsUp;
85    private boolean mLegacy;
86    private boolean mIsChildInGroup;
87    private int mSmallHeight;
88    private int mHeadsUpHeight;
89    private int mNotificationMaxHeight;
90    private int mNotificationAmbientHeight;
91    private StatusBarNotification mStatusBarNotification;
92    private NotificationGroupManager mGroupManager;
93    private RemoteInputController mRemoteInputController;
94    private Runnable mExpandedVisibleListener;
95
96    private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
97            = new ViewTreeObserver.OnPreDrawListener() {
98        @Override
99        public boolean onPreDraw() {
100            // We need to post since we don't want the notification to animate on the very first
101            // frame
102            post(new Runnable() {
103                @Override
104                public void run() {
105                    mAnimate = true;
106                }
107            });
108            getViewTreeObserver().removeOnPreDrawListener(this);
109            return true;
110        }
111    };
112
113    private OnClickListener mExpandClickListener;
114    private boolean mBeforeN;
115    private boolean mExpandable;
116    private boolean mClipToActualHeight = true;
117    private ExpandableNotificationRow mContainingNotification;
118    /** The visible type at the start of a touch driven transformation */
119    private int mTransformationStartVisibleType;
120    /** The visible type at the start of an animation driven transformation */
121    private int mAnimationStartVisibleType = UNDEFINED;
122    private boolean mUserExpanding;
123    private int mSingleLineWidthIndention;
124    private boolean mForceSelectNextLayout = true;
125    private PendingIntent mPreviousExpandedRemoteInputIntent;
126    private PendingIntent mPreviousHeadsUpRemoteInputIntent;
127    private RemoteInputView mCachedExpandedRemoteInput;
128    private RemoteInputView mCachedHeadsUpRemoteInput;
129
130    private int mContentHeightAtAnimationStart = UNDEFINED;
131    private boolean mFocusOnVisibilityChange;
132    private boolean mHeadsUpAnimatingAway;
133    private boolean mIconsVisible;
134    private int mClipBottomAmount;
135    private boolean mIsLowPriority;
136    private boolean mIsContentExpandable;
137
138
139    public NotificationContentView(Context context, AttributeSet attrs) {
140        super(context, attrs);
141        mHybridGroupManager = new HybridGroupManager(getContext(), this);
142        mMinContractedHeight = getResources().getDimensionPixelSize(
143                R.dimen.min_notification_layout_height);
144        mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
145                com.android.internal.R.dimen.notification_content_margin_end);
146    }
147
148    public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
149            int ambientHeight) {
150        mSmallHeight = smallHeight;
151        mHeadsUpHeight = headsUpMaxHeight;
152        mNotificationMaxHeight = maxHeight;
153        mNotificationAmbientHeight = ambientHeight;
154    }
155
156    @Override
157    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
158        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
159        boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
160        boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
161        int maxSize = Integer.MAX_VALUE;
162        int width = MeasureSpec.getSize(widthMeasureSpec);
163        if (hasFixedHeight || isHeightLimited) {
164            maxSize = MeasureSpec.getSize(heightMeasureSpec);
165        }
166        int maxChildHeight = 0;
167        if (mExpandedChild != null) {
168            int size = Math.min(maxSize, mNotificationMaxHeight);
169            ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
170            boolean useExactly = false;
171            if (layoutParams.height >= 0) {
172                // An actual height is set
173                size = Math.min(maxSize, layoutParams.height);
174                useExactly = true;
175            }
176            int spec = size == Integer.MAX_VALUE
177                    ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
178                    : MeasureSpec.makeMeasureSpec(size, useExactly
179                            ? MeasureSpec.EXACTLY
180                            : MeasureSpec.AT_MOST);
181            mExpandedChild.measure(widthMeasureSpec, spec);
182            maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
183        }
184        if (mContractedChild != null) {
185            int heightSpec;
186            int size = Math.min(maxSize, mSmallHeight);
187            ViewGroup.LayoutParams layoutParams = mContractedChild.getLayoutParams();
188            boolean useExactly = false;
189            if (layoutParams.height >= 0) {
190                // An actual height is set
191                size = Math.min(size, layoutParams.height);
192                useExactly = true;
193            }
194            if (shouldContractedBeFixedSize() || useExactly) {
195                heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
196            } else {
197                heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
198            }
199            mContractedChild.measure(widthMeasureSpec, heightSpec);
200            int measuredHeight = mContractedChild.getMeasuredHeight();
201            if (measuredHeight < mMinContractedHeight) {
202                heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
203                mContractedChild.measure(widthMeasureSpec, heightSpec);
204            }
205            maxChildHeight = Math.max(maxChildHeight, measuredHeight);
206            if (updateContractedHeaderWidth()) {
207                mContractedChild.measure(widthMeasureSpec, heightSpec);
208            }
209            if (mExpandedChild != null
210                    && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
211                // the Expanded child is smaller then the collapsed. Let's remeasure it.
212                heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
213                        MeasureSpec.EXACTLY);
214                mExpandedChild.measure(widthMeasureSpec, heightSpec);
215            }
216        }
217        if (mHeadsUpChild != null) {
218            int size = Math.min(maxSize, mHeadsUpHeight);
219            ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
220            boolean useExactly = false;
221            if (layoutParams.height >= 0) {
222                // An actual height is set
223                size = Math.min(size, layoutParams.height);
224                useExactly = true;
225            }
226            mHeadsUpChild.measure(widthMeasureSpec,
227                    MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
228                            : MeasureSpec.AT_MOST));
229            maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
230        }
231        if (mSingleLineView != null) {
232            int singleLineWidthSpec = widthMeasureSpec;
233            if (mSingleLineWidthIndention != 0
234                    && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
235                singleLineWidthSpec = MeasureSpec.makeMeasureSpec(
236                        width - mSingleLineWidthIndention + mSingleLineView.getPaddingEnd(),
237                        MeasureSpec.EXACTLY);
238            }
239            mSingleLineView.measure(singleLineWidthSpec,
240                    MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST));
241            maxChildHeight = Math.max(maxChildHeight, mSingleLineView.getMeasuredHeight());
242        }
243        if (mAmbientChild != null) {
244            int size = Math.min(maxSize, mNotificationAmbientHeight);
245            ViewGroup.LayoutParams layoutParams = mAmbientChild.getLayoutParams();
246            boolean useExactly = false;
247            if (layoutParams.height >= 0) {
248                // An actual height is set
249                size = Math.min(size, layoutParams.height);
250                useExactly = true;
251            }
252            mAmbientChild.measure(widthMeasureSpec,
253                    MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
254                            : MeasureSpec.AT_MOST));
255            maxChildHeight = Math.max(maxChildHeight, mAmbientChild.getMeasuredHeight());
256        }
257        if (mAmbientSingleLineChild != null) {
258            int size = Math.min(maxSize, mNotificationAmbientHeight);
259            ViewGroup.LayoutParams layoutParams = mAmbientSingleLineChild.getLayoutParams();
260            boolean useExactly = false;
261            if (layoutParams.height >= 0) {
262                // An actual height is set
263                size = Math.min(size, layoutParams.height);
264                useExactly = true;
265            }
266            int ambientSingleLineWidthSpec = widthMeasureSpec;
267            if (mSingleLineWidthIndention != 0
268                    && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
269                ambientSingleLineWidthSpec = MeasureSpec.makeMeasureSpec(
270                        width - mSingleLineWidthIndention + mAmbientSingleLineChild.getPaddingEnd(),
271                        MeasureSpec.EXACTLY);
272            }
273            mAmbientSingleLineChild.measure(ambientSingleLineWidthSpec,
274                    MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
275                            : MeasureSpec.AT_MOST));
276            maxChildHeight = Math.max(maxChildHeight, mAmbientSingleLineChild.getMeasuredHeight());
277        }
278        int ownHeight = Math.min(maxChildHeight, maxSize);
279        setMeasuredDimension(width, ownHeight);
280    }
281
282    private boolean updateContractedHeaderWidth() {
283        // We need to update the expanded and the collapsed header to have exactly the same with to
284        // have the expand buttons laid out at the same location.
285        NotificationHeaderView contractedHeader = mContractedWrapper.getNotificationHeader();
286        if (contractedHeader != null) {
287            if (mExpandedChild != null
288                    && mExpandedWrapper.getNotificationHeader() != null) {
289                NotificationHeaderView expandedHeader = mExpandedWrapper.getNotificationHeader();
290                int expandedSize = expandedHeader.getMeasuredWidth()
291                        - expandedHeader.getPaddingEnd();
292                int collapsedSize = contractedHeader.getMeasuredWidth()
293                        - expandedHeader.getPaddingEnd();
294                if (expandedSize != collapsedSize) {
295                    int paddingEnd = contractedHeader.getMeasuredWidth() - expandedSize;
296                    contractedHeader.setPadding(
297                            contractedHeader.isLayoutRtl()
298                                    ? paddingEnd
299                                    : contractedHeader.getPaddingLeft(),
300                            contractedHeader.getPaddingTop(),
301                            contractedHeader.isLayoutRtl()
302                                    ? contractedHeader.getPaddingLeft()
303                                    : paddingEnd,
304                            contractedHeader.getPaddingBottom());
305                    contractedHeader.setShowWorkBadgeAtEnd(true);
306                    return true;
307                }
308            } else {
309                int paddingEnd = mNotificationContentMarginEnd;
310                if (contractedHeader.getPaddingEnd() != paddingEnd) {
311                    contractedHeader.setPadding(
312                            contractedHeader.isLayoutRtl()
313                                    ? paddingEnd
314                                    : contractedHeader.getPaddingLeft(),
315                            contractedHeader.getPaddingTop(),
316                            contractedHeader.isLayoutRtl()
317                                    ? contractedHeader.getPaddingLeft()
318                                    : paddingEnd,
319                            contractedHeader.getPaddingBottom());
320                    contractedHeader.setShowWorkBadgeAtEnd(false);
321                    return true;
322                }
323            }
324        }
325        return false;
326    }
327
328    private boolean shouldContractedBeFixedSize() {
329        return mBeforeN && mContractedWrapper instanceof NotificationCustomViewWrapper;
330    }
331
332    @Override
333    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
334        int previousHeight = 0;
335        if (mExpandedChild != null) {
336            previousHeight = mExpandedChild.getHeight();
337        }
338        super.onLayout(changed, left, top, right, bottom);
339        if (previousHeight != 0 && mExpandedChild.getHeight() != previousHeight) {
340            mContentHeightAtAnimationStart = previousHeight;
341        }
342        updateClipping();
343        invalidateOutline();
344        selectLayout(false /* animate */, mForceSelectNextLayout /* force */);
345        mForceSelectNextLayout = false;
346        updateExpandButtons(mExpandable);
347    }
348
349    @Override
350    protected void onAttachedToWindow() {
351        super.onAttachedToWindow();
352        updateVisibility();
353    }
354
355    public View getContractedChild() {
356        return mContractedChild;
357    }
358
359    public View getExpandedChild() {
360        return mExpandedChild;
361    }
362
363    public View getHeadsUpChild() {
364        return mHeadsUpChild;
365    }
366
367    public View getAmbientChild() {
368        return mAmbientChild;
369    }
370
371    public HybridNotificationView getAmbientSingleLineChild() {
372        return mAmbientSingleLineChild;
373    }
374
375    public void setContractedChild(View child) {
376        if (mContractedChild != null) {
377            mContractedChild.animate().cancel();
378            removeView(mContractedChild);
379        }
380        addView(child);
381        mContractedChild = child;
382        mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
383                mContainingNotification);
384        mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
385    }
386
387    public void setExpandedChild(View child) {
388        if (mExpandedChild != null) {
389            mPreviousExpandedRemoteInputIntent = null;
390            if (mExpandedRemoteInput != null) {
391                mExpandedRemoteInput.onNotificationUpdateOrReset();
392                if (mExpandedRemoteInput.isActive()) {
393                    mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent();
394                    mCachedExpandedRemoteInput = mExpandedRemoteInput;
395                    mExpandedRemoteInput.dispatchStartTemporaryDetach();
396                    ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
397                }
398            }
399            mExpandedChild.animate().cancel();
400            removeView(mExpandedChild);
401            mExpandedRemoteInput = null;
402        }
403        if (child == null) {
404            mExpandedChild = null;
405            mExpandedWrapper = null;
406            if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
407                mVisibleType = VISIBLE_TYPE_CONTRACTED;
408            }
409            if (mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED) {
410                mTransformationStartVisibleType = UNDEFINED;
411            }
412            return;
413        }
414        addView(child);
415        mExpandedChild = child;
416        mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
417                mContainingNotification);
418    }
419
420    public void setHeadsUpChild(View child) {
421        if (mHeadsUpChild != null) {
422            mPreviousHeadsUpRemoteInputIntent = null;
423            if (mHeadsUpRemoteInput != null) {
424                mHeadsUpRemoteInput.onNotificationUpdateOrReset();
425                if (mHeadsUpRemoteInput.isActive()) {
426                    mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent();
427                    mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
428                    mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
429                    ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
430                }
431            }
432            mHeadsUpChild.animate().cancel();
433            removeView(mHeadsUpChild);
434            mHeadsUpRemoteInput = null;
435        }
436        if (child == null) {
437            mHeadsUpChild = null;
438            mHeadsUpWrapper = null;
439            if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
440                mVisibleType = VISIBLE_TYPE_CONTRACTED;
441            }
442            if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
443                mTransformationStartVisibleType = UNDEFINED;
444            }
445            return;
446        }
447        addView(child);
448        mHeadsUpChild = child;
449        mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
450                mContainingNotification);
451    }
452
453    public void setAmbientChild(View child) {
454        if (mAmbientChild != null) {
455            mAmbientChild.animate().cancel();
456            removeView(mAmbientChild);
457        }
458        if (child == null) {
459            return;
460        }
461        addView(child);
462        mAmbientChild = child;
463        mAmbientWrapper = NotificationViewWrapper.wrap(getContext(), child,
464                mContainingNotification);
465    }
466
467    @Override
468    protected void onVisibilityChanged(View changedView, int visibility) {
469        super.onVisibilityChanged(changedView, visibility);
470        updateVisibility();
471    }
472
473    private void updateVisibility() {
474        setVisible(isShown());
475    }
476
477    @Override
478    protected void onDetachedFromWindow() {
479        super.onDetachedFromWindow();
480        getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
481    }
482
483    private void setVisible(final boolean isVisible) {
484        if (isVisible) {
485            // This call can happen multiple times, but removing only removes a single one.
486            // We therefore need to remove the old one.
487            getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
488            // We only animate if we are drawn at least once, otherwise the view might animate when
489            // it's shown the first time
490            getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
491        } else {
492            getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
493            mAnimate = false;
494        }
495    }
496
497    private void focusExpandButtonIfNecessary() {
498        if (mFocusOnVisibilityChange) {
499            NotificationHeaderView header = getVisibleNotificationHeader();
500            if (header != null) {
501                ImageView expandButton = header.getExpandButton();
502                if (expandButton != null) {
503                    expandButton.requestAccessibilityFocus();
504                }
505            }
506            mFocusOnVisibilityChange = false;
507        }
508    }
509
510    public void setContentHeight(int contentHeight) {
511        mContentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());
512        selectLayout(mAnimate /* animate */, false /* force */);
513
514        int minHeightHint = getMinContentHeightHint();
515
516        NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
517        if (wrapper != null) {
518            wrapper.setContentHeight(mContentHeight, minHeightHint);
519        }
520
521        wrapper = getVisibleWrapper(mTransformationStartVisibleType);
522        if (wrapper != null) {
523            wrapper.setContentHeight(mContentHeight, minHeightHint);
524        }
525
526        updateClipping();
527        invalidateOutline();
528    }
529
530    /**
531     * @return the minimum apparent height that the wrapper should allow for the purpose
532     *         of aligning elements at the bottom edge. If this is larger than the content
533     *         height, the notification is clipped instead of being further shrunk.
534     */
535    private int getMinContentHeightHint() {
536        if (mIsChildInGroup && isVisibleOrTransitioning(VISIBLE_TYPE_SINGLELINE)) {
537            return mContext.getResources().getDimensionPixelSize(
538                        com.android.internal.R.dimen.notification_action_list_height);
539        }
540
541        // Transition between heads-up & expanded, or pinned.
542        if (mHeadsUpChild != null && mExpandedChild != null) {
543            boolean transitioningBetweenHunAndExpanded =
544                    isTransitioningFromTo(VISIBLE_TYPE_HEADSUP, VISIBLE_TYPE_EXPANDED) ||
545                    isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP);
546            boolean pinned = !isVisibleOrTransitioning(VISIBLE_TYPE_CONTRACTED)
547                    && (mIsHeadsUp || mHeadsUpAnimatingAway)
548                    && !mContainingNotification.isOnKeyguard();
549            if (transitioningBetweenHunAndExpanded || pinned) {
550                return Math.min(mHeadsUpChild.getHeight(), mExpandedChild.getHeight());
551            }
552        }
553
554        // Size change of the expanded version
555        if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart >= 0
556                && mExpandedChild != null) {
557            return Math.min(mContentHeightAtAnimationStart, mExpandedChild.getHeight());
558        }
559
560        int hint;
561        if (mAmbientChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_AMBIENT)) {
562            hint = mAmbientChild.getHeight();
563        } else if (mAmbientSingleLineChild != null && isVisibleOrTransitioning(
564                VISIBLE_TYPE_AMBIENT_SINGLELINE)) {
565            hint = mAmbientSingleLineChild.getHeight();
566        } else if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
567            hint = mHeadsUpChild.getHeight();
568        } else if (mExpandedChild != null) {
569            hint = mExpandedChild.getHeight();
570        } else {
571            hint = mContractedChild.getHeight() + mContext.getResources().getDimensionPixelSize(
572                    com.android.internal.R.dimen.notification_action_list_height);
573        }
574
575        if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) {
576            hint = Math.min(hint, mExpandedChild.getHeight());
577        }
578        return hint;
579    }
580
581    private boolean isTransitioningFromTo(int from, int to) {
582        return (mTransformationStartVisibleType == from || mAnimationStartVisibleType == from)
583                && mVisibleType == to;
584    }
585
586    private boolean isVisibleOrTransitioning(int type) {
587        return mVisibleType == type || mTransformationStartVisibleType == type
588                || mAnimationStartVisibleType == type;
589    }
590
591    private void updateContentTransformation() {
592        int visibleType = calculateVisibleType();
593        if (visibleType != mVisibleType) {
594            // A new transformation starts
595            mTransformationStartVisibleType = mVisibleType;
596            final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
597            final TransformableView hiddenView = getTransformableViewForVisibleType(
598                    mTransformationStartVisibleType);
599            shownView.transformFrom(hiddenView, 0.0f);
600            getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
601            hiddenView.transformTo(shownView, 0.0f);
602            mVisibleType = visibleType;
603            updateBackgroundColor(true /* animate */);
604        }
605        if (mForceSelectNextLayout) {
606            forceUpdateVisibilities();
607        }
608        if (mTransformationStartVisibleType != UNDEFINED
609                && mVisibleType != mTransformationStartVisibleType
610                && getViewForVisibleType(mTransformationStartVisibleType) != null) {
611            final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType);
612            final TransformableView hiddenView = getTransformableViewForVisibleType(
613                    mTransformationStartVisibleType);
614            float transformationAmount = calculateTransformationAmount();
615            shownView.transformFrom(hiddenView, transformationAmount);
616            hiddenView.transformTo(shownView, transformationAmount);
617            updateBackgroundTransformation(transformationAmount);
618        } else {
619            updateViewVisibilities(visibleType);
620            updateBackgroundColor(false);
621        }
622    }
623
624    private void updateBackgroundTransformation(float transformationAmount) {
625        int endColor = getBackgroundColor(mVisibleType);
626        int startColor = getBackgroundColor(mTransformationStartVisibleType);
627        if (endColor != startColor) {
628            if (startColor == 0) {
629                startColor = mContainingNotification.getBackgroundColorWithoutTint();
630            }
631            if (endColor == 0) {
632                endColor = mContainingNotification.getBackgroundColorWithoutTint();
633            }
634            endColor = NotificationUtils.interpolateColors(startColor, endColor,
635                    transformationAmount);
636        }
637        mContainingNotification.updateBackgroundAlpha(transformationAmount);
638        mContainingNotification.setContentBackground(endColor, false, this);
639    }
640
641    private float calculateTransformationAmount() {
642        int startHeight = getViewForVisibleType(mTransformationStartVisibleType).getHeight();
643        int endHeight = getViewForVisibleType(mVisibleType).getHeight();
644        int progress = Math.abs(mContentHeight - startHeight);
645        int totalDistance = Math.abs(endHeight - startHeight);
646        float amount = (float) progress / (float) totalDistance;
647        return Math.min(1.0f, amount);
648    }
649
650    public int getContentHeight() {
651        return mContentHeight;
652    }
653
654    public int getMaxHeight() {
655        if (mContainingNotification.isShowingAmbient()) {
656            return getShowingAmbientView().getHeight();
657        } else if (mExpandedChild != null) {
658            return mExpandedChild.getHeight();
659        } else if (mIsHeadsUp && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) {
660            return mHeadsUpChild.getHeight();
661        }
662        return mContractedChild.getHeight();
663    }
664
665    public int getMinHeight() {
666        return getMinHeight(false /* likeGroupExpanded */);
667    }
668
669    public int getMinHeight(boolean likeGroupExpanded) {
670        if (mContainingNotification.isShowingAmbient()) {
671            return getShowingAmbientView().getHeight();
672        } else if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
673            return mContractedChild.getHeight();
674        } else {
675            return mSingleLineView.getHeight();
676        }
677    }
678
679    public View getShowingAmbientView() {
680        View v = mIsChildInGroup ? mAmbientSingleLineChild : mAmbientChild;
681        if (v != null) {
682            return v;
683        } else {
684            return mContractedChild;
685        }
686    }
687
688    private boolean isGroupExpanded() {
689        return mGroupManager.isGroupExpanded(mStatusBarNotification);
690    }
691
692    public void setClipTopAmount(int clipTopAmount) {
693        mClipTopAmount = clipTopAmount;
694        updateClipping();
695    }
696
697
698    public void setClipBottomAmount(int clipBottomAmount) {
699        mClipBottomAmount = clipBottomAmount;
700        updateClipping();
701    }
702
703    @Override
704    public void setTranslationY(float translationY) {
705        super.setTranslationY(translationY);
706        updateClipping();
707    }
708
709    private void updateClipping() {
710        if (mClipToActualHeight) {
711            int top = (int) (mClipTopAmount - getTranslationY());
712            int bottom = (int) (mContentHeight - mClipBottomAmount - getTranslationY());
713            bottom = Math.max(top, bottom);
714            mClipBounds.set(0, top, getWidth(), bottom);
715            setClipBounds(mClipBounds);
716        } else {
717            setClipBounds(null);
718        }
719    }
720
721    public void setClipToActualHeight(boolean clipToActualHeight) {
722        mClipToActualHeight = clipToActualHeight;
723        updateClipping();
724    }
725
726    private void selectLayout(boolean animate, boolean force) {
727        if (mContractedChild == null) {
728            return;
729        }
730        if (mUserExpanding) {
731            updateContentTransformation();
732        } else {
733            int visibleType = calculateVisibleType();
734            boolean changedType = visibleType != mVisibleType;
735            if (changedType || force) {
736                View visibleView = getViewForVisibleType(visibleType);
737                if (visibleView != null) {
738                    visibleView.setVisibility(VISIBLE);
739                    transferRemoteInputFocus(visibleType);
740                }
741
742                if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
743                        || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
744                        || (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
745                        || visibleType == VISIBLE_TYPE_CONTRACTED)) {
746                    animateToVisibleType(visibleType);
747                } else {
748                    updateViewVisibilities(visibleType);
749                }
750                mVisibleType = visibleType;
751                if (changedType) {
752                    focusExpandButtonIfNecessary();
753                }
754                NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
755                if (visibleWrapper != null) {
756                    visibleWrapper.setContentHeight(mContentHeight, getMinContentHeightHint());
757                }
758                updateBackgroundColor(animate);
759            }
760        }
761    }
762
763    private void forceUpdateVisibilities() {
764        forceUpdateVisibility(VISIBLE_TYPE_CONTRACTED, mContractedChild, mContractedWrapper);
765        forceUpdateVisibility(VISIBLE_TYPE_EXPANDED, mExpandedChild, mExpandedWrapper);
766        forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
767        forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
768        forceUpdateVisibility(VISIBLE_TYPE_AMBIENT, mAmbientChild, mAmbientWrapper);
769        forceUpdateVisibility(VISIBLE_TYPE_AMBIENT_SINGLELINE, mAmbientSingleLineChild,
770                mAmbientSingleLineChild);
771        fireExpandedVisibleListenerIfVisible();
772        // forceUpdateVisibilities cancels outstanding animations without updating the
773        // mAnimationStartVisibleType. Do so here instead.
774        mAnimationStartVisibleType = UNDEFINED;
775    }
776
777    private void fireExpandedVisibleListenerIfVisible() {
778        if (mExpandedVisibleListener != null && mExpandedChild != null && isShown()
779                && mExpandedChild.getVisibility() == VISIBLE) {
780            Runnable listener = mExpandedVisibleListener;
781            mExpandedVisibleListener = null;
782            listener.run();
783        }
784    }
785
786    private void forceUpdateVisibility(int type, View view, TransformableView wrapper) {
787        if (view == null) {
788            return;
789        }
790        boolean visible = mVisibleType == type
791                || mTransformationStartVisibleType == type;
792        if (!visible) {
793            view.setVisibility(INVISIBLE);
794        } else {
795            wrapper.setVisible(true);
796        }
797    }
798
799    public void updateBackgroundColor(boolean animate) {
800        int customBackgroundColor = getBackgroundColor(mVisibleType);
801        mContainingNotification.resetBackgroundAlpha();
802        mContainingNotification.setContentBackground(customBackgroundColor, animate, this);
803    }
804
805    public int getVisibleType() {
806        return mVisibleType;
807    }
808
809    public int getBackgroundColorForExpansionState() {
810        // When expanding or user locked we want the new type, when collapsing we want
811        // the original type
812        final int visibleType = (mContainingNotification.isGroupExpanded()
813                || mContainingNotification.isUserLocked())
814                        ? calculateVisibleType()
815                        : getVisibleType();
816        return getBackgroundColor(visibleType);
817    }
818
819    public int getBackgroundColor(int visibleType) {
820        NotificationViewWrapper currentVisibleWrapper = getVisibleWrapper(visibleType);
821        int customBackgroundColor = 0;
822        if (currentVisibleWrapper != null) {
823            customBackgroundColor = currentVisibleWrapper.getCustomBackgroundColor();
824        }
825        return customBackgroundColor;
826    }
827
828    private void updateViewVisibilities(int visibleType) {
829        updateViewVisibility(visibleType, VISIBLE_TYPE_CONTRACTED,
830                mContractedChild, mContractedWrapper);
831        updateViewVisibility(visibleType, VISIBLE_TYPE_EXPANDED,
832                mExpandedChild, mExpandedWrapper);
833        updateViewVisibility(visibleType, VISIBLE_TYPE_HEADSUP,
834                mHeadsUpChild, mHeadsUpWrapper);
835        updateViewVisibility(visibleType, VISIBLE_TYPE_SINGLELINE,
836                mSingleLineView, mSingleLineView);
837        updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT,
838                mAmbientChild, mAmbientWrapper);
839        updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT_SINGLELINE,
840                mAmbientSingleLineChild, mAmbientSingleLineChild);
841        fireExpandedVisibleListenerIfVisible();
842        // updateViewVisibilities cancels outstanding animations without updating the
843        // mAnimationStartVisibleType. Do so here instead.
844        mAnimationStartVisibleType = UNDEFINED;
845    }
846
847    private void updateViewVisibility(int visibleType, int type, View view,
848            TransformableView wrapper) {
849        if (view != null) {
850            wrapper.setVisible(visibleType == type);
851        }
852    }
853
854    private void animateToVisibleType(int visibleType) {
855        final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
856        final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
857        if (shownView == hiddenView || hiddenView == null) {
858            shownView.setVisible(true);
859            return;
860        }
861        mAnimationStartVisibleType = mVisibleType;
862        shownView.transformFrom(hiddenView);
863        getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
864        hiddenView.transformTo(shownView, new Runnable() {
865            @Override
866            public void run() {
867                if (hiddenView != getTransformableViewForVisibleType(mVisibleType)) {
868                    hiddenView.setVisible(false);
869                }
870                mAnimationStartVisibleType = UNDEFINED;
871            }
872        });
873        fireExpandedVisibleListenerIfVisible();
874    }
875
876    private void transferRemoteInputFocus(int visibleType) {
877        if (visibleType == VISIBLE_TYPE_HEADSUP
878                && mHeadsUpRemoteInput != null
879                && (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive())) {
880            mHeadsUpRemoteInput.stealFocusFrom(mExpandedRemoteInput);
881        }
882        if (visibleType == VISIBLE_TYPE_EXPANDED
883                && mExpandedRemoteInput != null
884                && (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive())) {
885            mExpandedRemoteInput.stealFocusFrom(mHeadsUpRemoteInput);
886        }
887    }
888
889    /**
890     * @param visibleType one of the static enum types in this view
891     * @return the corresponding transformable view according to the given visible type
892     */
893    private TransformableView getTransformableViewForVisibleType(int visibleType) {
894        switch (visibleType) {
895            case VISIBLE_TYPE_EXPANDED:
896                return mExpandedWrapper;
897            case VISIBLE_TYPE_HEADSUP:
898                return mHeadsUpWrapper;
899            case VISIBLE_TYPE_SINGLELINE:
900                return mSingleLineView;
901            case VISIBLE_TYPE_AMBIENT:
902                return mAmbientWrapper;
903            case VISIBLE_TYPE_AMBIENT_SINGLELINE:
904                return mAmbientSingleLineChild;
905            default:
906                return mContractedWrapper;
907        }
908    }
909
910    /**
911     * @param visibleType one of the static enum types in this view
912     * @return the corresponding view according to the given visible type
913     */
914    private View getViewForVisibleType(int visibleType) {
915        switch (visibleType) {
916            case VISIBLE_TYPE_EXPANDED:
917                return mExpandedChild;
918            case VISIBLE_TYPE_HEADSUP:
919                return mHeadsUpChild;
920            case VISIBLE_TYPE_SINGLELINE:
921                return mSingleLineView;
922            case VISIBLE_TYPE_AMBIENT:
923                return mAmbientChild;
924            case VISIBLE_TYPE_AMBIENT_SINGLELINE:
925                return mAmbientSingleLineChild;
926            default:
927                return mContractedChild;
928        }
929    }
930
931    public NotificationViewWrapper getVisibleWrapper(int visibleType) {
932        switch (visibleType) {
933            case VISIBLE_TYPE_EXPANDED:
934                return mExpandedWrapper;
935            case VISIBLE_TYPE_HEADSUP:
936                return mHeadsUpWrapper;
937            case VISIBLE_TYPE_CONTRACTED:
938                return mContractedWrapper;
939            case VISIBLE_TYPE_AMBIENT:
940                return mAmbientWrapper;
941            default:
942                return null;
943        }
944    }
945
946    /**
947     * @return one of the static enum types in this view, calculated form the current state
948     */
949    public int calculateVisibleType() {
950        if (mContainingNotification.isShowingAmbient()) {
951            if (mIsChildInGroup && mAmbientSingleLineChild != null) {
952                return VISIBLE_TYPE_AMBIENT_SINGLELINE;
953            } else if (mAmbientChild != null) {
954                return VISIBLE_TYPE_AMBIENT;
955            } else {
956                return VISIBLE_TYPE_CONTRACTED;
957            }
958        }
959        if (mUserExpanding) {
960            int height = !mIsChildInGroup || isGroupExpanded()
961                    || mContainingNotification.isExpanded(true /* allowOnKeyguard */)
962                    ? mContainingNotification.getMaxContentHeight()
963                    : mContainingNotification.getShowingLayout().getMinHeight();
964            if (height == 0) {
965                height = mContentHeight;
966            }
967            int expandedVisualType = getVisualTypeForHeight(height);
968            int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
969                    ? VISIBLE_TYPE_SINGLELINE
970                    : getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
971            return mTransformationStartVisibleType == collapsedVisualType
972                    ? expandedVisualType
973                    : collapsedVisualType;
974        }
975        int intrinsicHeight = mContainingNotification.getIntrinsicHeight();
976        int viewHeight = mContentHeight;
977        if (intrinsicHeight != 0) {
978            // the intrinsicHeight might be 0 because it was just reset.
979            viewHeight = Math.min(mContentHeight, intrinsicHeight);
980        }
981        return getVisualTypeForHeight(viewHeight);
982    }
983
984    private int getVisualTypeForHeight(float viewHeight) {
985        boolean noExpandedChild = mExpandedChild == null;
986        if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
987            return VISIBLE_TYPE_EXPANDED;
988        }
989        if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
990            return VISIBLE_TYPE_SINGLELINE;
991        }
992
993        if ((mIsHeadsUp || mHeadsUpAnimatingAway) && mHeadsUpChild != null
994                && !mContainingNotification.isOnKeyguard()) {
995            if (viewHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
996                return VISIBLE_TYPE_HEADSUP;
997            } else {
998                return VISIBLE_TYPE_EXPANDED;
999            }
1000        } else {
1001            if (noExpandedChild || (viewHeight <= mContractedChild.getHeight()
1002                    && (!mIsChildInGroup || isGroupExpanded()
1003                            || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
1004                return VISIBLE_TYPE_CONTRACTED;
1005            } else {
1006                return VISIBLE_TYPE_EXPANDED;
1007            }
1008        }
1009    }
1010
1011    public boolean isContentExpandable() {
1012        return mIsContentExpandable;
1013    }
1014
1015    public void setDark(boolean dark, boolean fade, long delay) {
1016        if (mContractedChild == null) {
1017            return;
1018        }
1019        mDark = dark;
1020        if (mVisibleType == VISIBLE_TYPE_CONTRACTED || !dark) {
1021            mContractedWrapper.setDark(dark, fade, delay);
1022        }
1023        if (mVisibleType == VISIBLE_TYPE_EXPANDED || (mExpandedChild != null && !dark)) {
1024            mExpandedWrapper.setDark(dark, fade, delay);
1025        }
1026        if (mVisibleType == VISIBLE_TYPE_HEADSUP || (mHeadsUpChild != null && !dark)) {
1027            mHeadsUpWrapper.setDark(dark, fade, delay);
1028        }
1029        if (mSingleLineView != null && (mVisibleType == VISIBLE_TYPE_SINGLELINE || !dark)) {
1030            mSingleLineView.setDark(dark, fade, delay);
1031        }
1032        selectLayout(!dark && fade /* animate */, false /* force */);
1033    }
1034
1035    public void setHeadsUp(boolean headsUp) {
1036        mIsHeadsUp = headsUp;
1037        selectLayout(false /* animate */, true /* force */);
1038        updateExpandButtons(mExpandable);
1039    }
1040
1041    @Override
1042    public boolean hasOverlappingRendering() {
1043
1044        // This is not really true, but good enough when fading from the contracted to the expanded
1045        // layout, and saves us some layers.
1046        return false;
1047    }
1048
1049    public void setLegacy(boolean legacy) {
1050        mLegacy = legacy;
1051        updateLegacy();
1052    }
1053
1054    private void updateLegacy() {
1055        if (mContractedChild != null) {
1056            mContractedWrapper.setLegacy(mLegacy);
1057        }
1058        if (mExpandedChild != null) {
1059            mExpandedWrapper.setLegacy(mLegacy);
1060        }
1061        if (mHeadsUpChild != null) {
1062            mHeadsUpWrapper.setLegacy(mLegacy);
1063        }
1064    }
1065
1066    public void setIsChildInGroup(boolean isChildInGroup) {
1067        mIsChildInGroup = isChildInGroup;
1068        if (mContractedChild != null) {
1069            mContractedWrapper.setIsChildInGroup(mIsChildInGroup);
1070        }
1071        if (mExpandedChild != null) {
1072            mExpandedWrapper.setIsChildInGroup(mIsChildInGroup);
1073        }
1074        if (mHeadsUpChild != null) {
1075            mHeadsUpWrapper.setIsChildInGroup(mIsChildInGroup);
1076        }
1077        if (mAmbientChild != null) {
1078            mAmbientWrapper.setIsChildInGroup(mIsChildInGroup);
1079        }
1080        updateAllSingleLineViews();
1081    }
1082
1083    public void onNotificationUpdated(NotificationData.Entry entry) {
1084        mStatusBarNotification = entry.notification;
1085        mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
1086        updateAllSingleLineViews();
1087        if (mContractedChild != null) {
1088            mContractedWrapper.onContentUpdated(entry.row);
1089        }
1090        if (mExpandedChild != null) {
1091            mExpandedWrapper.onContentUpdated(entry.row);
1092        }
1093        if (mHeadsUpChild != null) {
1094            mHeadsUpWrapper.onContentUpdated(entry.row);
1095        }
1096        if (mAmbientChild != null) {
1097            mAmbientWrapper.onContentUpdated(entry.row);
1098        }
1099        applyRemoteInput(entry);
1100        updateLegacy();
1101        mForceSelectNextLayout = true;
1102        setDark(mDark, false /* animate */, 0 /* delay */);
1103        mPreviousExpandedRemoteInputIntent = null;
1104        mPreviousHeadsUpRemoteInputIntent = null;
1105    }
1106
1107    private void updateAllSingleLineViews() {
1108        updateSingleLineView();
1109        updateAmbientSingleLineView();
1110    }
1111    private void updateSingleLineView() {
1112        if (mIsChildInGroup) {
1113            mSingleLineView = mHybridGroupManager.bindFromNotification(
1114                    mSingleLineView, mStatusBarNotification.getNotification());
1115        } else if (mSingleLineView != null) {
1116            removeView(mSingleLineView);
1117            mSingleLineView = null;
1118        }
1119    }
1120
1121    private void updateAmbientSingleLineView() {
1122        if (mIsChildInGroup) {
1123            mAmbientSingleLineChild = mHybridGroupManager.bindAmbientFromNotification(
1124                    mAmbientSingleLineChild, mStatusBarNotification.getNotification());
1125        } else if (mAmbientSingleLineChild != null) {
1126            removeView(mAmbientSingleLineChild);
1127            mAmbientSingleLineChild = null;
1128        }
1129    }
1130
1131    private void applyRemoteInput(final NotificationData.Entry entry) {
1132        if (mRemoteInputController == null) {
1133            return;
1134        }
1135
1136        boolean hasRemoteInput = false;
1137
1138        Notification.Action[] actions = entry.notification.getNotification().actions;
1139        if (actions != null) {
1140            for (Notification.Action a : actions) {
1141                if (a.getRemoteInputs() != null) {
1142                    for (RemoteInput ri : a.getRemoteInputs()) {
1143                        if (ri.getAllowFreeFormInput()) {
1144                            hasRemoteInput = true;
1145                            break;
1146                        }
1147                    }
1148                }
1149            }
1150        }
1151
1152        View bigContentView = mExpandedChild;
1153        if (bigContentView != null) {
1154            mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
1155                    mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput,
1156                    mExpandedWrapper);
1157        } else {
1158            mExpandedRemoteInput = null;
1159        }
1160        if (mCachedExpandedRemoteInput != null
1161                && mCachedExpandedRemoteInput != mExpandedRemoteInput) {
1162            // We had a cached remote input but didn't reuse it. Clean up required.
1163            mCachedExpandedRemoteInput.dispatchFinishTemporaryDetach();
1164        }
1165        mCachedExpandedRemoteInput = null;
1166
1167        View headsUpContentView = mHeadsUpChild;
1168        if (headsUpContentView != null) {
1169            mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
1170                    mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput, mHeadsUpWrapper);
1171        } else {
1172            mHeadsUpRemoteInput = null;
1173        }
1174        if (mCachedHeadsUpRemoteInput != null
1175                && mCachedHeadsUpRemoteInput != mHeadsUpRemoteInput) {
1176            // We had a cached remote input but didn't reuse it. Clean up required.
1177            mCachedHeadsUpRemoteInput.dispatchFinishTemporaryDetach();
1178        }
1179        mCachedHeadsUpRemoteInput = null;
1180    }
1181
1182    private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
1183            boolean hasRemoteInput, PendingIntent existingPendingIntent,
1184            RemoteInputView cachedView, NotificationViewWrapper wrapper) {
1185        View actionContainerCandidate = view.findViewById(
1186                com.android.internal.R.id.actions_container);
1187        if (actionContainerCandidate instanceof FrameLayout) {
1188            RemoteInputView existing = (RemoteInputView)
1189                    view.findViewWithTag(RemoteInputView.VIEW_TAG);
1190
1191            if (existing != null) {
1192                existing.onNotificationUpdateOrReset();
1193            }
1194
1195            if (existing == null && hasRemoteInput) {
1196                ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
1197                if (cachedView == null) {
1198                    RemoteInputView riv = RemoteInputView.inflate(
1199                            mContext, actionContainer, entry, mRemoteInputController);
1200
1201                    riv.setVisibility(View.INVISIBLE);
1202                    actionContainer.addView(riv, new LayoutParams(
1203                            ViewGroup.LayoutParams.MATCH_PARENT,
1204                            ViewGroup.LayoutParams.MATCH_PARENT)
1205                    );
1206                    existing = riv;
1207                } else {
1208                    actionContainer.addView(cachedView);
1209                    cachedView.dispatchFinishTemporaryDetach();
1210                    cachedView.requestFocus();
1211                    existing = cachedView;
1212                }
1213            }
1214            if (hasRemoteInput) {
1215                int color = entry.notification.getNotification().color;
1216                if (color == Notification.COLOR_DEFAULT) {
1217                    color = mContext.getColor(R.color.default_remote_input_background);
1218                }
1219                existing.setBackgroundColor(NotificationColorUtil.ensureTextBackgroundColor(color,
1220                        mContext.getColor(R.color.remote_input_text_enabled),
1221                        mContext.getColor(R.color.remote_input_hint)));
1222
1223                existing.setWrapper(wrapper);
1224
1225                if (existingPendingIntent != null || existing.isActive()) {
1226                    // The current action could be gone, or the pending intent no longer valid.
1227                    // If we find a matching action in the new notification, focus, otherwise close.
1228                    Notification.Action[] actions = entry.notification.getNotification().actions;
1229                    if (existingPendingIntent != null) {
1230                        existing.setPendingIntent(existingPendingIntent);
1231                    }
1232                    if (existing.updatePendingIntentFromActions(actions)) {
1233                        if (!existing.isActive()) {
1234                            existing.focus();
1235                        }
1236                    } else {
1237                        if (existing.isActive()) {
1238                            existing.close();
1239                        }
1240                    }
1241                }
1242            }
1243            return existing;
1244        }
1245        return null;
1246    }
1247
1248    public void closeRemoteInput() {
1249        if (mHeadsUpRemoteInput != null) {
1250            mHeadsUpRemoteInput.close();
1251        }
1252        if (mExpandedRemoteInput != null) {
1253            mExpandedRemoteInput.close();
1254        }
1255    }
1256
1257    public void setGroupManager(NotificationGroupManager groupManager) {
1258        mGroupManager = groupManager;
1259    }
1260
1261    public void setRemoteInputController(RemoteInputController r) {
1262        mRemoteInputController = r;
1263    }
1264
1265    public void setExpandClickListener(OnClickListener expandClickListener) {
1266        mExpandClickListener = expandClickListener;
1267    }
1268
1269    public void updateExpandButtons(boolean expandable) {
1270        mExpandable = expandable;
1271        // if the expanded child has the same height as the collapsed one we hide it.
1272        if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
1273            if ((!mIsHeadsUp && !mHeadsUpAnimatingAway)
1274                    || mHeadsUpChild == null || mContainingNotification.isOnKeyguard()) {
1275                if (mExpandedChild.getHeight() <= mContractedChild.getHeight()) {
1276                    expandable = false;
1277                }
1278            } else if (mExpandedChild.getHeight() <= mHeadsUpChild.getHeight()) {
1279                expandable = false;
1280            }
1281        }
1282        if (mExpandedChild != null) {
1283            mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
1284        }
1285        if (mContractedChild != null) {
1286            mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
1287        }
1288        if (mHeadsUpChild != null) {
1289            mHeadsUpWrapper.updateExpandability(expandable,  mExpandClickListener);
1290        }
1291        mIsContentExpandable = expandable;
1292    }
1293
1294    public NotificationHeaderView getNotificationHeader() {
1295        NotificationHeaderView header = null;
1296        if (mContractedChild != null) {
1297            header = mContractedWrapper.getNotificationHeader();
1298        }
1299        if (header == null && mExpandedChild != null) {
1300            header = mExpandedWrapper.getNotificationHeader();
1301        }
1302        if (header == null && mHeadsUpChild != null) {
1303            header = mHeadsUpWrapper.getNotificationHeader();
1304        }
1305        if (header == null && mAmbientChild != null) {
1306            header = mAmbientWrapper.getNotificationHeader();
1307        }
1308        return header;
1309    }
1310
1311    public NotificationHeaderView getVisibleNotificationHeader() {
1312        NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
1313        return wrapper == null ? null : wrapper.getNotificationHeader();
1314    }
1315
1316    public void setContainingNotification(ExpandableNotificationRow containingNotification) {
1317        mContainingNotification = containingNotification;
1318    }
1319
1320    public void requestSelectLayout(boolean needsAnimation) {
1321        selectLayout(needsAnimation, false);
1322    }
1323
1324    public void reInflateViews() {
1325        if (mIsChildInGroup && mSingleLineView != null) {
1326            removeView(mSingleLineView);
1327            mSingleLineView = null;
1328            updateAllSingleLineViews();
1329        }
1330    }
1331
1332    public void setUserExpanding(boolean userExpanding) {
1333        mUserExpanding = userExpanding;
1334        if (userExpanding) {
1335            mTransformationStartVisibleType = mVisibleType;
1336        } else {
1337            mTransformationStartVisibleType = UNDEFINED;
1338            mVisibleType = calculateVisibleType();
1339            updateViewVisibilities(mVisibleType);
1340            updateBackgroundColor(false);
1341        }
1342    }
1343
1344    /**
1345     * Set by how much the single line view should be indented. Used when a overflow indicator is
1346     * present and only during measuring
1347     */
1348    public void setSingleLineWidthIndention(int singleLineWidthIndention) {
1349        if (singleLineWidthIndention != mSingleLineWidthIndention) {
1350            mSingleLineWidthIndention = singleLineWidthIndention;
1351            mContainingNotification.forceLayout();
1352            forceLayout();
1353        }
1354    }
1355
1356    public HybridNotificationView getSingleLineView() {
1357        return mSingleLineView;
1358    }
1359
1360    public void setRemoved() {
1361        if (mExpandedRemoteInput != null) {
1362            mExpandedRemoteInput.setRemoved();
1363        }
1364        if (mHeadsUpRemoteInput != null) {
1365            mHeadsUpRemoteInput.setRemoved();
1366        }
1367    }
1368
1369    public void setContentHeightAnimating(boolean animating) {
1370        if (!animating) {
1371            mContentHeightAtAnimationStart = UNDEFINED;
1372        }
1373    }
1374
1375    @VisibleForTesting
1376    boolean isAnimatingVisibleType() {
1377        return mAnimationStartVisibleType != UNDEFINED;
1378    }
1379
1380    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
1381        mHeadsUpAnimatingAway = headsUpAnimatingAway;
1382        selectLayout(false /* animate */, true /* force */);
1383    }
1384
1385    public void setFocusOnVisibilityChange() {
1386        mFocusOnVisibilityChange = true;
1387    }
1388
1389    public void setIconsVisible(boolean iconsVisible) {
1390        mIconsVisible = iconsVisible;
1391        updateIconVisibilities();
1392    }
1393
1394    private void updateIconVisibilities() {
1395        if (mContractedWrapper != null) {
1396            NotificationHeaderView header = mContractedWrapper.getNotificationHeader();
1397            if (header != null) {
1398                header.getIcon().setForceHidden(!mIconsVisible);
1399            }
1400        }
1401        if (mHeadsUpWrapper != null) {
1402            NotificationHeaderView header = mHeadsUpWrapper.getNotificationHeader();
1403            if (header != null) {
1404                header.getIcon().setForceHidden(!mIconsVisible);
1405            }
1406        }
1407        if (mExpandedWrapper != null) {
1408            NotificationHeaderView header = mExpandedWrapper.getNotificationHeader();
1409            if (header != null) {
1410                header.getIcon().setForceHidden(!mIconsVisible);
1411            }
1412        }
1413    }
1414
1415    @Override
1416    public void onVisibilityAggregated(boolean isVisible) {
1417        super.onVisibilityAggregated(isVisible);
1418        if (isVisible) {
1419            fireExpandedVisibleListenerIfVisible();
1420        }
1421    }
1422
1423    /**
1424     * Sets a one-shot listener for when the expanded view becomes visible.
1425     *
1426     * This will fire the listener immediately if the expanded view is already visible.
1427     */
1428    public void setOnExpandedVisibleListener(Runnable r) {
1429        mExpandedVisibleListener = r;
1430        fireExpandedVisibleListenerIfVisible();
1431    }
1432
1433    public void setIsLowPriority(boolean isLowPriority) {
1434        mIsLowPriority = isLowPriority;
1435    }
1436
1437    public boolean isDimmable() {
1438        if (!mContractedWrapper.isDimmable()) {
1439            return false;
1440        }
1441        return true;
1442    }
1443}
1444