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 
17 package com.android.systemui.statusbar;
18 
19 import android.app.Notification;
20 import android.app.PendingIntent;
21 import android.app.RemoteInput;
22 import android.content.Context;
23 import android.graphics.Rect;
24 import android.os.Build;
25 import android.service.notification.StatusBarNotification;
26 import android.util.AttributeSet;
27 import android.view.NotificationHeaderView;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.ViewTreeObserver;
31 import android.widget.FrameLayout;
32 import android.widget.ImageView;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.util.NotificationColorUtil;
36 import com.android.systemui.R;
37 import com.android.systemui.statusbar.notification.HybridNotificationView;
38 import com.android.systemui.statusbar.notification.HybridGroupManager;
39 import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
40 import com.android.systemui.statusbar.notification.NotificationUtils;
41 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
42 import com.android.systemui.statusbar.phone.NotificationGroupManager;
43 import 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  */
50 public 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 
NotificationContentView(Context context, AttributeSet attrs)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 
setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight, int ambientHeight)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 
updateContractedHeaderWidth()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 
shouldContractedBeFixedSize()328     private boolean shouldContractedBeFixedSize() {
329         return mBeforeN && mContractedWrapper instanceof NotificationCustomViewWrapper;
330     }
331 
332     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)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
onAttachedToWindow()350     protected void onAttachedToWindow() {
351         super.onAttachedToWindow();
352         updateVisibility();
353     }
354 
getContractedChild()355     public View getContractedChild() {
356         return mContractedChild;
357     }
358 
getExpandedChild()359     public View getExpandedChild() {
360         return mExpandedChild;
361     }
362 
getHeadsUpChild()363     public View getHeadsUpChild() {
364         return mHeadsUpChild;
365     }
366 
getAmbientChild()367     public View getAmbientChild() {
368         return mAmbientChild;
369     }
370 
getAmbientSingleLineChild()371     public HybridNotificationView getAmbientSingleLineChild() {
372         return mAmbientSingleLineChild;
373     }
374 
setContractedChild(View child)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 
setExpandedChild(View child)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 
setHeadsUpChild(View child)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 
setAmbientChild(View child)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
onVisibilityChanged(View changedView, int visibility)468     protected void onVisibilityChanged(View changedView, int visibility) {
469         super.onVisibilityChanged(changedView, visibility);
470         updateVisibility();
471     }
472 
updateVisibility()473     private void updateVisibility() {
474         setVisible(isShown());
475     }
476 
477     @Override
onDetachedFromWindow()478     protected void onDetachedFromWindow() {
479         super.onDetachedFromWindow();
480         getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
481     }
482 
setVisible(final boolean isVisible)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 
focusExpandButtonIfNecessary()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 
setContentHeight(int contentHeight)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      */
getMinContentHeightHint()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 
isTransitioningFromTo(int from, int to)581     private boolean isTransitioningFromTo(int from, int to) {
582         return (mTransformationStartVisibleType == from || mAnimationStartVisibleType == from)
583                 && mVisibleType == to;
584     }
585 
isVisibleOrTransitioning(int type)586     private boolean isVisibleOrTransitioning(int type) {
587         return mVisibleType == type || mTransformationStartVisibleType == type
588                 || mAnimationStartVisibleType == type;
589     }
590 
updateContentTransformation()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 
updateBackgroundTransformation(float transformationAmount)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 
calculateTransformationAmount()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 
getContentHeight()650     public int getContentHeight() {
651         return mContentHeight;
652     }
653 
getMaxHeight()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 
getMinHeight()665     public int getMinHeight() {
666         return getMinHeight(false /* likeGroupExpanded */);
667     }
668 
getMinHeight(boolean likeGroupExpanded)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 
getShowingAmbientView()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 
isGroupExpanded()688     private boolean isGroupExpanded() {
689         return mGroupManager.isGroupExpanded(mStatusBarNotification);
690     }
691 
setClipTopAmount(int clipTopAmount)692     public void setClipTopAmount(int clipTopAmount) {
693         mClipTopAmount = clipTopAmount;
694         updateClipping();
695     }
696 
697 
setClipBottomAmount(int clipBottomAmount)698     public void setClipBottomAmount(int clipBottomAmount) {
699         mClipBottomAmount = clipBottomAmount;
700         updateClipping();
701     }
702 
703     @Override
setTranslationY(float translationY)704     public void setTranslationY(float translationY) {
705         super.setTranslationY(translationY);
706         updateClipping();
707     }
708 
updateClipping()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 
setClipToActualHeight(boolean clipToActualHeight)721     public void setClipToActualHeight(boolean clipToActualHeight) {
722         mClipToActualHeight = clipToActualHeight;
723         updateClipping();
724     }
725 
selectLayout(boolean animate, boolean force)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 
forceUpdateVisibilities()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 
fireExpandedVisibleListenerIfVisible()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 
forceUpdateVisibility(int type, View view, TransformableView wrapper)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 
updateBackgroundColor(boolean animate)799     public void updateBackgroundColor(boolean animate) {
800         int customBackgroundColor = getBackgroundColor(mVisibleType);
801         mContainingNotification.resetBackgroundAlpha();
802         mContainingNotification.setContentBackground(customBackgroundColor, animate, this);
803     }
804 
getVisibleType()805     public int getVisibleType() {
806         return mVisibleType;
807     }
808 
getBackgroundColorForExpansionState()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 
getBackgroundColor(int visibleType)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 
updateViewVisibilities(int visibleType)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 
updateViewVisibility(int visibleType, int type, View view, TransformableView wrapper)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 
animateToVisibleType(int visibleType)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 
transferRemoteInputFocus(int visibleType)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      */
getTransformableViewForVisibleType(int visibleType)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      */
getViewForVisibleType(int visibleType)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 
getVisibleWrapper(int visibleType)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      */
calculateVisibleType()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 
getVisualTypeForHeight(float viewHeight)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 
isContentExpandable()1011     public boolean isContentExpandable() {
1012         return mIsContentExpandable;
1013     }
1014 
setDark(boolean dark, boolean fade, long delay)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 
setHeadsUp(boolean headsUp)1035     public void setHeadsUp(boolean headsUp) {
1036         mIsHeadsUp = headsUp;
1037         selectLayout(false /* animate */, true /* force */);
1038         updateExpandButtons(mExpandable);
1039     }
1040 
1041     @Override
hasOverlappingRendering()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 
setLegacy(boolean legacy)1049     public void setLegacy(boolean legacy) {
1050         mLegacy = legacy;
1051         updateLegacy();
1052     }
1053 
updateLegacy()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 
setIsChildInGroup(boolean isChildInGroup)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 
onNotificationUpdated(NotificationData.Entry entry)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