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