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.notification.row;
18 
19 import android.annotation.Nullable;
20 import android.app.Notification;
21 import android.app.PendingIntent;
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.ArrayMap;
27 import android.util.ArraySet;
28 import android.util.AttributeSet;
29 import android.util.Log;
30 import android.view.MotionEvent;
31 import android.view.NotificationHeaderView;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.ViewTreeObserver;
35 import android.widget.FrameLayout;
36 import android.widget.ImageView;
37 import android.widget.LinearLayout;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.util.ContrastColorUtil;
41 import com.android.systemui.Dependency;
42 import com.android.systemui.R;
43 import com.android.systemui.statusbar.MediaTransferManager;
44 import com.android.systemui.statusbar.RemoteInputController;
45 import com.android.systemui.statusbar.SmartReplyController;
46 import com.android.systemui.statusbar.TransformableView;
47 import com.android.systemui.statusbar.notification.NotificationUtils;
48 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
49 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
50 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
51 import com.android.systemui.statusbar.phone.NotificationGroupManager;
52 import com.android.systemui.statusbar.policy.InflatedSmartReplies;
53 import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
54 import com.android.systemui.statusbar.policy.RemoteInputView;
55 import com.android.systemui.statusbar.policy.SmartReplyConstants;
56 import com.android.systemui.statusbar.policy.SmartReplyView;
57 
58 import java.io.FileDescriptor;
59 import java.io.PrintWriter;
60 
61 /**
62  * A frame layout containing the actual payload of the notification, including the contracted,
63  * expanded and heads up layout. This class is responsible for clipping the content and and
64  * switching between the expanded, contracted and the heads up view depending on its clipped size.
65  */
66 public class NotificationContentView extends FrameLayout {
67 
68     private static final String TAG = "NotificationContentView";
69     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
70     public static final int VISIBLE_TYPE_CONTRACTED = 0;
71     public static final int VISIBLE_TYPE_EXPANDED = 1;
72     public static final int VISIBLE_TYPE_HEADSUP = 2;
73     private static final int VISIBLE_TYPE_SINGLELINE = 3;
74     public static final int UNDEFINED = -1;
75 
76     private final Rect mClipBounds = new Rect();
77 
78     private int mMinContractedHeight;
79     private int mNotificationContentMarginEnd;
80     private View mContractedChild;
81     private View mExpandedChild;
82     private View mHeadsUpChild;
83     private HybridNotificationView mSingleLineView;
84 
85     private RemoteInputView mExpandedRemoteInput;
86     private RemoteInputView mHeadsUpRemoteInput;
87 
88     private SmartReplyConstants mSmartReplyConstants;
89     private SmartReplyView mExpandedSmartReplyView;
90     private SmartReplyView mHeadsUpSmartReplyView;
91     private SmartReplyController mSmartReplyController;
92     private InflatedSmartReplies mExpandedInflatedSmartReplies;
93     private InflatedSmartReplies mHeadsUpInflatedSmartReplies;
94     private SmartRepliesAndActions mCurrentSmartRepliesAndActions;
95 
96     private NotificationViewWrapper mContractedWrapper;
97     private NotificationViewWrapper mExpandedWrapper;
98     private NotificationViewWrapper mHeadsUpWrapper;
99     private HybridGroupManager mHybridGroupManager;
100     private int mClipTopAmount;
101     private int mContentHeight;
102     private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
103     private boolean mAnimate;
104     private boolean mIsHeadsUp;
105     private boolean mLegacy;
106     private boolean mIsChildInGroup;
107     private int mSmallHeight;
108     private int mHeadsUpHeight;
109     private int mNotificationMaxHeight;
110     private StatusBarNotification mStatusBarNotification;
111     private NotificationGroupManager mGroupManager;
112     private RemoteInputController mRemoteInputController;
113     private Runnable mExpandedVisibleListener;
114     /**
115      * List of listeners for when content views become inactive (i.e. not the showing view).
116      */
117     private final ArrayMap<View, Runnable> mOnContentViewInactiveListeners = new ArrayMap<>();
118 
119     private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
120             = new ViewTreeObserver.OnPreDrawListener() {
121         @Override
122         public boolean onPreDraw() {
123             // We need to post since we don't want the notification to animate on the very first
124             // frame
125             post(new Runnable() {
126                 @Override
127                 public void run() {
128                     mAnimate = true;
129                 }
130             });
131             getViewTreeObserver().removeOnPreDrawListener(this);
132             return true;
133         }
134     };
135 
136     private OnClickListener mExpandClickListener;
137     private boolean mBeforeN;
138     private boolean mExpandable;
139     private boolean mClipToActualHeight = true;
140     private ExpandableNotificationRow mContainingNotification;
141     /** The visible type at the start of a touch driven transformation */
142     private int mTransformationStartVisibleType;
143     /** The visible type at the start of an animation driven transformation */
144     private int mAnimationStartVisibleType = UNDEFINED;
145     private boolean mUserExpanding;
146     private int mSingleLineWidthIndention;
147     private boolean mForceSelectNextLayout = true;
148     private PendingIntent mPreviousExpandedRemoteInputIntent;
149     private PendingIntent mPreviousHeadsUpRemoteInputIntent;
150     private RemoteInputView mCachedExpandedRemoteInput;
151     private RemoteInputView mCachedHeadsUpRemoteInput;
152 
153     private int mContentHeightAtAnimationStart = UNDEFINED;
154     private boolean mFocusOnVisibilityChange;
155     private boolean mHeadsUpAnimatingAway;
156     private boolean mIconsVisible;
157     private int mClipBottomAmount;
158     private boolean mIsLowPriority;
159     private boolean mIsContentExpandable;
160     private boolean mRemoteInputVisible;
161     private int mUnrestrictedContentHeight;
162     private MediaTransferManager mMediaTransferManager;
163 
NotificationContentView(Context context, AttributeSet attrs)164     public NotificationContentView(Context context, AttributeSet attrs) {
165         super(context, attrs);
166         mHybridGroupManager = new HybridGroupManager(getContext(), this);
167         mMediaTransferManager = new MediaTransferManager(getContext());
168         mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
169         mSmartReplyController = Dependency.get(SmartReplyController.class);
170         initView();
171     }
172 
initView()173     public void initView() {
174         mMinContractedHeight = getResources().getDimensionPixelSize(
175                 R.dimen.min_notification_layout_height);
176         mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
177                 com.android.internal.R.dimen.notification_content_margin_end);
178     }
179 
setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight)180     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight) {
181         mSmallHeight = smallHeight;
182         mHeadsUpHeight = headsUpMaxHeight;
183         mNotificationMaxHeight = maxHeight;
184     }
185 
186     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)187     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
188         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
189         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
190         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
191         int maxSize = Integer.MAX_VALUE / 2;
192         int width = MeasureSpec.getSize(widthMeasureSpec);
193         if (hasFixedHeight || isHeightLimited) {
194             maxSize = MeasureSpec.getSize(heightMeasureSpec);
195         }
196         int maxChildHeight = 0;
197         if (mExpandedChild != null) {
198             int notificationMaxHeight = mNotificationMaxHeight;
199             if (mExpandedSmartReplyView != null) {
200                 notificationMaxHeight += mExpandedSmartReplyView.getHeightUpperLimit();
201             }
202             notificationMaxHeight += mExpandedWrapper.getExtraMeasureHeight();
203             int size = notificationMaxHeight;
204             ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
205             boolean useExactly = false;
206             if (layoutParams.height >= 0) {
207                 // An actual height is set
208                 size = Math.min(size, layoutParams.height);
209                 useExactly = true;
210             }
211             int spec = MeasureSpec.makeMeasureSpec(size, useExactly
212                             ? MeasureSpec.EXACTLY
213                             : MeasureSpec.AT_MOST);
214             measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
215             maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
216         }
217         if (mContractedChild != null) {
218             int heightSpec;
219             int size = mSmallHeight;
220             ViewGroup.LayoutParams layoutParams = mContractedChild.getLayoutParams();
221             boolean useExactly = false;
222             if (layoutParams.height >= 0) {
223                 // An actual height is set
224                 size = Math.min(size, layoutParams.height);
225                 useExactly = true;
226             }
227             if (shouldContractedBeFixedSize() || useExactly) {
228                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
229             } else {
230                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
231             }
232             measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
233             int measuredHeight = mContractedChild.getMeasuredHeight();
234             if (measuredHeight < mMinContractedHeight) {
235                 heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
236                 measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
237             }
238             maxChildHeight = Math.max(maxChildHeight, measuredHeight);
239             if (updateContractedHeaderWidth()) {
240                 measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
241             }
242             if (mExpandedChild != null
243                     && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
244                 // the Expanded child is smaller then the collapsed. Let's remeasure it.
245                 heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
246                         MeasureSpec.EXACTLY);
247                 measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
248             }
249         }
250         if (mHeadsUpChild != null) {
251             int maxHeight = mHeadsUpHeight;
252             if (mHeadsUpSmartReplyView != null) {
253                 maxHeight += mHeadsUpSmartReplyView.getHeightUpperLimit();
254             }
255             maxHeight += mHeadsUpWrapper.getExtraMeasureHeight();
256             int size = maxHeight;
257             ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
258             boolean useExactly = false;
259             if (layoutParams.height >= 0) {
260                 // An actual height is set
261                 size = Math.min(size, layoutParams.height);
262                 useExactly = true;
263             }
264             measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
265                     MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
266                             : MeasureSpec.AT_MOST), 0);
267             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
268         }
269         if (mSingleLineView != null) {
270             int singleLineWidthSpec = widthMeasureSpec;
271             if (mSingleLineWidthIndention != 0
272                     && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
273                 singleLineWidthSpec = MeasureSpec.makeMeasureSpec(
274                         width - mSingleLineWidthIndention + mSingleLineView.getPaddingEnd(),
275                         MeasureSpec.EXACTLY);
276             }
277             mSingleLineView.measure(singleLineWidthSpec,
278                     MeasureSpec.makeMeasureSpec(mNotificationMaxHeight, MeasureSpec.AT_MOST));
279             maxChildHeight = Math.max(maxChildHeight, mSingleLineView.getMeasuredHeight());
280         }
281         int ownHeight = Math.min(maxChildHeight, maxSize);
282         setMeasuredDimension(width, ownHeight);
283     }
284 
285     /**
286      * Get the extra height that needs to be added to the notification height for a given
287      * {@link RemoteInputView}.
288      * This is needed when the user is inline replying in order to ensure that the reply bar has
289      * enough padding.
290      *
291      * @param remoteInput The remote input to check.
292      * @return The extra height needed.
293      */
getExtraRemoteInputHeight(RemoteInputView remoteInput)294     private int getExtraRemoteInputHeight(RemoteInputView remoteInput) {
295         if (remoteInput != null && (remoteInput.isActive() || remoteInput.isSending())) {
296             return getResources().getDimensionPixelSize(
297                     com.android.internal.R.dimen.notification_content_margin);
298         }
299         return 0;
300     }
301 
updateContractedHeaderWidth()302     private boolean updateContractedHeaderWidth() {
303         // We need to update the expanded and the collapsed header to have exactly the same with to
304         // have the expand buttons laid out at the same location.
305         NotificationHeaderView contractedHeader = mContractedWrapper.getNotificationHeader();
306         if (contractedHeader != null) {
307             if (mExpandedChild != null
308                     && mExpandedWrapper.getNotificationHeader() != null) {
309                 NotificationHeaderView expandedHeader = mExpandedWrapper.getNotificationHeader();
310 
311                 int headerTextMargin = expandedHeader.getHeaderTextMarginEnd();
312                 if (headerTextMargin != contractedHeader.getHeaderTextMarginEnd()) {
313                     contractedHeader.setHeaderTextMarginEnd(headerTextMargin);
314                     return true;
315                 }
316             } else {
317                 int paddingEnd = mNotificationContentMarginEnd;
318                 if (contractedHeader.getPaddingEnd() != paddingEnd) {
319                     contractedHeader.setPadding(
320                             contractedHeader.isLayoutRtl()
321                                     ? paddingEnd
322                                     : contractedHeader.getPaddingLeft(),
323                             contractedHeader.getPaddingTop(),
324                             contractedHeader.isLayoutRtl()
325                                     ? contractedHeader.getPaddingLeft()
326                                     : paddingEnd,
327                             contractedHeader.getPaddingBottom());
328                     contractedHeader.setShowWorkBadgeAtEnd(false);
329                     return true;
330                 }
331             }
332         }
333         return false;
334     }
335 
shouldContractedBeFixedSize()336     private boolean shouldContractedBeFixedSize() {
337         return mBeforeN && mContractedWrapper instanceof NotificationCustomViewWrapper;
338     }
339 
340     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)341     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
342         int previousHeight = 0;
343         if (mExpandedChild != null) {
344             previousHeight = mExpandedChild.getHeight();
345         }
346         super.onLayout(changed, left, top, right, bottom);
347         if (previousHeight != 0 && mExpandedChild.getHeight() != previousHeight) {
348             mContentHeightAtAnimationStart = previousHeight;
349         }
350         updateClipping();
351         invalidateOutline();
352         selectLayout(false /* animate */, mForceSelectNextLayout /* force */);
353         mForceSelectNextLayout = false;
354         updateExpandButtons(mExpandable);
355     }
356 
357     @Override
onAttachedToWindow()358     protected void onAttachedToWindow() {
359         super.onAttachedToWindow();
360         updateVisibility();
361     }
362 
getContractedChild()363     public View getContractedChild() {
364         return mContractedChild;
365     }
366 
getExpandedChild()367     public View getExpandedChild() {
368         return mExpandedChild;
369     }
370 
getHeadsUpChild()371     public View getHeadsUpChild() {
372         return mHeadsUpChild;
373     }
374 
375     /**
376      * Sets the contracted view. Child may be null to remove the content view.
377      *
378      * @param child contracted content view to set
379      */
setContractedChild(@ullable View child)380     public void setContractedChild(@Nullable View child) {
381         if (mContractedChild != null) {
382             mContractedChild.animate().cancel();
383             removeView(mContractedChild);
384         }
385         if (child == null) {
386             mContractedChild = null;
387             mContractedWrapper = null;
388             if (mTransformationStartVisibleType == VISIBLE_TYPE_CONTRACTED) {
389                 mTransformationStartVisibleType = UNDEFINED;
390             }
391             return;
392         }
393         addView(child);
394         mContractedChild = child;
395         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
396                 mContainingNotification);
397     }
398 
getWrapperForView(View child)399     private NotificationViewWrapper getWrapperForView(View child) {
400         if (child == mContractedChild) {
401             return mContractedWrapper;
402         }
403         if (child == mExpandedChild) {
404             return mExpandedWrapper;
405         }
406         if (child == mHeadsUpChild) {
407             return mHeadsUpWrapper;
408         }
409         return null;
410     }
411 
412     /**
413      * Sets the expanded view. Child may be null to remove the content view.
414      *
415      * @param child expanded content view to set
416      */
setExpandedChild(@ullable View child)417     public void setExpandedChild(@Nullable View child) {
418         if (mExpandedChild != null) {
419             mPreviousExpandedRemoteInputIntent = null;
420             if (mExpandedRemoteInput != null) {
421                 mExpandedRemoteInput.onNotificationUpdateOrReset();
422                 if (mExpandedRemoteInput.isActive()) {
423                     mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent();
424                     mCachedExpandedRemoteInput = mExpandedRemoteInput;
425                     mExpandedRemoteInput.dispatchStartTemporaryDetach();
426                     ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
427                 }
428             }
429             mExpandedChild.animate().cancel();
430             removeView(mExpandedChild);
431             mExpandedRemoteInput = null;
432         }
433         if (child == null) {
434             mExpandedChild = null;
435             mExpandedWrapper = null;
436             if (mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED) {
437                 mTransformationStartVisibleType = UNDEFINED;
438             }
439             if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
440                 selectLayout(false /* animate */, true /* force */);
441             }
442             return;
443         }
444         addView(child);
445         mExpandedChild = child;
446         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
447                 mContainingNotification);
448     }
449 
450     /**
451      * Sets the heads up view. Child may be null to remove the content view.
452      *
453      * @param child heads up content view to set
454      */
setHeadsUpChild(@ullable View child)455     public void setHeadsUpChild(@Nullable View child) {
456         if (mHeadsUpChild != null) {
457             mPreviousHeadsUpRemoteInputIntent = null;
458             if (mHeadsUpRemoteInput != null) {
459                 mHeadsUpRemoteInput.onNotificationUpdateOrReset();
460                 if (mHeadsUpRemoteInput.isActive()) {
461                     mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent();
462                     mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
463                     mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
464                     ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
465                 }
466             }
467             mHeadsUpChild.animate().cancel();
468             removeView(mHeadsUpChild);
469             mHeadsUpRemoteInput = null;
470         }
471         if (child == null) {
472             mHeadsUpChild = null;
473             mHeadsUpWrapper = null;
474             if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
475                 mTransformationStartVisibleType = UNDEFINED;
476             }
477             if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
478                 selectLayout(false /* animate */, true /* force */);
479             }
480             return;
481         }
482         addView(child);
483         mHeadsUpChild = child;
484         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
485                 mContainingNotification);
486     }
487 
488     @Override
onViewAdded(View child)489     public void onViewAdded(View child) {
490         super.onViewAdded(child);
491         child.setTag(R.id.row_tag_for_content_view, mContainingNotification);
492     }
493 
494     @Override
onVisibilityChanged(View changedView, int visibility)495     protected void onVisibilityChanged(View changedView, int visibility) {
496         super.onVisibilityChanged(changedView, visibility);
497         updateVisibility();
498         if (visibility != VISIBLE) {
499             // View is no longer visible so all content views are inactive.
500             for (Runnable r : mOnContentViewInactiveListeners.values()) {
501                 r.run();
502             }
503             mOnContentViewInactiveListeners.clear();
504         }
505     }
506 
updateVisibility()507     private void updateVisibility() {
508         setVisible(isShown());
509     }
510 
511     @Override
onDetachedFromWindow()512     protected void onDetachedFromWindow() {
513         super.onDetachedFromWindow();
514         getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
515     }
516 
setVisible(final boolean isVisible)517     private void setVisible(final boolean isVisible) {
518         if (isVisible) {
519             // This call can happen multiple times, but removing only removes a single one.
520             // We therefore need to remove the old one.
521             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
522             // We only animate if we are drawn at least once, otherwise the view might animate when
523             // it's shown the first time
524             getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
525         } else {
526             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
527             mAnimate = false;
528         }
529     }
530 
focusExpandButtonIfNecessary()531     private void focusExpandButtonIfNecessary() {
532         if (mFocusOnVisibilityChange) {
533             NotificationHeaderView header = getVisibleNotificationHeader();
534             if (header != null) {
535                 ImageView expandButton = header.getExpandButton();
536                 if (expandButton != null) {
537                     expandButton.requestAccessibilityFocus();
538                 }
539             }
540             mFocusOnVisibilityChange = false;
541         }
542     }
543 
setContentHeight(int contentHeight)544     public void setContentHeight(int contentHeight) {
545         mUnrestrictedContentHeight = Math.max(contentHeight, getMinHeight());
546         int maxContentHeight = mContainingNotification.getIntrinsicHeight()
547                 - getExtraRemoteInputHeight(mExpandedRemoteInput)
548                 - getExtraRemoteInputHeight(mHeadsUpRemoteInput);
549         mContentHeight = Math.min(mUnrestrictedContentHeight, maxContentHeight);
550         selectLayout(mAnimate /* animate */, false /* force */);
551 
552         if (mContractedChild == null) {
553             // Contracted child may be null if this is the public content view and we don't need to
554             // show it.
555             return;
556         }
557 
558         int minHeightHint = getMinContentHeightHint();
559 
560         NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
561         if (wrapper != null) {
562             wrapper.setContentHeight(mUnrestrictedContentHeight, minHeightHint);
563         }
564 
565         wrapper = getVisibleWrapper(mTransformationStartVisibleType);
566         if (wrapper != null) {
567             wrapper.setContentHeight(mUnrestrictedContentHeight, minHeightHint);
568         }
569 
570         updateClipping();
571         invalidateOutline();
572     }
573 
574     /**
575      * @return the minimum apparent height that the wrapper should allow for the purpose
576      *         of aligning elements at the bottom edge. If this is larger than the content
577      *         height, the notification is clipped instead of being further shrunk.
578      */
getMinContentHeightHint()579     private int getMinContentHeightHint() {
580         if (mIsChildInGroup && isVisibleOrTransitioning(VISIBLE_TYPE_SINGLELINE)) {
581             return mContext.getResources().getDimensionPixelSize(
582                         com.android.internal.R.dimen.notification_action_list_height);
583         }
584 
585         // Transition between heads-up & expanded, or pinned.
586         if (mHeadsUpChild != null && mExpandedChild != null) {
587             boolean transitioningBetweenHunAndExpanded =
588                     isTransitioningFromTo(VISIBLE_TYPE_HEADSUP, VISIBLE_TYPE_EXPANDED) ||
589                     isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP);
590             boolean pinned = !isVisibleOrTransitioning(VISIBLE_TYPE_CONTRACTED)
591                     && (mIsHeadsUp || mHeadsUpAnimatingAway)
592                     && mContainingNotification.canShowHeadsUp();
593             if (transitioningBetweenHunAndExpanded || pinned) {
594                 return Math.min(getViewHeight(VISIBLE_TYPE_HEADSUP),
595                         getViewHeight(VISIBLE_TYPE_EXPANDED));
596             }
597         }
598 
599         // Size change of the expanded version
600         if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart >= 0
601                 && mExpandedChild != null) {
602             return Math.min(mContentHeightAtAnimationStart, getViewHeight(VISIBLE_TYPE_EXPANDED));
603         }
604 
605         int hint;
606         if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
607             hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
608         } else if (mExpandedChild != null) {
609             hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
610         } else {
611             hint = getViewHeight(VISIBLE_TYPE_CONTRACTED)
612                     + mContext.getResources().getDimensionPixelSize(
613                             com.android.internal.R.dimen.notification_action_list_height);
614         }
615 
616         if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) {
617             hint = Math.min(hint, getViewHeight(VISIBLE_TYPE_EXPANDED));
618         }
619         return hint;
620     }
621 
isTransitioningFromTo(int from, int to)622     private boolean isTransitioningFromTo(int from, int to) {
623         return (mTransformationStartVisibleType == from || mAnimationStartVisibleType == from)
624                 && mVisibleType == to;
625     }
626 
isVisibleOrTransitioning(int type)627     private boolean isVisibleOrTransitioning(int type) {
628         return mVisibleType == type || mTransformationStartVisibleType == type
629                 || mAnimationStartVisibleType == type;
630     }
631 
updateContentTransformation()632     private void updateContentTransformation() {
633         int visibleType = calculateVisibleType();
634         if (visibleType != mVisibleType) {
635             // A new transformation starts
636             mTransformationStartVisibleType = mVisibleType;
637             final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
638             final TransformableView hiddenView = getTransformableViewForVisibleType(
639                     mTransformationStartVisibleType);
640             shownView.transformFrom(hiddenView, 0.0f);
641             getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
642             hiddenView.transformTo(shownView, 0.0f);
643             mVisibleType = visibleType;
644             updateBackgroundColor(true /* animate */);
645         }
646         if (mForceSelectNextLayout) {
647             forceUpdateVisibilities();
648         }
649         if (mTransformationStartVisibleType != UNDEFINED
650                 && mVisibleType != mTransformationStartVisibleType
651                 && getViewForVisibleType(mTransformationStartVisibleType) != null) {
652             final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType);
653             final TransformableView hiddenView = getTransformableViewForVisibleType(
654                     mTransformationStartVisibleType);
655             float transformationAmount = calculateTransformationAmount();
656             shownView.transformFrom(hiddenView, transformationAmount);
657             hiddenView.transformTo(shownView, transformationAmount);
658             updateBackgroundTransformation(transformationAmount);
659         } else {
660             updateViewVisibilities(visibleType);
661             updateBackgroundColor(false);
662         }
663     }
664 
updateBackgroundTransformation(float transformationAmount)665     private void updateBackgroundTransformation(float transformationAmount) {
666         int endColor = getBackgroundColor(mVisibleType);
667         int startColor = getBackgroundColor(mTransformationStartVisibleType);
668         if (endColor != startColor) {
669             if (startColor == 0) {
670                 startColor = mContainingNotification.getBackgroundColorWithoutTint();
671             }
672             if (endColor == 0) {
673                 endColor = mContainingNotification.getBackgroundColorWithoutTint();
674             }
675             endColor = NotificationUtils.interpolateColors(startColor, endColor,
676                     transformationAmount);
677         }
678         mContainingNotification.updateBackgroundAlpha(transformationAmount);
679         mContainingNotification.setContentBackground(endColor, false, this);
680     }
681 
calculateTransformationAmount()682     private float calculateTransformationAmount() {
683         int startHeight = getViewHeight(mTransformationStartVisibleType);
684         int endHeight = getViewHeight(mVisibleType);
685         int progress = Math.abs(mContentHeight - startHeight);
686         int totalDistance = Math.abs(endHeight - startHeight);
687         if (totalDistance == 0) {
688             Log.wtf(TAG, "the total transformation distance is 0"
689                     + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
690                     + "\n VisibleType: " + mVisibleType + " height: " + endHeight
691                     + "\n mContentHeight: " + mContentHeight);
692             return 1.0f;
693         }
694         float amount = (float) progress / (float) totalDistance;
695         return Math.min(1.0f, amount);
696     }
697 
getContentHeight()698     public int getContentHeight() {
699         return mContentHeight;
700     }
701 
getMaxHeight()702     public int getMaxHeight() {
703         if (mExpandedChild != null) {
704             return getViewHeight(VISIBLE_TYPE_EXPANDED)
705                     + getExtraRemoteInputHeight(mExpandedRemoteInput);
706         } else if (mIsHeadsUp && mHeadsUpChild != null && mContainingNotification.canShowHeadsUp()) {
707             return getViewHeight(VISIBLE_TYPE_HEADSUP)
708                     + getExtraRemoteInputHeight(mHeadsUpRemoteInput);
709         } else if (mContractedChild != null) {
710             return getViewHeight(VISIBLE_TYPE_CONTRACTED);
711         }
712         return mNotificationMaxHeight;
713     }
714 
getViewHeight(int visibleType)715     private int getViewHeight(int visibleType) {
716         return getViewHeight(visibleType, false /* forceNoHeader */);
717     }
718 
getViewHeight(int visibleType, boolean forceNoHeader)719     private int getViewHeight(int visibleType, boolean forceNoHeader) {
720         View view = getViewForVisibleType(visibleType);
721         int height = view.getHeight();
722         NotificationViewWrapper viewWrapper = getWrapperForView(view);
723         if (viewWrapper != null) {
724             height += viewWrapper.getHeaderTranslation(forceNoHeader);
725         }
726         return height;
727     }
728 
getMinHeight()729     public int getMinHeight() {
730         return getMinHeight(false /* likeGroupExpanded */);
731     }
732 
getMinHeight(boolean likeGroupExpanded)733     public int getMinHeight(boolean likeGroupExpanded) {
734         if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
735             return mContractedChild != null
736                     ? getViewHeight(VISIBLE_TYPE_CONTRACTED) : mMinContractedHeight;
737         } else {
738             return mSingleLineView.getHeight();
739         }
740     }
741 
isGroupExpanded()742     private boolean isGroupExpanded() {
743         return mGroupManager.isGroupExpanded(mStatusBarNotification);
744     }
745 
setClipTopAmount(int clipTopAmount)746     public void setClipTopAmount(int clipTopAmount) {
747         mClipTopAmount = clipTopAmount;
748         updateClipping();
749     }
750 
751 
setClipBottomAmount(int clipBottomAmount)752     public void setClipBottomAmount(int clipBottomAmount) {
753         mClipBottomAmount = clipBottomAmount;
754         updateClipping();
755     }
756 
757     @Override
setTranslationY(float translationY)758     public void setTranslationY(float translationY) {
759         super.setTranslationY(translationY);
760         updateClipping();
761     }
762 
updateClipping()763     private void updateClipping() {
764         if (mClipToActualHeight) {
765             int top = (int) (mClipTopAmount - getTranslationY());
766             int bottom = (int) (mUnrestrictedContentHeight - mClipBottomAmount - getTranslationY());
767             bottom = Math.max(top, bottom);
768             mClipBounds.set(0, top, getWidth(), bottom);
769             setClipBounds(mClipBounds);
770         } else {
771             setClipBounds(null);
772         }
773     }
774 
setClipToActualHeight(boolean clipToActualHeight)775     public void setClipToActualHeight(boolean clipToActualHeight) {
776         mClipToActualHeight = clipToActualHeight;
777         updateClipping();
778     }
779 
selectLayout(boolean animate, boolean force)780     private void selectLayout(boolean animate, boolean force) {
781         if (mContractedChild == null) {
782             return;
783         }
784         if (mUserExpanding) {
785             updateContentTransformation();
786         } else {
787             int visibleType = calculateVisibleType();
788             boolean changedType = visibleType != mVisibleType;
789             if (changedType || force) {
790                 View visibleView = getViewForVisibleType(visibleType);
791                 if (visibleView != null) {
792                     visibleView.setVisibility(VISIBLE);
793                     transferRemoteInputFocus(visibleType);
794                 }
795 
796                 if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
797                         || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
798                         || (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
799                         || visibleType == VISIBLE_TYPE_CONTRACTED)) {
800                     animateToVisibleType(visibleType);
801                 } else {
802                     updateViewVisibilities(visibleType);
803                 }
804                 mVisibleType = visibleType;
805                 if (changedType) {
806                     focusExpandButtonIfNecessary();
807                 }
808                 NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
809                 if (visibleWrapper != null) {
810                     visibleWrapper.setContentHeight(mUnrestrictedContentHeight,
811                             getMinContentHeightHint());
812                 }
813                 updateBackgroundColor(animate);
814             }
815         }
816     }
817 
forceUpdateVisibilities()818     private void forceUpdateVisibilities() {
819         forceUpdateVisibility(VISIBLE_TYPE_CONTRACTED, mContractedChild, mContractedWrapper);
820         forceUpdateVisibility(VISIBLE_TYPE_EXPANDED, mExpandedChild, mExpandedWrapper);
821         forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
822         forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
823         fireExpandedVisibleListenerIfVisible();
824         // forceUpdateVisibilities cancels outstanding animations without updating the
825         // mAnimationStartVisibleType. Do so here instead.
826         mAnimationStartVisibleType = UNDEFINED;
827     }
828 
fireExpandedVisibleListenerIfVisible()829     private void fireExpandedVisibleListenerIfVisible() {
830         if (mExpandedVisibleListener != null && mExpandedChild != null && isShown()
831                 && mExpandedChild.getVisibility() == VISIBLE) {
832             Runnable listener = mExpandedVisibleListener;
833             mExpandedVisibleListener = null;
834             listener.run();
835         }
836     }
837 
forceUpdateVisibility(int type, View view, TransformableView wrapper)838     private void forceUpdateVisibility(int type, View view, TransformableView wrapper) {
839         if (view == null) {
840             return;
841         }
842         boolean visible = mVisibleType == type
843                 || mTransformationStartVisibleType == type;
844         if (!visible) {
845             view.setVisibility(INVISIBLE);
846         } else {
847             wrapper.setVisible(true);
848         }
849     }
850 
updateBackgroundColor(boolean animate)851     public void updateBackgroundColor(boolean animate) {
852         int customBackgroundColor = getBackgroundColor(mVisibleType);
853         mContainingNotification.resetBackgroundAlpha();
854         mContainingNotification.setContentBackground(customBackgroundColor, animate, this);
855     }
856 
setBackgroundTintColor(int color)857     public void setBackgroundTintColor(int color) {
858         if (mExpandedSmartReplyView != null) {
859             mExpandedSmartReplyView.setBackgroundTintColor(color);
860         }
861         if (mHeadsUpSmartReplyView != null) {
862             mHeadsUpSmartReplyView.setBackgroundTintColor(color);
863         }
864     }
865 
getVisibleType()866     public int getVisibleType() {
867         return mVisibleType;
868     }
869 
getBackgroundColorForExpansionState()870     public int getBackgroundColorForExpansionState() {
871         // When expanding or user locked we want the new type, when collapsing we want
872         // the original type
873         final int visibleType = (mContainingNotification.isGroupExpanded()
874                 || mContainingNotification.isUserLocked())
875                         ? calculateVisibleType()
876                         : getVisibleType();
877         return getBackgroundColor(visibleType);
878     }
879 
getBackgroundColor(int visibleType)880     public int getBackgroundColor(int visibleType) {
881         NotificationViewWrapper currentVisibleWrapper = getVisibleWrapper(visibleType);
882         int customBackgroundColor = 0;
883         if (currentVisibleWrapper != null) {
884             customBackgroundColor = currentVisibleWrapper.getCustomBackgroundColor();
885         }
886         return customBackgroundColor;
887     }
888 
updateViewVisibilities(int visibleType)889     private void updateViewVisibilities(int visibleType) {
890         updateViewVisibility(visibleType, VISIBLE_TYPE_CONTRACTED,
891                 mContractedChild, mContractedWrapper);
892         updateViewVisibility(visibleType, VISIBLE_TYPE_EXPANDED,
893                 mExpandedChild, mExpandedWrapper);
894         updateViewVisibility(visibleType, VISIBLE_TYPE_HEADSUP,
895                 mHeadsUpChild, mHeadsUpWrapper);
896         updateViewVisibility(visibleType, VISIBLE_TYPE_SINGLELINE,
897                 mSingleLineView, mSingleLineView);
898         fireExpandedVisibleListenerIfVisible();
899         // updateViewVisibilities cancels outstanding animations without updating the
900         // mAnimationStartVisibleType. Do so here instead.
901         mAnimationStartVisibleType = UNDEFINED;
902     }
903 
updateViewVisibility(int visibleType, int type, View view, TransformableView wrapper)904     private void updateViewVisibility(int visibleType, int type, View view,
905             TransformableView wrapper) {
906         if (view != null) {
907             wrapper.setVisible(visibleType == type);
908         }
909     }
910 
animateToVisibleType(int visibleType)911     private void animateToVisibleType(int visibleType) {
912         final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
913         final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
914         if (shownView == hiddenView || hiddenView == null) {
915             shownView.setVisible(true);
916             return;
917         }
918         mAnimationStartVisibleType = mVisibleType;
919         shownView.transformFrom(hiddenView);
920         getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
921         hiddenView.transformTo(shownView, new Runnable() {
922             @Override
923             public void run() {
924                 if (hiddenView != getTransformableViewForVisibleType(mVisibleType)) {
925                     hiddenView.setVisible(false);
926                 }
927                 mAnimationStartVisibleType = UNDEFINED;
928             }
929         });
930         fireExpandedVisibleListenerIfVisible();
931     }
932 
transferRemoteInputFocus(int visibleType)933     private void transferRemoteInputFocus(int visibleType) {
934         if (visibleType == VISIBLE_TYPE_HEADSUP
935                 && mHeadsUpRemoteInput != null
936                 && (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive())) {
937             mHeadsUpRemoteInput.stealFocusFrom(mExpandedRemoteInput);
938         }
939         if (visibleType == VISIBLE_TYPE_EXPANDED
940                 && mExpandedRemoteInput != null
941                 && (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive())) {
942             mExpandedRemoteInput.stealFocusFrom(mHeadsUpRemoteInput);
943         }
944     }
945 
946     /**
947      * @param visibleType one of the static enum types in this view
948      * @return the corresponding transformable view according to the given visible type
949      */
getTransformableViewForVisibleType(int visibleType)950     private TransformableView getTransformableViewForVisibleType(int visibleType) {
951         switch (visibleType) {
952             case VISIBLE_TYPE_EXPANDED:
953                 return mExpandedWrapper;
954             case VISIBLE_TYPE_HEADSUP:
955                 return mHeadsUpWrapper;
956             case VISIBLE_TYPE_SINGLELINE:
957                 return mSingleLineView;
958             default:
959                 return mContractedWrapper;
960         }
961     }
962 
963     /**
964      * @param visibleType one of the static enum types in this view
965      * @return the corresponding view according to the given visible type
966      */
getViewForVisibleType(int visibleType)967     private View getViewForVisibleType(int visibleType) {
968         switch (visibleType) {
969             case VISIBLE_TYPE_EXPANDED:
970                 return mExpandedChild;
971             case VISIBLE_TYPE_HEADSUP:
972                 return mHeadsUpChild;
973             case VISIBLE_TYPE_SINGLELINE:
974                 return mSingleLineView;
975             default:
976                 return mContractedChild;
977         }
978     }
979 
getVisibleWrapper(int visibleType)980     public NotificationViewWrapper getVisibleWrapper(int visibleType) {
981         switch (visibleType) {
982             case VISIBLE_TYPE_EXPANDED:
983                 return mExpandedWrapper;
984             case VISIBLE_TYPE_HEADSUP:
985                 return mHeadsUpWrapper;
986             case VISIBLE_TYPE_CONTRACTED:
987                 return mContractedWrapper;
988             default:
989                 return null;
990         }
991     }
992 
993     /**
994      * @return one of the static enum types in this view, calculated form the current state
995      */
calculateVisibleType()996     public int calculateVisibleType() {
997         if (mUserExpanding) {
998             int height = !mIsChildInGroup || isGroupExpanded()
999                     || mContainingNotification.isExpanded(true /* allowOnKeyguard */)
1000                     ? mContainingNotification.getMaxContentHeight()
1001                     : mContainingNotification.getShowingLayout().getMinHeight();
1002             if (height == 0) {
1003                 height = mContentHeight;
1004             }
1005             int expandedVisualType = getVisualTypeForHeight(height);
1006             int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
1007                     ? VISIBLE_TYPE_SINGLELINE
1008                     : getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
1009             return mTransformationStartVisibleType == collapsedVisualType
1010                     ? expandedVisualType
1011                     : collapsedVisualType;
1012         }
1013         int intrinsicHeight = mContainingNotification.getIntrinsicHeight();
1014         int viewHeight = mContentHeight;
1015         if (intrinsicHeight != 0) {
1016             // the intrinsicHeight might be 0 because it was just reset.
1017             viewHeight = Math.min(mContentHeight, intrinsicHeight);
1018         }
1019         return getVisualTypeForHeight(viewHeight);
1020     }
1021 
getVisualTypeForHeight(float viewHeight)1022     private int getVisualTypeForHeight(float viewHeight) {
1023         boolean noExpandedChild = mExpandedChild == null;
1024         if (!noExpandedChild && viewHeight == getViewHeight(VISIBLE_TYPE_EXPANDED)) {
1025             return VISIBLE_TYPE_EXPANDED;
1026         }
1027         if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
1028             return VISIBLE_TYPE_SINGLELINE;
1029         }
1030 
1031         if ((mIsHeadsUp || mHeadsUpAnimatingAway) && mHeadsUpChild != null
1032                 && mContainingNotification.canShowHeadsUp()) {
1033             if (viewHeight <= getViewHeight(VISIBLE_TYPE_HEADSUP) || noExpandedChild) {
1034                 return VISIBLE_TYPE_HEADSUP;
1035             } else {
1036                 return VISIBLE_TYPE_EXPANDED;
1037             }
1038         } else {
1039             if (noExpandedChild || (mContractedChild != null
1040                     && viewHeight <= getViewHeight(VISIBLE_TYPE_CONTRACTED)
1041                     && (!mIsChildInGroup || isGroupExpanded()
1042                             || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
1043                 return VISIBLE_TYPE_CONTRACTED;
1044             } else {
1045                 return VISIBLE_TYPE_EXPANDED;
1046             }
1047         }
1048     }
1049 
isContentExpandable()1050     public boolean isContentExpandable() {
1051         return mIsContentExpandable;
1052     }
1053 
setHeadsUp(boolean headsUp)1054     public void setHeadsUp(boolean headsUp) {
1055         mIsHeadsUp = headsUp;
1056         selectLayout(false /* animate */, true /* force */);
1057         updateExpandButtons(mExpandable);
1058     }
1059 
1060     @Override
hasOverlappingRendering()1061     public boolean hasOverlappingRendering() {
1062 
1063         // This is not really true, but good enough when fading from the contracted to the expanded
1064         // layout, and saves us some layers.
1065         return false;
1066     }
1067 
setLegacy(boolean legacy)1068     public void setLegacy(boolean legacy) {
1069         mLegacy = legacy;
1070         updateLegacy();
1071     }
1072 
updateLegacy()1073     private void updateLegacy() {
1074         if (mContractedChild != null) {
1075             mContractedWrapper.setLegacy(mLegacy);
1076         }
1077         if (mExpandedChild != null) {
1078             mExpandedWrapper.setLegacy(mLegacy);
1079         }
1080         if (mHeadsUpChild != null) {
1081             mHeadsUpWrapper.setLegacy(mLegacy);
1082         }
1083     }
1084 
setIsChildInGroup(boolean isChildInGroup)1085     public void setIsChildInGroup(boolean isChildInGroup) {
1086         mIsChildInGroup = isChildInGroup;
1087         if (mContractedChild != null) {
1088             mContractedWrapper.setIsChildInGroup(mIsChildInGroup);
1089         }
1090         if (mExpandedChild != null) {
1091             mExpandedWrapper.setIsChildInGroup(mIsChildInGroup);
1092         }
1093         if (mHeadsUpChild != null) {
1094             mHeadsUpWrapper.setIsChildInGroup(mIsChildInGroup);
1095         }
1096         updateAllSingleLineViews();
1097     }
1098 
onNotificationUpdated(NotificationEntry entry)1099     public void onNotificationUpdated(NotificationEntry entry) {
1100         mStatusBarNotification = entry.notification;
1101         mOnContentViewInactiveListeners.clear();
1102         mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
1103         updateAllSingleLineViews();
1104         ExpandableNotificationRow row = entry.getRow();
1105         if (mContractedChild != null) {
1106             mContractedWrapper.onContentUpdated(row);
1107         }
1108         if (mExpandedChild != null) {
1109             mExpandedWrapper.onContentUpdated(row);
1110         }
1111         if (mHeadsUpChild != null) {
1112             mHeadsUpWrapper.onContentUpdated(row);
1113         }
1114         applyRemoteInputAndSmartReply(entry);
1115         applyMediaTransfer(entry);
1116         updateLegacy();
1117         mForceSelectNextLayout = true;
1118         mPreviousExpandedRemoteInputIntent = null;
1119         mPreviousHeadsUpRemoteInputIntent = null;
1120     }
1121 
1122     private void updateAllSingleLineViews() {
1123         updateSingleLineView();
1124     }
1125 
1126     private void updateSingleLineView() {
1127         if (mIsChildInGroup) {
1128             boolean isNewView = mSingleLineView == null;
1129             mSingleLineView = mHybridGroupManager.bindFromNotification(
1130                     mSingleLineView, mStatusBarNotification.getNotification());
1131             if (isNewView) {
1132                 updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
1133                         mSingleLineView, mSingleLineView);
1134             }
1135         } else if (mSingleLineView != null) {
1136             removeView(mSingleLineView);
1137             mSingleLineView = null;
1138         }
1139     }
1140 
1141     private void applyMediaTransfer(final NotificationEntry entry) {
1142         View bigContentView = mExpandedChild;
1143         if (bigContentView == null || !entry.isMediaNotification()) {
1144             return;
1145         }
1146 
1147         View mediaActionContainer = bigContentView.findViewById(
1148                 com.android.internal.R.id.media_actions);
1149         if (!(mediaActionContainer instanceof LinearLayout)) {
1150             return;
1151         }
1152 
1153         mMediaTransferManager.applyMediaTransferView((ViewGroup) mediaActionContainer,
1154                 entry);
1155     }
1156 
1157     private void applyRemoteInputAndSmartReply(final NotificationEntry entry) {
1158         if (mRemoteInputController == null) {
1159             return;
1160         }
1161 
1162         applyRemoteInput(entry, InflatedSmartReplies.hasFreeformRemoteInput(entry));
1163 
1164         if (mExpandedInflatedSmartReplies == null && mHeadsUpInflatedSmartReplies == null) {
1165             if (DEBUG) {
1166                 Log.d(TAG, "Both expanded, and heads-up InflatedSmartReplies are null, "
1167                         + "don't add smart replies.");
1168             }
1169             return;
1170         }
1171         // The inflated smart-reply objects for the expanded view and the heads-up view both contain
1172         // the same SmartRepliesAndActions to avoid discrepancies between the two views. We here
1173         // reuse that object for our local SmartRepliesAndActions to avoid discrepancies between
1174         // this class and the InflatedSmartReplies classes.
1175         mCurrentSmartRepliesAndActions = mExpandedInflatedSmartReplies != null
1176                 ? mExpandedInflatedSmartReplies.getSmartRepliesAndActions()
1177                 : mHeadsUpInflatedSmartReplies.getSmartRepliesAndActions();
1178         if (DEBUG) {
1179             Log.d(TAG, String.format("Adding suggestions for %s, %d actions, and %d replies.",
1180                     entry.notification.getKey(),
1181                     mCurrentSmartRepliesAndActions.smartActions == null ? 0 :
1182                             mCurrentSmartRepliesAndActions.smartActions.actions.size(),
1183                     mCurrentSmartRepliesAndActions.smartReplies == null ? 0 :
1184                             mCurrentSmartRepliesAndActions.smartReplies.choices.length));
1185         }
1186         applySmartReplyView(mCurrentSmartRepliesAndActions, entry);
1187     }
1188 
1189     private void applyRemoteInput(NotificationEntry entry, boolean hasFreeformRemoteInput) {
1190         View bigContentView = mExpandedChild;
1191         if (bigContentView != null) {
1192             mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasFreeformRemoteInput,
1193                     mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput,
1194                     mExpandedWrapper);
1195         } else {
1196             mExpandedRemoteInput = null;
1197         }
1198         if (mCachedExpandedRemoteInput != null
1199                 && mCachedExpandedRemoteInput != mExpandedRemoteInput) {
1200             // We had a cached remote input but didn't reuse it. Clean up required.
1201             mCachedExpandedRemoteInput.dispatchFinishTemporaryDetach();
1202         }
1203         mCachedExpandedRemoteInput = null;
1204 
1205         View headsUpContentView = mHeadsUpChild;
1206         if (headsUpContentView != null) {
1207             mHeadsUpRemoteInput = applyRemoteInput(
1208                     headsUpContentView, entry, hasFreeformRemoteInput,
1209                     mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput, mHeadsUpWrapper);
1210         } else {
1211             mHeadsUpRemoteInput = null;
1212         }
1213         if (mCachedHeadsUpRemoteInput != null
1214                 && mCachedHeadsUpRemoteInput != mHeadsUpRemoteInput) {
1215             // We had a cached remote input but didn't reuse it. Clean up required.
1216             mCachedHeadsUpRemoteInput.dispatchFinishTemporaryDetach();
1217         }
1218         mCachedHeadsUpRemoteInput = null;
1219     }
1220 
1221     private RemoteInputView applyRemoteInput(View view, NotificationEntry entry,
1222             boolean hasRemoteInput, PendingIntent existingPendingIntent,
1223             RemoteInputView cachedView, NotificationViewWrapper wrapper) {
1224         View actionContainerCandidate = view.findViewById(
1225                 com.android.internal.R.id.actions_container);
1226         if (actionContainerCandidate instanceof FrameLayout) {
1227             RemoteInputView existing = (RemoteInputView)
1228                     view.findViewWithTag(RemoteInputView.VIEW_TAG);
1229 
1230             if (existing != null) {
1231                 existing.onNotificationUpdateOrReset();
1232             }
1233 
1234             if (existing == null && hasRemoteInput) {
1235                 ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
1236                 if (cachedView == null) {
1237                     RemoteInputView riv = RemoteInputView.inflate(
1238                             mContext, actionContainer, entry, mRemoteInputController);
1239 
1240                     riv.setVisibility(View.INVISIBLE);
1241                     actionContainer.addView(riv, new LayoutParams(
1242                             ViewGroup.LayoutParams.MATCH_PARENT,
1243                             ViewGroup.LayoutParams.MATCH_PARENT)
1244                     );
1245                     existing = riv;
1246                 } else {
1247                     actionContainer.addView(cachedView);
1248                     cachedView.dispatchFinishTemporaryDetach();
1249                     cachedView.requestFocus();
1250                     existing = cachedView;
1251                 }
1252             }
1253             if (hasRemoteInput) {
1254                 int color = entry.notification.getNotification().color;
1255                 if (color == Notification.COLOR_DEFAULT) {
1256                     color = mContext.getColor(R.color.default_remote_input_background);
1257                 }
1258                 existing.setBackgroundColor(ContrastColorUtil.ensureTextBackgroundColor(color,
1259                         mContext.getColor(R.color.remote_input_text_enabled),
1260                         mContext.getColor(R.color.remote_input_hint)));
1261 
1262                 existing.setWrapper(wrapper);
1263                 existing.setOnVisibilityChangedListener(this::setRemoteInputVisible);
1264 
1265                 if (existingPendingIntent != null || existing.isActive()) {
1266                     // The current action could be gone, or the pending intent no longer valid.
1267                     // If we find a matching action in the new notification, focus, otherwise close.
1268                     Notification.Action[] actions = entry.notification.getNotification().actions;
1269                     if (existingPendingIntent != null) {
1270                         existing.setPendingIntent(existingPendingIntent);
1271                     }
1272                     if (existing.updatePendingIntentFromActions(actions)) {
1273                         if (!existing.isActive()) {
1274                             existing.focus();
1275                         }
1276                     } else {
1277                         if (existing.isActive()) {
1278                             existing.close();
1279                         }
1280                     }
1281                 }
1282             }
1283             return existing;
1284         }
1285         return null;
1286     }
1287 
1288     private void applySmartReplyView(
1289             SmartRepliesAndActions smartRepliesAndActions,
1290             NotificationEntry entry) {
1291         if (mExpandedChild != null) {
1292             mExpandedSmartReplyView = applySmartReplyView(mExpandedChild, smartRepliesAndActions,
1293                     entry, mExpandedInflatedSmartReplies);
1294             if (mExpandedSmartReplyView != null) {
1295                 if (smartRepliesAndActions.smartReplies != null
1296                         || smartRepliesAndActions.smartActions != null) {
1297                     int numSmartReplies = smartRepliesAndActions.smartReplies == null
1298                             ? 0 : smartRepliesAndActions.smartReplies.choices.length;
1299                     int numSmartActions = smartRepliesAndActions.smartActions == null
1300                             ? 0 : smartRepliesAndActions.smartActions.actions.size();
1301                     boolean fromAssistant = smartRepliesAndActions.smartReplies == null
1302                             ? smartRepliesAndActions.smartActions.fromAssistant
1303                             : smartRepliesAndActions.smartReplies.fromAssistant;
1304                     boolean editBeforeSending = smartRepliesAndActions.smartReplies != null
1305                             && mSmartReplyConstants.getEffectiveEditChoicesBeforeSending(
1306                                     smartRepliesAndActions.smartReplies.remoteInput
1307                                             .getEditChoicesBeforeSending());
1308 
1309                     mSmartReplyController.smartSuggestionsAdded(entry, numSmartReplies,
1310                             numSmartActions, fromAssistant, editBeforeSending);
1311                 }
1312             }
1313         }
1314         if (mHeadsUpChild != null && mSmartReplyConstants.getShowInHeadsUp()) {
1315             mHeadsUpSmartReplyView = applySmartReplyView(mHeadsUpChild, smartRepliesAndActions,
1316                     entry, mHeadsUpInflatedSmartReplies);
1317         }
1318     }
1319 
1320     @Nullable
1321     private SmartReplyView applySmartReplyView(View view,
1322             SmartRepliesAndActions smartRepliesAndActions,
1323             NotificationEntry entry, InflatedSmartReplies inflatedSmartReplyView) {
1324         View smartReplyContainerCandidate = view.findViewById(
1325                 com.android.internal.R.id.smart_reply_container);
1326         if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
1327             return null;
1328         }
1329 
1330         LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
1331         if (!InflatedSmartReplies.shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
1332             smartReplyContainer.setVisibility(View.GONE);
1333             return null;
1334         }
1335 
1336         SmartReplyView smartReplyView = null;
1337         if (smartReplyContainer.getChildCount() == 1
1338                 && smartReplyContainer.getChildAt(0) instanceof SmartReplyView) {
1339             // If we already have a SmartReplyView - replace it with the newly inflated one. The
1340             // newly inflated one is connected to the new inflated smart reply/action buttons.
1341             smartReplyContainer.removeAllViews();
1342         }
1343         if (smartReplyContainer.getChildCount() == 0
1344                 && inflatedSmartReplyView != null
1345                 && inflatedSmartReplyView.getSmartReplyView() != null) {
1346             smartReplyView = inflatedSmartReplyView.getSmartReplyView();
1347             smartReplyContainer.addView(smartReplyView);
1348         }
1349         if (smartReplyView != null) {
1350             smartReplyView.resetSmartSuggestions(smartReplyContainer);
1351             smartReplyView.addPreInflatedButtons(
1352                     inflatedSmartReplyView.getSmartSuggestionButtons());
1353             // Ensure the colors of the smart suggestion buttons are up-to-date.
1354             smartReplyView.setBackgroundTintColor(entry.getRow().getCurrentBackgroundTint());
1355             smartReplyContainer.setVisibility(View.VISIBLE);
1356         }
1357         return smartReplyView;
1358     }
1359 
1360     /**
1361      * Set pre-inflated views necessary to display smart replies and actions in the expanded
1362      * notification state.
1363      *
1364      * @param inflatedSmartReplies the pre-inflated state to add to this view. If null the existing
1365      * {@link SmartReplyView} related to the expanded notification state is cleared.
1366      */
1367     public void setExpandedInflatedSmartReplies(
1368             @Nullable InflatedSmartReplies inflatedSmartReplies) {
1369         mExpandedInflatedSmartReplies = inflatedSmartReplies;
1370         if (inflatedSmartReplies == null) {
1371             mExpandedSmartReplyView = null;
1372         }
1373     }
1374 
1375     /**
1376      * Set pre-inflated views necessary to display smart replies and actions in the heads-up
1377      * notification state.
1378      *
1379      * @param inflatedSmartReplies the pre-inflated state to add to this view. If null the existing
1380      * {@link SmartReplyView} related to the heads-up notification state is cleared.
1381      */
1382     public void setHeadsUpInflatedSmartReplies(
1383             @Nullable InflatedSmartReplies inflatedSmartReplies) {
1384         mHeadsUpInflatedSmartReplies = inflatedSmartReplies;
1385         if (inflatedSmartReplies == null) {
1386             mHeadsUpSmartReplyView = null;
1387         }
1388     }
1389 
1390     /**
1391      * Returns the smart replies and actions currently shown in the notification.
1392      */
1393     @Nullable public SmartRepliesAndActions getCurrentSmartRepliesAndActions() {
1394         return mCurrentSmartRepliesAndActions;
1395     }
1396 
1397     public void closeRemoteInput() {
1398         if (mHeadsUpRemoteInput != null) {
1399             mHeadsUpRemoteInput.close();
1400         }
1401         if (mExpandedRemoteInput != null) {
1402             mExpandedRemoteInput.close();
1403         }
1404     }
1405 
1406     public void setGroupManager(NotificationGroupManager groupManager) {
1407         mGroupManager = groupManager;
1408     }
1409 
1410     public void setRemoteInputController(RemoteInputController r) {
1411         mRemoteInputController = r;
1412     }
1413 
1414     public void setExpandClickListener(OnClickListener expandClickListener) {
1415         mExpandClickListener = expandClickListener;
1416     }
1417 
1418     public void updateExpandButtons(boolean expandable) {
1419         mExpandable = expandable;
1420         // if the expanded child has the same height as the collapsed one we hide it.
1421         if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
1422             if ((!mIsHeadsUp && !mHeadsUpAnimatingAway)
1423                     || mHeadsUpChild == null || !mContainingNotification.canShowHeadsUp()) {
1424                 if (mExpandedChild.getHeight() <= mContractedChild.getHeight()) {
1425                     expandable = false;
1426                 }
1427             } else if (mExpandedChild.getHeight() <= mHeadsUpChild.getHeight()) {
1428                 expandable = false;
1429             }
1430         }
1431         if (mExpandedChild != null) {
1432             mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
1433         }
1434         if (mContractedChild != null) {
1435             mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
1436         }
1437         if (mHeadsUpChild != null) {
1438             mHeadsUpWrapper.updateExpandability(expandable,  mExpandClickListener);
1439         }
1440         mIsContentExpandable = expandable;
1441     }
1442 
1443     public NotificationHeaderView getNotificationHeader() {
1444         NotificationHeaderView header = null;
1445         if (mContractedChild != null) {
1446             header = mContractedWrapper.getNotificationHeader();
1447         }
1448         if (header == null && mExpandedChild != null) {
1449             header = mExpandedWrapper.getNotificationHeader();
1450         }
1451         if (header == null && mHeadsUpChild != null) {
1452             header = mHeadsUpWrapper.getNotificationHeader();
1453         }
1454         return header;
1455     }
1456 
1457     public void showAppOpsIcons(ArraySet<Integer> activeOps) {
1458         if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
1459             mContractedWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
1460         }
1461         if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
1462             mExpandedWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
1463         }
1464         if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
1465             mHeadsUpWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
1466         }
1467     }
1468 
1469     /** Sets whether the notification being displayed audibly alerted the user. */
1470     public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
1471         if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
1472             mContractedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
1473         }
1474         if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
1475             mExpandedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
1476         }
1477         if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
1478             mHeadsUpWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
1479         }
1480     }
1481 
1482     public NotificationHeaderView getContractedNotificationHeader() {
1483         if (mContractedChild != null) {
1484             return mContractedWrapper.getNotificationHeader();
1485         }
1486         return null;
1487     }
1488 
1489     public NotificationHeaderView getVisibleNotificationHeader() {
1490         NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
1491         return wrapper == null ? null : wrapper.getNotificationHeader();
1492     }
1493 
1494     public void setContainingNotification(ExpandableNotificationRow containingNotification) {
1495         mContainingNotification = containingNotification;
1496     }
1497 
1498     public void requestSelectLayout(boolean needsAnimation) {
1499         selectLayout(needsAnimation, false);
1500     }
1501 
1502     public void reInflateViews() {
1503         if (mIsChildInGroup && mSingleLineView != null) {
1504             removeView(mSingleLineView);
1505             mSingleLineView = null;
1506             updateAllSingleLineViews();
1507         }
1508     }
1509 
1510     public void setUserExpanding(boolean userExpanding) {
1511         mUserExpanding = userExpanding;
1512         if (userExpanding) {
1513             mTransformationStartVisibleType = mVisibleType;
1514         } else {
1515             mTransformationStartVisibleType = UNDEFINED;
1516             mVisibleType = calculateVisibleType();
1517             updateViewVisibilities(mVisibleType);
1518             updateBackgroundColor(false);
1519         }
1520     }
1521 
1522     /**
1523      * Set by how much the single line view should be indented. Used when a overflow indicator is
1524      * present and only during measuring
1525      */
1526     public void setSingleLineWidthIndention(int singleLineWidthIndention) {
1527         if (singleLineWidthIndention != mSingleLineWidthIndention) {
1528             mSingleLineWidthIndention = singleLineWidthIndention;
1529             mContainingNotification.forceLayout();
1530             forceLayout();
1531         }
1532     }
1533 
1534     public HybridNotificationView getSingleLineView() {
1535         return mSingleLineView;
1536     }
1537 
1538     public void setRemoved() {
1539         if (mExpandedRemoteInput != null) {
1540             mExpandedRemoteInput.setRemoved();
1541         }
1542         if (mHeadsUpRemoteInput != null) {
1543             mHeadsUpRemoteInput.setRemoved();
1544         }
1545         if (mExpandedWrapper != null) {
1546             mExpandedWrapper.setRemoved();
1547         }
1548         if (mContractedWrapper != null) {
1549             mContractedWrapper.setRemoved();
1550         }
1551         if (mHeadsUpWrapper != null) {
1552             mHeadsUpWrapper.setRemoved();
1553         }
1554     }
1555 
1556     public void setContentHeightAnimating(boolean animating) {
1557         if (!animating) {
1558             mContentHeightAtAnimationStart = UNDEFINED;
1559         }
1560     }
1561 
1562     @VisibleForTesting
1563     boolean isAnimatingVisibleType() {
1564         return mAnimationStartVisibleType != UNDEFINED;
1565     }
1566 
1567     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
1568         mHeadsUpAnimatingAway = headsUpAnimatingAway;
1569         selectLayout(false /* animate */, true /* force */);
1570     }
1571 
1572     public void setFocusOnVisibilityChange() {
1573         mFocusOnVisibilityChange = true;
1574     }
1575 
1576     public void setIconsVisible(boolean iconsVisible) {
1577         mIconsVisible = iconsVisible;
1578         updateIconVisibilities();
1579     }
1580 
1581     private void updateIconVisibilities() {
1582         if (mContractedWrapper != null) {
1583             NotificationHeaderView header = mContractedWrapper.getNotificationHeader();
1584             if (header != null) {
1585                 header.getIcon().setForceHidden(!mIconsVisible);
1586             }
1587         }
1588         if (mHeadsUpWrapper != null) {
1589             NotificationHeaderView header = mHeadsUpWrapper.getNotificationHeader();
1590             if (header != null) {
1591                 header.getIcon().setForceHidden(!mIconsVisible);
1592             }
1593         }
1594         if (mExpandedWrapper != null) {
1595             NotificationHeaderView header = mExpandedWrapper.getNotificationHeader();
1596             if (header != null) {
1597                 header.getIcon().setForceHidden(!mIconsVisible);
1598             }
1599         }
1600     }
1601 
1602     @Override
1603     public void onVisibilityAggregated(boolean isVisible) {
1604         super.onVisibilityAggregated(isVisible);
1605         if (isVisible) {
1606             fireExpandedVisibleListenerIfVisible();
1607         }
1608     }
1609 
1610     /**
1611      * Sets a one-shot listener for when the expanded view becomes visible.
1612      *
1613      * This will fire the listener immediately if the expanded view is already visible.
1614      */
1615     public void setOnExpandedVisibleListener(Runnable r) {
1616         mExpandedVisibleListener = r;
1617         fireExpandedVisibleListenerIfVisible();
1618     }
1619 
1620     /**
1621      * Set a one-shot listener to run when a given content view becomes inactive.
1622      *
1623      * @param visibleType visible type corresponding to the content view to listen
1624      * @param listener runnable to run once when the content view becomes inactive
1625      */
1626     public void performWhenContentInactive(int visibleType, Runnable listener) {
1627         View view = getViewForVisibleType(visibleType);
1628         // View is already inactive
1629         if (view == null || isContentViewInactive(visibleType)) {
1630             listener.run();
1631             return;
1632         }
1633         mOnContentViewInactiveListeners.put(view, listener);
1634     }
1635 
1636     /**
1637      * Whether or not the content view is inactive.  This means it should not be visible
1638      * or the showing content as removing it would cause visual jank.
1639      *
1640      * @param visibleType visible type corresponding to the content view to be removed
1641      * @return true if the content view is inactive, false otherwise
1642      */
1643     public boolean isContentViewInactive(int visibleType) {
1644         View view = getViewForVisibleType(visibleType);
1645         return isContentViewInactive(view);
1646     }
1647 
1648     /**
1649      * Whether or not the content view is inactive.
1650      *
1651      * @param view view to see if its inactive
1652      * @return true if the view is inactive, false o/w
1653      */
1654     private boolean isContentViewInactive(View view) {
1655         if (view == null) {
1656             return true;
1657         }
1658         return !isShown()
1659                 || (view.getVisibility() != VISIBLE && getViewForVisibleType(mVisibleType) != view);
1660     }
1661 
1662     @Override
1663     protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
1664         super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
1665         if (isContentViewInactive(child)) {
1666             Runnable listener = mOnContentViewInactiveListeners.remove(child);
1667             if (listener != null) {
1668                 listener.run();
1669             }
1670         }
1671     }
1672 
1673     public void setIsLowPriority(boolean isLowPriority) {
1674         mIsLowPriority = isLowPriority;
1675     }
1676 
1677     public boolean isDimmable() {
1678         return mContractedWrapper != null && mContractedWrapper.isDimmable();
1679     }
1680 
1681     /**
1682      * Should a single click be disallowed on this view when on the keyguard?
1683      */
1684     public boolean disallowSingleClick(float x, float y) {
1685         NotificationViewWrapper visibleWrapper = getVisibleWrapper(getVisibleType());
1686         if (visibleWrapper != null) {
1687             return visibleWrapper.disallowSingleClick(x, y);
1688         }
1689         return false;
1690     }
1691 
1692     public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
1693         boolean needsPaddings = shouldClipToRounding(getVisibleType(), topRounded, bottomRounded);
1694         if (mUserExpanding) {
1695              needsPaddings |= shouldClipToRounding(mTransformationStartVisibleType, topRounded,
1696                      bottomRounded);
1697         }
1698         return needsPaddings;
1699     }
1700 
1701     private boolean shouldClipToRounding(int visibleType, boolean topRounded,
1702             boolean bottomRounded) {
1703         NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
1704         if (visibleWrapper == null) {
1705             return false;
1706         }
1707         return visibleWrapper.shouldClipToRounding(topRounded, bottomRounded);
1708     }
1709 
1710     public CharSequence getActiveRemoteInputText() {
1711         if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
1712             return mExpandedRemoteInput.getText();
1713         }
1714         if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
1715             return mHeadsUpRemoteInput.getText();
1716         }
1717         return null;
1718     }
1719 
1720     @Override
1721     public boolean dispatchTouchEvent(MotionEvent ev) {
1722         float y = ev.getY();
1723         // We still want to distribute touch events to the remote input even if it's outside the
1724         // view boundary. We're therefore manually dispatching these events to the remote view
1725         RemoteInputView riv = getRemoteInputForView(getViewForVisibleType(mVisibleType));
1726         if (riv != null && riv.getVisibility() == VISIBLE) {
1727             int inputStart = mUnrestrictedContentHeight - riv.getHeight();
1728             if (y <= mUnrestrictedContentHeight && y >= inputStart) {
1729                 ev.offsetLocation(0, -inputStart);
1730                 return riv.dispatchTouchEvent(ev);
1731             }
1732         }
1733         return super.dispatchTouchEvent(ev);
1734     }
1735 
1736     /**
1737      * Overridden to make sure touches to the reply action bar actually go through to this view
1738      */
1739     @Override
1740     public boolean pointInView(float localX, float localY, float slop) {
1741         float top = mClipTopAmount;
1742         float bottom = mUnrestrictedContentHeight;
1743         return localX >= -slop && localY >= top - slop && localX < ((mRight - mLeft) + slop) &&
1744                 localY < (bottom + slop);
1745     }
1746 
1747     private RemoteInputView getRemoteInputForView(View child) {
1748         if (child == mExpandedChild) {
1749             return mExpandedRemoteInput;
1750         } else if (child == mHeadsUpChild) {
1751             return mHeadsUpRemoteInput;
1752         }
1753         return null;
1754     }
1755 
1756     public int getExpandHeight() {
1757         int viewType = VISIBLE_TYPE_EXPANDED;
1758         if (mExpandedChild == null) {
1759             viewType = VISIBLE_TYPE_CONTRACTED;
1760         }
1761         return getViewHeight(viewType) + getExtraRemoteInputHeight(mExpandedRemoteInput);
1762     }
1763 
1764     public int getHeadsUpHeight(boolean forceNoHeader) {
1765         int viewType = VISIBLE_TYPE_HEADSUP;
1766         if (mHeadsUpChild == null) {
1767             viewType = VISIBLE_TYPE_CONTRACTED;
1768         }
1769         // The headsUp remote input quickly switches to the expanded one, so lets also include that
1770         // one
1771         return getViewHeight(viewType, forceNoHeader)
1772                 + getExtraRemoteInputHeight(mHeadsUpRemoteInput)
1773                 + getExtraRemoteInputHeight(mExpandedRemoteInput);
1774     }
1775 
1776     public void setRemoteInputVisible(boolean remoteInputVisible) {
1777         mRemoteInputVisible = remoteInputVisible;
1778         setClipChildren(!remoteInputVisible);
1779     }
1780 
1781     @Override
1782     public void setClipChildren(boolean clipChildren) {
1783         clipChildren = clipChildren && !mRemoteInputVisible;
1784         super.setClipChildren(clipChildren);
1785     }
1786 
1787     public void setHeaderVisibleAmount(float headerVisibleAmount) {
1788         if (mContractedWrapper != null) {
1789             mContractedWrapper.setHeaderVisibleAmount(headerVisibleAmount);
1790         }
1791         if (mHeadsUpWrapper != null) {
1792             mHeadsUpWrapper.setHeaderVisibleAmount(headerVisibleAmount);
1793         }
1794         if (mExpandedWrapper != null) {
1795             mExpandedWrapper.setHeaderVisibleAmount(headerVisibleAmount);
1796         }
1797     }
1798 
1799     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1800         pw.print("    ");
1801         pw.print("contentView visibility: " + getVisibility());
1802         pw.print(", alpha: " + getAlpha());
1803         pw.print(", clipBounds: " + getClipBounds());
1804         pw.print(", contentHeight: " + mContentHeight);
1805         pw.print(", visibleType: " + mVisibleType);
1806         View view = getViewForVisibleType(mVisibleType);
1807         pw.print(", visibleView ");
1808         if (view != null) {
1809             pw.print(" visibility: " + view.getVisibility());
1810             pw.print(", alpha: " + view.getAlpha());
1811             pw.print(", clipBounds: " + view.getClipBounds());
1812         } else {
1813             pw.print("null");
1814         }
1815         pw.println();
1816     }
1817 
1818     public RemoteInputView getExpandedRemoteInput() {
1819         return mExpandedRemoteInput;
1820     }
1821 }
1822