• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.app.Flags.notificationsRedesignTemplates;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.Flags;
24 import android.app.Notification;
25 import android.app.PendingIntent;
26 import android.content.Context;
27 import android.graphics.Canvas;
28 import android.graphics.Rect;
29 import android.graphics.drawable.Drawable;
30 import android.os.Build;
31 import android.os.RemoteException;
32 import android.os.Trace;
33 import android.service.notification.StatusBarNotification;
34 import android.util.ArrayMap;
35 import android.util.AttributeSet;
36 import android.util.IndentingPrintWriter;
37 import android.util.Log;
38 import android.view.LayoutInflater;
39 import android.view.MotionEvent;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.ViewTreeObserver;
43 import android.view.accessibility.AccessibilityEvent;
44 import android.widget.FrameLayout;
45 import android.widget.ImageView;
46 import android.widget.LinearLayout;
47 
48 import androidx.annotation.MainThread;
49 
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.logging.UiEventLogger;
52 import com.android.internal.statusbar.IStatusBarService;
53 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
54 import com.android.systemui.res.R;
55 import com.android.systemui.statusbar.RemoteInputController;
56 import com.android.systemui.statusbar.SmartReplyController;
57 import com.android.systemui.statusbar.TransformableView;
58 import com.android.systemui.statusbar.notification.FeedbackIcon;
59 import com.android.systemui.statusbar.notification.NotificationFadeAware;
60 import com.android.systemui.statusbar.notification.NotificationUtils;
61 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
62 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
63 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
64 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
65 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactHeadsUpTemplateViewWrapper;
66 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
67 import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
68 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
69 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
70 import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
71 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
72 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
73 import com.android.systemui.statusbar.policy.RemoteInputView;
74 import com.android.systemui.statusbar.policy.RemoteInputViewController;
75 import com.android.systemui.statusbar.policy.SmartReplyConstants;
76 import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt;
77 import com.android.systemui.statusbar.policy.SmartReplyView;
78 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
79 import com.android.systemui.util.Compile;
80 import com.android.systemui.util.DumpUtilsKt;
81 
82 import java.io.PrintWriter;
83 import java.util.ArrayList;
84 import java.util.Collections;
85 import java.util.List;
86 
87 /**
88  * A frame layout containing the actual payload of the notification, including the contracted,
89  * expanded and heads up layout. This class is responsible for clipping the content and
90  * switching between the expanded, contracted and the heads up view depending on its clipped size.
91  */
92 public class NotificationContentView extends FrameLayout implements NotificationFadeAware {
93 
94     private static final String TAG = "NotificationContentView";
95     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
96     public static final int VISIBLE_TYPE_CONTRACTED = 0;
97     public static final int VISIBLE_TYPE_EXPANDED = 1;
98     public static final int VISIBLE_TYPE_HEADSUP = 2;
99     public static final int VISIBLE_TYPE_SINGLELINE = 3;
100     /**
101      * Used when there is no content on the view such as when we're a public layout but don't
102      * need to show.
103      */
104     private static final int VISIBLE_TYPE_NONE = -1;
105 
106     private static final int UNDEFINED = -1;
107 
108     protected static final boolean INCLUDE_HEIGHTS_TO_DUMP = true;
109 
110     private final Rect mClipBounds = new Rect();
111 
112     private int mMinContractedHeight;
113     private int mMinSingleLineHeight;
114     private View mContractedChild;
115     private View mExpandedChild;
116     private View mHeadsUpChild;
117     @VisibleForTesting
118     protected HybridNotificationView mSingleLineView;
119 
120     private RemoteInputView mExpandedRemoteInput;
121     private RemoteInputView mHeadsUpRemoteInput;
122 
123     private SmartReplyConstants mSmartReplyConstants;
124     private SmartReplyView mExpandedSmartReplyView;
125     private SmartReplyView mHeadsUpSmartReplyView;
126     @Nullable private RemoteInputViewController mExpandedRemoteInputController;
127     @Nullable private RemoteInputViewController mHeadsUpRemoteInputController;
128     private SmartReplyController mSmartReplyController;
129     private InflatedSmartReplyViewHolder mExpandedInflatedSmartReplies;
130     private InflatedSmartReplyViewHolder mHeadsUpInflatedSmartReplies;
131     private InflatedSmartReplyState mCurrentSmartReplyState;
132 
133     private NotificationViewWrapper mContractedWrapper;
134     private NotificationViewWrapper mExpandedWrapper;
135     private NotificationViewWrapper mHeadsUpWrapper;
136     @Nullable private NotificationViewWrapper mShownWrapper = null;
137     private final HybridGroupManager mHybridGroupManager;
138     private int mClipTopAmount;
139     private int mContentHeight;
140     private int mVisibleType = VISIBLE_TYPE_NONE;
141     private boolean mAnimate;
142     private boolean mIsHeadsUp;
143     private boolean mLegacy;
144     private boolean mIsChildInGroup;
145     private int mSmallHeight;
146     private int mHeadsUpHeight;
147     private int mNotificationMaxHeight;
148     private NotificationEntry mNotificationEntry;
149     private RemoteInputController mRemoteInputController;
150     private Runnable mExpandedVisibleListener;
151     private PeopleNotificationIdentifier mPeopleIdentifier;
152     private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory;
153     private IStatusBarService mStatusBarService;
154     private boolean mBubblesEnabledForUser;
155 
156     /**
157      * List of listeners for when content views become inactive (i.e. not the showing view).
158      */
159     private final ArrayMap<View, Runnable> mOnContentViewInactiveListeners = new ArrayMap<>();
160 
161     private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
162             = new ViewTreeObserver.OnPreDrawListener() {
163         @Override
164         public boolean onPreDraw() {
165             // We need to post since we don't want the notification to animate on the very first
166             // frame
167             post(new Runnable() {
168                 @Override
169                 public void run() {
170                     mAnimate = true;
171                 }
172             });
173             getViewTreeObserver().removeOnPreDrawListener(this);
174             return true;
175         }
176     };
177 
178     private OnClickListener mExpandClickListener;
179     private boolean mBeforeN;
180     private boolean mExpandable;
181     private boolean mClipToActualHeight = true;
182     private ExpandableNotificationRow mContainingNotification;
183     /** The visible type at the start of a touch driven transformation */
184     private int mTransformationStartVisibleType;
185     /** The visible type at the start of an animation driven transformation */
186     private int mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
187     private boolean mUserExpanding;
188     private int mSingleLineWidthIndention;
189     private boolean mForceSelectNextLayout = true;
190 
191     // Cache for storing the RemoteInputView during a notification update. Needed because
192     // setExpandedChild sets the actual field to null, but then onNotificationUpdated will restore
193     // it from the cache, if present, otherwise inflate a new one.
194     // ONLY USED WHEN THE ORIGINAL WAS isActive() WHEN REPLACED
195     private RemoteInputView mCachedExpandedRemoteInput;
196     private RemoteInputView mCachedHeadsUpRemoteInput;
197     private RemoteInputViewController mCachedExpandedRemoteInputViewController;
198     private RemoteInputViewController mCachedHeadsUpRemoteInputViewController;
199     private PendingIntent mPreviousExpandedRemoteInputIntent;
200     private PendingIntent mPreviousHeadsUpRemoteInputIntent;
201 
202     private int mContentHeightAtAnimationStart = UNDEFINED;
203     private boolean mFocusOnVisibilityChange;
204     private boolean mHeadsUpAnimatingAway;
205     private int mClipBottomAmount;
206     private boolean mIsContentExpandable;
207     private boolean mRemoteInputVisible;
208     private int mUnrestrictedContentHeight;
209 
210     private boolean mContentAnimating;
211     private UiEventLogger mUiEventLogger;
212 
213     private boolean mIsHUNCompact;
214 
NotificationContentView(Context context, AttributeSet attrs)215     public NotificationContentView(Context context, AttributeSet attrs) {
216         super(context, attrs);
217         mHybridGroupManager = new HybridGroupManager(getContext());
218         reinflate();
219     }
220 
initialize( PeopleNotificationIdentifier peopleNotificationIdentifier, RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController, IStatusBarService statusBarService, UiEventLogger uiEventLogger)221     public void initialize(
222             PeopleNotificationIdentifier peopleNotificationIdentifier,
223             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
224             SmartReplyConstants smartReplyConstants,
225             SmartReplyController smartReplyController,
226             IStatusBarService statusBarService,
227             UiEventLogger uiEventLogger) {
228         mPeopleIdentifier = peopleNotificationIdentifier;
229         mRemoteInputSubcomponentFactory = rivSubcomponentFactory;
230         mSmartReplyConstants = smartReplyConstants;
231         mSmartReplyController = smartReplyController;
232         mStatusBarService = statusBarService;
233         mUiEventLogger = uiEventLogger;
234         // We set root namespace so that we avoid searching children for id. Notification  might
235         // contain custom view and their ids may clash with ids already existing in shade or
236         // notification panel
237         setIsRootNamespace(true);
238     }
239 
240     @Override
focusSearch(View focused, int direction)241     public View focusSearch(View focused, int direction) {
242         // This implementation is copied from ViewGroup but with removed special handling of
243         // setIsRootNamespace. This view is set as tree root using setIsRootNamespace and it
244         // causes focus to be stuck inside of it. We need to be root to avoid id conflicts
245         // but we don't want to behave like root when it comes to focusing.
246         if (mParent != null) {
247             return mParent.focusSearch(focused, direction);
248         }
249         Log.wtf(TAG, "NotificationContentView doesn't have parent");
250         return null;
251     }
252 
reinflate()253     public void reinflate() {
254         mMinContractedHeight = getResources().getDimensionPixelSize(
255                 R.dimen.min_notification_layout_height);
256         if (AsyncHybridViewInflation.isEnabled()) {
257             //TODO (b/217799515): single-line view height is the greater of two heights: text view
258             // height and icon height (when there's an icon). icon height is fixed to be
259             // conversation_single_line_face_pile_size (24dp), the text view's height is 16sp,
260             // its pixel height changes with the system's font scaling factor.
261             mMinSingleLineHeight = getResources().getDimensionPixelSize(
262                     R.dimen.conversation_single_line_face_pile_size);
263         }
264     }
265 
setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight)266     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight) {
267         mSmallHeight = smallHeight;
268         mHeadsUpHeight = headsUpMaxHeight;
269         mNotificationMaxHeight = maxHeight;
270     }
271 
272     // This logic is mirrored in FrameLayoutWithMaxHeight.onMeasure in AODPromotedNotification.kt.
273     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)274     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
275         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
276         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
277         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
278         int maxSize = Integer.MAX_VALUE / 2;
279         int width = MeasureSpec.getSize(widthMeasureSpec);
280         if (hasFixedHeight || isHeightLimited) {
281             maxSize = MeasureSpec.getSize(heightMeasureSpec);
282         }
283         int maxChildHeight = 0;
284         if (mExpandedChild != null) {
285             int notificationMaxHeight = mNotificationMaxHeight;
286             if (mExpandedSmartReplyView != null) {
287                 notificationMaxHeight += mExpandedSmartReplyView.getHeightUpperLimit();
288             }
289             notificationMaxHeight += mExpandedWrapper.getExtraMeasureHeight();
290             int size = notificationMaxHeight;
291             ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
292             boolean useExactly = false;
293             if (layoutParams.height >= 0) {
294                 // An actual height is set
295                 size = Math.min(size, layoutParams.height);
296                 useExactly = true;
297             }
298             int spec = MeasureSpec.makeMeasureSpec(size, useExactly
299                     ? MeasureSpec.EXACTLY
300                     : MeasureSpec.AT_MOST);
301             measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
302             maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
303         }
304         if (mContractedChild != null) {
305             int heightSpec;
306             int size = mSmallHeight;
307             ViewGroup.LayoutParams layoutParams = mContractedChild.getLayoutParams();
308             boolean useExactly = false;
309             if (layoutParams.height >= 0) {
310                 // An actual height is set
311                 size = Math.min(size, layoutParams.height);
312                 useExactly = true;
313             }
314             if (shouldContractedBeFixedSize() || useExactly) {
315                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
316             } else {
317                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
318             }
319             measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
320             int measuredHeight = mContractedChild.getMeasuredHeight();
321             if (measuredHeight < mMinContractedHeight) {
322                 heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
323                 measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
324             }
325             maxChildHeight = Math.max(maxChildHeight, measuredHeight);
326             if (mExpandedChild != null
327                     && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
328                 // the Expanded child is smaller then the collapsed. Let's remeasure it.
329                 heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
330                         MeasureSpec.EXACTLY);
331                 measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
332             }
333         }
334         if (mHeadsUpChild != null) {
335             int maxHeight = mHeadsUpHeight;
336             if (mHeadsUpSmartReplyView != null) {
337                 maxHeight += mHeadsUpSmartReplyView.getHeightUpperLimit();
338             }
339             maxHeight += mHeadsUpWrapper.getExtraMeasureHeight();
340             int size = maxHeight;
341             ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
342             boolean useExactly = false;
343             if (layoutParams.height >= 0) {
344                 // An actual height is set
345                 size = Math.min(size, layoutParams.height);
346                 useExactly = true;
347             }
348             measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
349                     MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
350                             : MeasureSpec.AT_MOST), 0);
351             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
352         }
353         if (mSingleLineView != null) {
354             int singleLineWidthSpec = widthMeasureSpec;
355             if (mSingleLineWidthIndention != 0
356                     && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
357                 singleLineWidthSpec = MeasureSpec.makeMeasureSpec(
358                         width - mSingleLineWidthIndention + mSingleLineView.getPaddingEnd(),
359                         MeasureSpec.EXACTLY);
360             }
361             mSingleLineView.measure(singleLineWidthSpec,
362                     MeasureSpec.makeMeasureSpec(mNotificationMaxHeight, MeasureSpec.AT_MOST));
363             maxChildHeight = Math.max(maxChildHeight, mSingleLineView.getMeasuredHeight());
364         }
365         int ownHeight = Math.min(maxChildHeight, maxSize);
366         setMeasuredDimension(width, ownHeight);
367     }
368 
369     /**
370      * Get the extra height that needs to be added to the notification height for a given
371      * {@link RemoteInputView}.
372      * This is needed when the user is inline replying in order to ensure that the reply bar has
373      * enough padding.
374      *
375      * @param remoteInput The remote input to check.
376      * @return The extra height needed.
377      */
getExtraRemoteInputHeight(RemoteInputView remoteInput)378     private int getExtraRemoteInputHeight(RemoteInputView remoteInput) {
379         if (remoteInput != null && (remoteInput.isActive() || remoteInput.isSending())) {
380             return getResources().getDimensionPixelSize(
381                     com.android.internal.R.dimen.notification_content_margin);
382         }
383         return 0;
384     }
385 
shouldContractedBeFixedSize()386     private boolean shouldContractedBeFixedSize() {
387         return mBeforeN && mContractedWrapper instanceof NotificationCustomViewWrapper;
388     }
389 
390     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)391     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
392         int previousHeight = 0;
393         if (mExpandedChild != null) {
394             previousHeight = mExpandedChild.getHeight();
395         }
396         super.onLayout(changed, left, top, right, bottom);
397         if (previousHeight != 0 && mExpandedChild.getHeight() != previousHeight) {
398             mContentHeightAtAnimationStart = previousHeight;
399         }
400         updateClipping();
401         invalidateOutline();
402         selectLayout(false /* animate */, mForceSelectNextLayout /* force */);
403         mForceSelectNextLayout = false;
404         // TODO(b/182314698): move this to onMeasure.  This requires switching to getMeasuredHeight,
405         //  and also requires revisiting all of the logic called earlier in this method.
406         updateExpandButtonsDuringLayout(mExpandable, true /* duringLayout */);
407     }
408 
409     @Override
onAttachedToWindow()410     protected void onAttachedToWindow() {
411         super.onAttachedToWindow();
412         updateVisibility();
413     }
414 
getContractedChild()415     public View getContractedChild() {
416         return mContractedChild;
417     }
418 
getExpandedChild()419     public View getExpandedChild() {
420         return mExpandedChild;
421     }
422 
getHeadsUpChild()423     public View getHeadsUpChild() {
424         return mHeadsUpChild;
425     }
426 
427     /**
428      * Sets the contracted view. Child may be null to remove the content view.
429      *
430      * @param child contracted content view to set
431      */
setContractedChild(@ullable View child)432     public void setContractedChild(@Nullable View child) {
433         if (mContractedChild != null) {
434             mOnContentViewInactiveListeners.remove(mContractedChild);
435             mContractedChild.animate().cancel();
436             removeView(mContractedChild);
437         }
438         if (child == null) {
439             mContractedChild = null;
440             mContractedWrapper = null;
441             if (mTransformationStartVisibleType == VISIBLE_TYPE_CONTRACTED) {
442                 mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
443             }
444             return;
445         }
446         addView(child);
447         mContractedChild = child;
448         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
449                 mContainingNotification);
450         // The contracted wrapper has changed. If this is the shown wrapper, we need to update it.
451         updateShownWrapper(mVisibleType);
452     }
453 
getWrapperForView(View child)454     private NotificationViewWrapper getWrapperForView(View child) {
455         if (child == mContractedChild) {
456             return mContractedWrapper;
457         }
458         if (child == mExpandedChild) {
459             return mExpandedWrapper;
460         }
461         if (child == mHeadsUpChild) {
462             return mHeadsUpWrapper;
463         }
464         return null;
465     }
466 
467     /**
468      * Sets the expanded view. Child may be null to remove the content view.
469      *
470      * @param child expanded content view to set
471      */
setExpandedChild(@ullable View child)472     public void setExpandedChild(@Nullable View child) {
473         if (mExpandedChild != null) {
474             mPreviousExpandedRemoteInputIntent = null;
475             if (mExpandedRemoteInput != null) {
476                 mExpandedRemoteInput.onNotificationUpdateOrReset();
477                 if (mExpandedRemoteInput.isActive()) {
478                     if (mExpandedRemoteInputController != null) {
479                         mPreviousExpandedRemoteInputIntent =
480                                 mExpandedRemoteInputController.getPendingIntent();
481                     }
482                     mCachedExpandedRemoteInput = mExpandedRemoteInput;
483                     mCachedExpandedRemoteInputViewController = mExpandedRemoteInputController;
484                     mExpandedRemoteInput.dispatchStartTemporaryDetach();
485                     ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
486                 }
487             }
488             mOnContentViewInactiveListeners.remove(mExpandedChild);
489             mExpandedChild.animate().cancel();
490             removeView(mExpandedChild);
491             mExpandedRemoteInput = null;
492             if (mExpandedRemoteInputController != null) {
493                 mExpandedRemoteInputController.unbind();
494             }
495             mExpandedRemoteInputController = null;
496         }
497         if (child == null) {
498             mExpandedChild = null;
499             mExpandedWrapper = null;
500             if (mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED) {
501                 mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
502             }
503             if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
504                 selectLayout(false /* animate */, true /* force */);
505             }
506             return;
507         }
508         addView(child);
509         mExpandedChild = child;
510         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
511                 mContainingNotification);
512         if (mContainingNotification != null) {
513             applySystemActions(mExpandedChild, mContainingNotification.getEntry());
514         }
515         // The expanded wrapper has changed. If this is the shown wrapper, we need to update it.
516         updateShownWrapper(mVisibleType);
517     }
518 
519     /**
520      * Sets the heads up view. Child may be null to remove the content view.
521      *
522      * @param child heads up content view to set
523      */
setHeadsUpChild(@ullable View child)524     public void setHeadsUpChild(@Nullable View child) {
525         if (mHeadsUpChild != null) {
526             mPreviousHeadsUpRemoteInputIntent = null;
527             if (mHeadsUpRemoteInput != null) {
528                 mHeadsUpRemoteInput.onNotificationUpdateOrReset();
529                 if (mHeadsUpRemoteInput.isActive()) {
530                     if (mHeadsUpRemoteInputController != null) {
531                         mPreviousHeadsUpRemoteInputIntent =
532                                 mHeadsUpRemoteInputController.getPendingIntent();
533                     }
534                     mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
535                     mCachedHeadsUpRemoteInputViewController = mHeadsUpRemoteInputController;
536                     mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
537                     ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
538                 }
539             }
540             mOnContentViewInactiveListeners.remove(mHeadsUpChild);
541             mHeadsUpChild.animate().cancel();
542             removeView(mHeadsUpChild);
543             mHeadsUpRemoteInput = null;
544             if (mHeadsUpRemoteInputController != null) {
545                 mHeadsUpRemoteInputController.unbind();
546             }
547             mHeadsUpRemoteInputController = null;
548         }
549         if (child == null) {
550             mHeadsUpChild = null;
551             mHeadsUpWrapper = null;
552             mIsHUNCompact = false;
553             if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
554                 mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
555             }
556             if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
557                 selectLayout(false /* animate */, true /* force */);
558             }
559             return;
560         }
561         addView(child);
562         mHeadsUpChild = child;
563         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
564                 mContainingNotification);
565 
566         mIsHUNCompact = Flags.compactHeadsUpNotification()
567                 && mHeadsUpWrapper instanceof NotificationCompactHeadsUpTemplateViewWrapper;
568         if (mIsHUNCompact) {
569             logCompactHUNShownEvent();
570         }
571 
572         if (mContainingNotification != null) {
573             applySystemActions(mHeadsUpChild, mContainingNotification.getEntry());
574         }
575         // The heads up wrapper has changed. If this is the shown wrapper, we need to update it.
576         updateShownWrapper(mVisibleType);
577     }
578 
logCompactHUNShownEvent()579     private void logCompactHUNShownEvent() {
580         if (mUiEventLogger == null) {
581             return;
582         }
583 
584         final StatusBarNotification containingRowSbn = getContainingRowSbn();
585         if (containingRowSbn == null) {
586             return;
587         }
588 
589         mUiEventLogger.logWithInstanceId(
590                 NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN,
591                 containingRowSbn.getUid(),
592                 containingRowSbn.getPackageName(),
593                 containingRowSbn.getInstanceId());
594     }
595 
596     @Nullable
getContainingRowSbn()597     private StatusBarNotification getContainingRowSbn() {
598         if (mContainingNotification == null) {
599             return null;
600         }
601         if (NotificationBundleUi.isEnabled()) {
602             return mContainingNotification.getEntryAdapter().getSbn();
603         } else {
604             return mContainingNotification.getEntryLegacy().getSbn();
605         }
606     }
607 
608     /**
609      * Sets the single-line view. Child may be null to remove the view.
610      * @param child single-line content view to set
611      */
setSingleLineView(@ullable HybridNotificationView child)612     public void setSingleLineView(@Nullable HybridNotificationView child) {
613         if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
614         if (mSingleLineView != null) {
615             mOnContentViewInactiveListeners.remove(mSingleLineView);
616             mSingleLineView.animate().cancel();
617             removeView(mSingleLineView);
618         }
619         if (child == null) {
620             mSingleLineView = null;
621             if (mTransformationStartVisibleType == VISIBLE_TYPE_SINGLELINE) {
622                 mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
623             }
624             return;
625         }
626         addView(child);
627         mSingleLineView = child;
628     }
629 
630     @Override
onViewAdded(View child)631     public void onViewAdded(View child) {
632         super.onViewAdded(child);
633         child.setTag(R.id.row_tag_for_content_view, mContainingNotification);
634     }
635 
636     @Override
onVisibilityChanged(View changedView, int visibility)637     protected void onVisibilityChanged(View changedView, int visibility) {
638         super.onVisibilityChanged(changedView, visibility);
639         updateVisibility();
640         if (visibility != VISIBLE && !mOnContentViewInactiveListeners.isEmpty()) {
641             // View is no longer visible so all content views are inactive.
642             // Clone list as runnables may modify the list of listeners
643             ArrayList<Runnable> listeners = new ArrayList<>(
644                     mOnContentViewInactiveListeners.values());
645             for (Runnable r : listeners) {
646                 r.run();
647             }
648             mOnContentViewInactiveListeners.clear();
649         }
650     }
651 
updateVisibility()652     private void updateVisibility() {
653         setVisible(isShown());
654     }
655 
656     @Override
onDetachedFromWindow()657     protected void onDetachedFromWindow() {
658         super.onDetachedFromWindow();
659         getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
660     }
661 
setVisible(final boolean isVisible)662     private void setVisible(final boolean isVisible) {
663         if (isVisible) {
664             // This call can happen multiple times, but removing only removes a single one.
665             // We therefore need to remove the old one.
666             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
667             // We only animate if we are drawn at least once, otherwise the view might animate when
668             // it's shown the first time
669             getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
670         } else {
671             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
672             mAnimate = false;
673         }
674     }
675 
focusExpandButtonIfNecessary()676     private void focusExpandButtonIfNecessary() {
677         if (mFocusOnVisibilityChange) {
678             NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
679             if (wrapper != null) {
680                 View expandButton = wrapper.getExpandButton();
681                 if (expandButton != null) {
682                     expandButton.requestAccessibilityFocus();
683                 }
684             }
685             mFocusOnVisibilityChange = false;
686         }
687     }
688 
setContentHeight(int contentHeight)689     public void setContentHeight(int contentHeight) {
690         mUnrestrictedContentHeight = Math.max(contentHeight, getMinHeight());
691         int maxContentHeight = mContainingNotification.getIntrinsicHeight()
692                 - getExtraRemoteInputHeight(mExpandedRemoteInput)
693                 - getExtraRemoteInputHeight(mHeadsUpRemoteInput);
694         mContentHeight = Math.min(mUnrestrictedContentHeight, maxContentHeight);
695         selectLayout(mAnimate /* animate */, false /* force */);
696 
697         if (mContractedChild == null) {
698             // Contracted child may be null if this is the public content view and we don't need to
699             // show it.
700             return;
701         }
702 
703         int minHeightHint = getMinContentHeightHint();
704 
705         NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
706         if (wrapper != null) {
707             wrapper.setContentHeight(mUnrestrictedContentHeight, minHeightHint);
708         }
709 
710         wrapper = getVisibleWrapper(mTransformationStartVisibleType);
711         if (wrapper != null) {
712             wrapper.setContentHeight(mUnrestrictedContentHeight, minHeightHint);
713         }
714 
715         updateClipping();
716         invalidateOutline();
717     }
718 
719     /**
720      * @return the minimum apparent height that the wrapper should allow for the purpose
721      *         of aligning elements at the bottom edge. If this is larger than the content
722      *         height, the notification is clipped instead of being further shrunk.
723      */
getMinContentHeightHint()724     private int getMinContentHeightHint() {
725         int actionListHeight = mContext.getResources().getDimensionPixelSize(
726                 notificationsRedesignTemplates()
727                         ? com.android.internal.R.dimen.notification_2025_action_list_height
728                         : com.android.internal.R.dimen.notification_action_list_height);
729         if (mIsChildInGroup && isVisibleOrTransitioning(VISIBLE_TYPE_SINGLELINE)) {
730             return actionListHeight;
731         }
732 
733         // Transition between heads-up & expanded, or pinned.
734         if (mHeadsUpChild != null && mExpandedChild != null) {
735             boolean transitioningBetweenHunAndExpanded =
736                     isTransitioningFromTo(VISIBLE_TYPE_HEADSUP, VISIBLE_TYPE_EXPANDED) ||
737                             isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP);
738             boolean pinned = !isVisibleOrTransitioning(VISIBLE_TYPE_CONTRACTED)
739                     && (mIsHeadsUp || mHeadsUpAnimatingAway)
740                     && mContainingNotification.canShowHeadsUp();
741             if (transitioningBetweenHunAndExpanded || pinned) {
742                 return Math.min(getViewHeight(VISIBLE_TYPE_HEADSUP),
743                         getViewHeight(VISIBLE_TYPE_EXPANDED));
744             }
745         }
746 
747         // Size change of the expanded version
748         if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart != UNDEFINED
749                 && mExpandedChild != null) {
750             return Math.min(mContentHeightAtAnimationStart, getViewHeight(VISIBLE_TYPE_EXPANDED));
751         }
752 
753         int hint;
754         if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
755             hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
756             if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()) {
757                 // While the RemoteInputView is animating its appearance, it should be allowed
758                 // to overlap the hint, therefore no space is reserved for the hint during the
759                 // appearance animation of the RemoteInputView
760                 hint = 0;
761             }
762         } else if (mExpandedChild != null) {
763             hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
764         } else if (mContractedChild != null) {
765             hint = getViewHeight(VISIBLE_TYPE_CONTRACTED) + actionListHeight;
766         } else {
767             hint = getMinHeight();
768         }
769 
770         if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) {
771             hint = Math.min(hint, getViewHeight(VISIBLE_TYPE_EXPANDED));
772         }
773         return hint;
774     }
775 
isTransitioningFromTo(int from, int to)776     private boolean isTransitioningFromTo(int from, int to) {
777         return (mTransformationStartVisibleType == from || mAnimationStartVisibleType == from)
778                 && mVisibleType == to;
779     }
780 
isVisibleOrTransitioning(int type)781     private boolean isVisibleOrTransitioning(int type) {
782         return mVisibleType == type || mTransformationStartVisibleType == type
783                 || mAnimationStartVisibleType == type;
784     }
785 
updateContentTransformation()786     private void updateContentTransformation() {
787         int visibleType = calculateVisibleType();
788         if (getTransformableViewForVisibleType(mVisibleType) == null) {
789             // Case where visible view was removed in middle of transformation. In this case, we
790             // just update immediately to the appropriate view.
791             mVisibleType = visibleType;
792             updateViewVisibilities(visibleType);
793             updateBackgroundColor(false);
794             return;
795         }
796         if (visibleType != mVisibleType) {
797             // A new transformation starts
798             mTransformationStartVisibleType = mVisibleType;
799             final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
800             final TransformableView hiddenView = getTransformableViewForVisibleType(
801                     mTransformationStartVisibleType);
802             shownView.transformFrom(hiddenView, 0.0f);
803             getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
804             hiddenView.transformTo(shownView, 0.0f);
805             mVisibleType = visibleType;
806             updateBackgroundColor(true /* animate */);
807         }
808         if (mForceSelectNextLayout) {
809             forceUpdateVisibilities();
810         }
811         if (mTransformationStartVisibleType != VISIBLE_TYPE_NONE
812                 && mVisibleType != mTransformationStartVisibleType
813                 && getViewForVisibleType(mTransformationStartVisibleType) != null) {
814             final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType);
815             final TransformableView hiddenView = getTransformableViewForVisibleType(
816                     mTransformationStartVisibleType);
817             float transformationAmount = calculateTransformationAmount();
818             shownView.transformFrom(hiddenView, transformationAmount);
819             hiddenView.transformTo(shownView, transformationAmount);
820             updateBackgroundTransformation(transformationAmount);
821         } else {
822             updateViewVisibilities(visibleType);
823             updateBackgroundColor(false);
824         }
825     }
826 
updateBackgroundTransformation(float transformationAmount)827     private void updateBackgroundTransformation(float transformationAmount) {
828         int endColor = getBackgroundColor(mVisibleType);
829         int startColor = getBackgroundColor(mTransformationStartVisibleType);
830         if (endColor != startColor) {
831             if (startColor == 0) {
832                 startColor = mContainingNotification.getBackgroundColorWithoutTint();
833             }
834             if (endColor == 0) {
835                 endColor = mContainingNotification.getBackgroundColorWithoutTint();
836             }
837             endColor = NotificationUtils.interpolateColors(startColor, endColor,
838                     transformationAmount);
839         }
840         mContainingNotification.setContentBackground(endColor, false, this);
841     }
842 
calculateTransformationAmount()843     private float calculateTransformationAmount() {
844         int startHeight = getViewHeight(mTransformationStartVisibleType);
845         int endHeight = getViewHeight(mVisibleType);
846         int progress = Math.abs(mContentHeight - startHeight);
847         int totalDistance = Math.abs(endHeight - startHeight);
848         if (totalDistance == 0) {
849             Log.wtf(TAG, "the total transformation distance is 0"
850                     + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
851                     + "\n VisibleType: " + mVisibleType + " height: " + endHeight
852                     + "\n mContentHeight: " + mContentHeight);
853             return 1.0f;
854         }
855         float amount = (float) progress / (float) totalDistance;
856         return Math.min(1.0f, amount);
857     }
858 
getContentHeight()859     public int getContentHeight() {
860         return mContentHeight;
861     }
862 
getMaxHeight()863     public int getMaxHeight() {
864         if (mExpandedChild != null) {
865             return getViewHeight(VISIBLE_TYPE_EXPANDED)
866                     + getExtraRemoteInputHeight(mExpandedRemoteInput);
867         } else if (mIsHeadsUp && mHeadsUpChild != null && mContainingNotification.canShowHeadsUp()) {
868             return getViewHeight(VISIBLE_TYPE_HEADSUP)
869                     + getExtraRemoteInputHeight(mHeadsUpRemoteInput);
870         } else if (mContractedChild != null) {
871             return getViewHeight(VISIBLE_TYPE_CONTRACTED);
872         }
873         return mNotificationMaxHeight;
874     }
875 
getViewHeight(int visibleType)876     private int getViewHeight(int visibleType) {
877         return getViewHeight(visibleType, false /* forceNoHeader */);
878     }
879 
getViewHeight(int visibleType, boolean forceNoHeader)880     private int getViewHeight(int visibleType, boolean forceNoHeader) {
881         View view = getViewForVisibleType(visibleType);
882         int height = view.getHeight();
883         NotificationViewWrapper viewWrapper = getWrapperForView(view);
884         if (viewWrapper != null) {
885             height += viewWrapper.getHeaderTranslation(forceNoHeader);
886         }
887         return height;
888     }
889 
getMinHeight()890     public int getMinHeight() {
891         return getMinHeight(false /* likeGroupExpanded */);
892     }
893 
getMinHeight(boolean likeGroupExpanded)894     public int getMinHeight(boolean likeGroupExpanded) {
895         if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
896             return mContractedChild != null
897                     ? getViewHeight(VISIBLE_TYPE_CONTRACTED) : mMinContractedHeight;
898         } else {
899             if (AsyncHybridViewInflation.isEnabled()) {
900                 if (mSingleLineView != null) {
901                     return getViewHeight(VISIBLE_TYPE_SINGLELINE);
902                 } else {
903                     //TODO(b/217799515): investigate the impact of min-height value
904                     return mMinSingleLineHeight;
905                 }
906             } else {
907                 AsyncHybridViewInflation.assertInLegacyMode();
908                 return mSingleLineView.getHeight();
909             }
910         }
911     }
912 
isHUNCompact()913     public boolean isHUNCompact() {
914         return mIsHUNCompact;
915     }
916 
isGroupExpanded()917     private boolean isGroupExpanded() {
918         return mContainingNotification.isGroupExpanded();
919     }
920 
setClipTopAmount(int clipTopAmount)921     public void setClipTopAmount(int clipTopAmount) {
922         mClipTopAmount = clipTopAmount;
923         updateClipping();
924     }
925 
926 
setClipBottomAmount(int clipBottomAmount)927     public void setClipBottomAmount(int clipBottomAmount) {
928         mClipBottomAmount = clipBottomAmount;
929         updateClipping();
930     }
931 
932     @Override
setTranslationY(float translationY)933     public void setTranslationY(float translationY) {
934         super.setTranslationY(translationY);
935         updateClipping();
936     }
937 
updateClipping()938     private void updateClipping() {
939         if (mClipToActualHeight) {
940             int top = (int) (mClipTopAmount - getTranslationY());
941             int bottom = (int) (mUnrestrictedContentHeight - mClipBottomAmount - getTranslationY());
942             bottom = Math.max(top, bottom);
943             mClipBounds.set(0, top, getWidth(), bottom);
944             setClipBounds(mClipBounds);
945         } else {
946             setClipBounds(null);
947         }
948     }
949 
setClipToActualHeight(boolean clipToActualHeight)950     public void setClipToActualHeight(boolean clipToActualHeight) {
951         mClipToActualHeight = clipToActualHeight;
952         updateClipping();
953     }
954 
selectLayout(boolean animate, boolean force)955     private void selectLayout(boolean animate, boolean force) {
956         if (mContractedChild == null) {
957             return;
958         }
959         if (mUserExpanding) {
960             updateContentTransformation();
961         } else {
962             int visibleType = calculateVisibleType();
963             boolean changedType = visibleType != mVisibleType;
964             if (changedType || force) {
965                 View visibleView = getViewForVisibleType(visibleType);
966                 if (visibleView != null) {
967                     visibleView.setVisibility(VISIBLE);
968                     if (!ExpandHeadsUpOnInlineReply.isEnabled()) {
969                         transferRemoteInputFocus(visibleType);
970                     }
971                 }
972 
973                 if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
974                         || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
975                         || (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
976                         || visibleType == VISIBLE_TYPE_CONTRACTED)) {
977                     animateToVisibleType(visibleType);
978                 } else {
979                     updateViewVisibilities(visibleType);
980                 }
981                 mVisibleType = visibleType;
982                 if (changedType) {
983                     focusExpandButtonIfNecessary();
984                 }
985                 NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
986                 if (visibleWrapper != null) {
987                     visibleWrapper.setContentHeight(mUnrestrictedContentHeight,
988                             getMinContentHeightHint());
989                 }
990                 updateBackgroundColor(animate);
991             }
992         }
993     }
994 
forceUpdateVisibilities()995     private void forceUpdateVisibilities() {
996         forceUpdateVisibility(VISIBLE_TYPE_CONTRACTED, mContractedChild, mContractedWrapper);
997         forceUpdateVisibility(VISIBLE_TYPE_EXPANDED, mExpandedChild, mExpandedWrapper);
998         forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
999         forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
1000         updateShownWrapper(mVisibleType);
1001         fireExpandedVisibleListenerIfVisible();
1002         // forceUpdateVisibilities cancels outstanding animations without updating the
1003         // mAnimationStartVisibleType. Do so here instead.
1004         mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
1005         notifySubtreeForAccessibilityContentChange();
1006     }
1007 
fireExpandedVisibleListenerIfVisible()1008     private void fireExpandedVisibleListenerIfVisible() {
1009         if (mExpandedVisibleListener != null && mExpandedChild != null && isShown()
1010                 && mExpandedChild.getVisibility() == VISIBLE) {
1011             Runnable listener = mExpandedVisibleListener;
1012             mExpandedVisibleListener = null;
1013             listener.run();
1014         }
1015     }
1016 
forceUpdateVisibility(int type, View view, TransformableView wrapper)1017     private void forceUpdateVisibility(int type, View view, TransformableView wrapper) {
1018         if (view == null) {
1019             return;
1020         }
1021         boolean visible = mVisibleType == type
1022                 || mTransformationStartVisibleType == type;
1023         if (!visible) {
1024             view.setVisibility(INVISIBLE);
1025         } else {
1026             wrapper.setVisible(true);
1027         }
1028     }
1029 
updateBackgroundColor(boolean animate)1030     public void updateBackgroundColor(boolean animate) {
1031         int customBackgroundColor = getBackgroundColor(mVisibleType);
1032         mContainingNotification.setContentBackground(customBackgroundColor, animate, this);
1033     }
1034 
setBackgroundTintColor(int color)1035     public void setBackgroundTintColor(int color) {
1036         boolean colorized = mNotificationEntry.getSbn().getNotification().isColorized();
1037         if (mExpandedSmartReplyView != null) {
1038             mExpandedSmartReplyView.setBackgroundTintColor(color, colorized);
1039         }
1040         if (mHeadsUpSmartReplyView != null) {
1041             mHeadsUpSmartReplyView.setBackgroundTintColor(color, colorized);
1042         }
1043         if (mExpandedRemoteInput != null) {
1044             mExpandedRemoteInput.setBackgroundTintColor(color, colorized);
1045         }
1046         if (mHeadsUpRemoteInput != null) {
1047             mHeadsUpRemoteInput.setBackgroundTintColor(color, colorized);
1048         }
1049     }
1050 
getVisibleType()1051     public int getVisibleType() {
1052         return mVisibleType;
1053     }
1054 
getBackgroundColorForExpansionState()1055     public int getBackgroundColorForExpansionState() {
1056         // When expanding or user locked we want the new type, when collapsing we want
1057         // the original type
1058         final int visibleType = (
1059                 isGroupExpanded() || mContainingNotification.isUserLocked())
1060                 ? calculateVisibleType()
1061                 : getVisibleType();
1062         return getBackgroundColor(visibleType);
1063     }
1064 
getBackgroundColor(int visibleType)1065     public int getBackgroundColor(int visibleType) {
1066         NotificationViewWrapper currentVisibleWrapper = getVisibleWrapper(visibleType);
1067         int customBackgroundColor = 0;
1068         if (currentVisibleWrapper != null) {
1069             customBackgroundColor = currentVisibleWrapper.getCustomBackgroundColor();
1070         }
1071         return customBackgroundColor;
1072     }
1073 
updateViewVisibilities(int visibleType)1074     private void updateViewVisibilities(int visibleType) {
1075         updateViewVisibility(visibleType, VISIBLE_TYPE_CONTRACTED,
1076                 mContractedChild, mContractedWrapper);
1077         updateViewVisibility(visibleType, VISIBLE_TYPE_EXPANDED,
1078                 mExpandedChild, mExpandedWrapper);
1079         updateViewVisibility(visibleType, VISIBLE_TYPE_HEADSUP,
1080                 mHeadsUpChild, mHeadsUpWrapper);
1081         updateViewVisibility(visibleType, VISIBLE_TYPE_SINGLELINE,
1082                 mSingleLineView, mSingleLineView);
1083         updateShownWrapper(visibleType);
1084         fireExpandedVisibleListenerIfVisible();
1085         // updateViewVisibilities cancels outstanding animations without updating the
1086         // mAnimationStartVisibleType. Do so here instead.
1087         mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
1088         notifySubtreeForAccessibilityContentChange();
1089     }
1090 
updateViewVisibility(int visibleType, int type, View view, TransformableView wrapper)1091     private void updateViewVisibility(int visibleType, int type, View view,
1092             TransformableView wrapper) {
1093         if (view != null) {
1094             wrapper.setVisible(visibleType == type);
1095         }
1096     }
1097 
1098     /**
1099      * Called when the currently shown wrapper is potentially affected by a change to the
1100      * {mVisibleType} or the user-visibility of this view.
1101      *
1102      * @see View#isShown()
1103      */
updateShownWrapper(int visibleType)1104     private void updateShownWrapper(int visibleType) {
1105         final NotificationViewWrapper shownWrapper = isShown() ? getVisibleWrapper(visibleType)
1106                 : null;
1107 
1108         if (mShownWrapper != shownWrapper) {
1109             NotificationViewWrapper hiddenWrapper = mShownWrapper;
1110             mShownWrapper = shownWrapper;
1111             if (hiddenWrapper != null) {
1112                 hiddenWrapper.onContentShown(false);
1113             }
1114             if (shownWrapper != null) {
1115                 shownWrapper.onContentShown(true);
1116             }
1117         }
1118     }
1119 
animateToVisibleType(int visibleType)1120     private void animateToVisibleType(int visibleType) {
1121         final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
1122         final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
1123         if (shownView == hiddenView || hiddenView == null) {
1124             shownView.setVisible(true);
1125             return;
1126         }
1127         mAnimationStartVisibleType = mVisibleType;
1128         shownView.transformFrom(hiddenView);
1129         getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
1130         updateShownWrapper(visibleType);
1131         hiddenView.transformTo(shownView, new Runnable() {
1132             @Override
1133             public void run() {
1134                 if (hiddenView != getTransformableViewForVisibleType(mVisibleType)) {
1135                     hiddenView.setVisible(false);
1136                 }
1137                 mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
1138                 notifySubtreeForAccessibilityContentChange();
1139             }
1140         });
1141         fireExpandedVisibleListenerIfVisible();
1142     }
1143 
transferRemoteInputFocus(int visibleType)1144     private void transferRemoteInputFocus(int visibleType) {
1145         if (visibleType == VISIBLE_TYPE_HEADSUP
1146                 && mHeadsUpRemoteInputController != null
1147                 && mExpandedRemoteInputController != null
1148                 && mExpandedRemoteInputController.isActive()) {
1149             mHeadsUpRemoteInputController.stealFocusFrom(mExpandedRemoteInputController);
1150         }
1151         if (visibleType == VISIBLE_TYPE_EXPANDED
1152                 && mExpandedRemoteInputController != null
1153                 && mHeadsUpRemoteInputController != null
1154                 && mHeadsUpRemoteInputController.isActive()) {
1155             mExpandedRemoteInputController.stealFocusFrom(mHeadsUpRemoteInputController);
1156         }
1157     }
1158 
1159     @Override
notifySubtreeAccessibilityStateChanged(View child, View source, int changeType)1160     public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
1161         if (isAnimatingVisibleType()) {
1162             // Don't send A11y events while animating to reduce Jank.
1163             return;
1164         }
1165         super.notifySubtreeAccessibilityStateChanged(child, source, changeType);
1166     }
1167 
notifySubtreeForAccessibilityContentChange()1168     private void notifySubtreeForAccessibilityContentChange() {
1169         if (mParent != null) {
1170             mParent.notifySubtreeAccessibilityStateChanged(this, this,
1171                     AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
1172         }
1173     }
1174 
1175     /**
1176      * @param visibleType one of the static enum types in this view
1177      * @return the corresponding transformable view according to the given visible type
1178      */
getTransformableViewForVisibleType(int visibleType)1179     private TransformableView getTransformableViewForVisibleType(int visibleType) {
1180         switch (visibleType) {
1181             case VISIBLE_TYPE_EXPANDED:
1182                 return mExpandedWrapper;
1183             case VISIBLE_TYPE_HEADSUP:
1184                 return mHeadsUpWrapper;
1185             case VISIBLE_TYPE_SINGLELINE:
1186                 return mSingleLineView;
1187             default:
1188                 return mContractedWrapper;
1189         }
1190     }
1191 
1192     /**
1193      * @param visibleType one of the static enum types in this view
1194      * @return the corresponding view according to the given visible type
1195      */
getViewForVisibleType(int visibleType)1196     private View getViewForVisibleType(int visibleType) {
1197         switch (visibleType) {
1198             case VISIBLE_TYPE_EXPANDED:
1199                 return mExpandedChild;
1200             case VISIBLE_TYPE_HEADSUP:
1201                 return mHeadsUpChild;
1202             case VISIBLE_TYPE_SINGLELINE:
1203                 return mSingleLineView;
1204             default:
1205                 return mContractedChild;
1206         }
1207     }
1208 
getAllViews()1209     public @NonNull View[] getAllViews() {
1210         return new View[] {
1211                 mContractedChild,
1212                 mHeadsUpChild,
1213                 mExpandedChild,
1214                 mSingleLineView };
1215     }
1216 
getVisibleWrapper()1217     public NotificationViewWrapper getVisibleWrapper() {
1218         return getVisibleWrapper(mVisibleType);
1219     }
1220 
getVisibleWrapper(int visibleType)1221     public NotificationViewWrapper getVisibleWrapper(int visibleType) {
1222         switch (visibleType) {
1223             case VISIBLE_TYPE_EXPANDED:
1224                 return mExpandedWrapper;
1225             case VISIBLE_TYPE_HEADSUP:
1226                 return mHeadsUpWrapper;
1227             case VISIBLE_TYPE_CONTRACTED:
1228                 return mContractedWrapper;
1229             default:
1230                 return null;
1231         }
1232     }
1233 
1234     /**
1235      * @return one of the static enum types in this view, calculated from the current state
1236      */
calculateVisibleType()1237     public int calculateVisibleType() {
1238         if (mUserExpanding) {
1239             int height = !mIsChildInGroup || isGroupExpanded()
1240                     || mContainingNotification.isExpanded(true /* allowOnKeyguard */)
1241                     ? mContainingNotification.getMaxContentHeight()
1242                     : mContainingNotification.getShowingLayout().getMinHeight();
1243             if (height == 0) {
1244                 height = mContentHeight;
1245             }
1246             int expandedVisualType = getVisualTypeForHeight(height);
1247             final boolean shouldShowSingleLineView = mIsChildInGroup && !isGroupExpanded();
1248             final boolean isSingleLineViewPresent = mSingleLineView != null;
1249 
1250             if (shouldShowSingleLineView && !isSingleLineViewPresent) {
1251                 Log.wtf(TAG, "calculateVisibleType: SingleLineView is not available!");
1252             }
1253 
1254             final int collapsedVisualType = shouldShowSingleLineView && isSingleLineViewPresent
1255                     ? VISIBLE_TYPE_SINGLELINE
1256                     : getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
1257             return mTransformationStartVisibleType == collapsedVisualType
1258                     ? expandedVisualType
1259                     : collapsedVisualType;
1260         }
1261         int intrinsicHeight = mContainingNotification.getIntrinsicHeight();
1262         int viewHeight = mContentHeight;
1263         if (intrinsicHeight != 0) {
1264             // the intrinsicHeight might be 0 because it was just reset.
1265             viewHeight = Math.min(mContentHeight, intrinsicHeight);
1266         }
1267         return getVisualTypeForHeight(viewHeight);
1268     }
1269 
getVisualTypeForHeight(float viewHeight)1270     private int getVisualTypeForHeight(float viewHeight) {
1271         boolean noExpandedChild = mExpandedChild == null;
1272         if (!noExpandedChild && viewHeight == getViewHeight(VISIBLE_TYPE_EXPANDED)) {
1273             return VISIBLE_TYPE_EXPANDED;
1274         }
1275         final boolean shouldShowSingleLineView = mIsChildInGroup && !isGroupExpanded();
1276         final boolean isSingleLinePresent =  mSingleLineView != null;
1277 
1278         if (!mUserExpanding && shouldShowSingleLineView && isSingleLinePresent) {
1279             return VISIBLE_TYPE_SINGLELINE;
1280         }
1281 
1282         if ((mIsHeadsUp || mHeadsUpAnimatingAway) && mHeadsUpChild != null
1283                 && mContainingNotification.canShowHeadsUp()) {
1284             if (viewHeight <= getViewHeight(VISIBLE_TYPE_HEADSUP) || noExpandedChild) {
1285                 return VISIBLE_TYPE_HEADSUP;
1286             } else {
1287                 return VISIBLE_TYPE_EXPANDED;
1288             }
1289         } else {
1290             if (noExpandedChild || (mContractedChild != null
1291                     && viewHeight <= getViewHeight(VISIBLE_TYPE_CONTRACTED)
1292                     && (!mIsChildInGroup || isGroupExpanded()
1293                     || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
1294                 return VISIBLE_TYPE_CONTRACTED;
1295             } else if (!noExpandedChild) {
1296                 return VISIBLE_TYPE_EXPANDED;
1297             } else {
1298                 return VISIBLE_TYPE_NONE;
1299             }
1300         }
1301     }
1302 
isContentExpandable()1303     public boolean isContentExpandable() {
1304         return mIsContentExpandable;
1305     }
1306 
setHeadsUp(boolean headsUp)1307     public void setHeadsUp(boolean headsUp) {
1308         mIsHeadsUp = headsUp;
1309         selectLayout(false /* animate */, true /* force */);
1310         updateExpandButtons(mExpandable);
1311     }
1312 
1313     @Override
hasOverlappingRendering()1314     public boolean hasOverlappingRendering() {
1315 
1316         // This is not really true, but good enough when fading from the contracted to the expanded
1317         // layout, and saves us some layers.
1318         return false;
1319     }
1320 
setLegacy(boolean legacy)1321     public void setLegacy(boolean legacy) {
1322         mLegacy = legacy;
1323         updateLegacy();
1324     }
1325 
updateLegacy()1326     private void updateLegacy() {
1327         if (mContractedChild != null) {
1328             mContractedWrapper.setLegacy(mLegacy);
1329         }
1330         if (mExpandedChild != null) {
1331             mExpandedWrapper.setLegacy(mLegacy);
1332         }
1333         if (mHeadsUpChild != null) {
1334             mHeadsUpWrapper.setLegacy(mLegacy);
1335         }
1336     }
1337 
setIsChildInGroup(boolean isChildInGroup)1338     public void setIsChildInGroup(boolean isChildInGroup) {
1339         mIsChildInGroup = isChildInGroup;
1340         if (mContractedChild != null) {
1341             mContractedWrapper.setIsChildInGroup(mIsChildInGroup);
1342         }
1343         if (mExpandedChild != null) {
1344             mExpandedWrapper.setIsChildInGroup(mIsChildInGroup);
1345         }
1346         if (mHeadsUpChild != null) {
1347             mHeadsUpWrapper.setIsChildInGroup(mIsChildInGroup);
1348         }
1349         updateAllSingleLineViews();
1350     }
1351 
onNotificationUpdated(NotificationEntry entry)1352     public void onNotificationUpdated(NotificationEntry entry) {
1353         mNotificationEntry = entry;
1354         mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
1355         updateAllSingleLineViews();
1356         ExpandableNotificationRow row = entry.getRow();
1357         if (mContractedChild != null) {
1358             mContractedWrapper.onContentUpdated(row);
1359         }
1360         if (mExpandedChild != null) {
1361             mExpandedWrapper.onContentUpdated(row);
1362         }
1363         if (mHeadsUpChild != null) {
1364             mHeadsUpWrapper.onContentUpdated(row);
1365         }
1366         applyRemoteInputAndSmartReply();
1367         updateLegacy();
1368         mForceSelectNextLayout = true;
1369         mPreviousExpandedRemoteInputIntent = null;
1370         mPreviousHeadsUpRemoteInputIntent = null;
1371         applySystemActions(mExpandedChild, entry);
1372         applySystemActions(mHeadsUpChild, entry);
1373     }
1374 
1375     private void updateAllSingleLineViews() {
1376         updateSingleLineView();
1377     }
1378 
1379     private void updateSingleLineView() {
1380         try {
1381             Trace.beginSection("NotifContentView#updateSingleLineView");
1382             if (AsyncHybridViewInflation.isEnabled()) {
1383                 return;
1384             }
1385             AsyncHybridViewInflation.assertInLegacyMode();
1386             if (mIsChildInGroup) {
1387                 boolean isNewView = mSingleLineView == null;
1388                 mSingleLineView = mHybridGroupManager.bindFromNotification(
1389                         /* reusableView = */ mSingleLineView,
1390                         /* contentView = */ mContractedChild,
1391                         /* notification = */ mNotificationEntry.getSbn(),
1392                         /* parent = */ this
1393                 );
1394                 if (isNewView && mSingleLineView != null) {
1395                     updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
1396                             mSingleLineView, mSingleLineView);
1397                 }
1398             } else if (mSingleLineView != null) {
1399                 removeView(mSingleLineView);
1400                 mSingleLineView = null;
1401             }
1402         } finally {
1403             Trace.endSection();
1404         }
1405     }
1406 
1407     /**
1408      * Returns whether the {@link Notification} represented by entry has a free-form remote input.
1409      * Such an input can be used e.g. to implement smart reply buttons - by passing the replies
1410      * through the remote input.
1411      */
1412     public static boolean hasFreeformRemoteInput(NotificationEntry entry) {
1413         Notification notification = entry.getSbn().getNotification();
1414         return null != notification.findRemoteInputActionPair(true /* freeform */);
1415     }
1416 
1417     private void applyRemoteInputAndSmartReply() {
1418         if (mRemoteInputController != null) {
1419             applyRemoteInput();
1420         }
1421 
1422         if (mCurrentSmartReplyState == null) {
1423             if (DEBUG) {
1424                 Log.d(TAG, "InflatedSmartReplies are null, don't add smart replies.");
1425             }
1426             return;
1427         }
1428         if (DEBUG) {
1429             Log.d(TAG, String.format("Adding suggestions for %s, %d actions, and %d replies.",
1430                     mNotificationEntry.getSbn().getKey(),
1431                     mCurrentSmartReplyState.getSmartActionsList().size(),
1432                     mCurrentSmartReplyState.getSmartRepliesList().size()));
1433         }
1434         applySmartReplyView();
1435     }
1436 
1437     private void applyRemoteInput() {
1438         boolean hasFreeformRemoteInput = hasFreeformRemoteInput(mNotificationEntry);
1439         if (mExpandedChild != null) {
1440             RemoteInputViewData expandedData = applyRemoteInput(mExpandedChild, mNotificationEntry,
1441                     hasFreeformRemoteInput, mPreviousExpandedRemoteInputIntent,
1442                     mCachedExpandedRemoteInput, mCachedExpandedRemoteInputViewController,
1443                     mExpandedWrapper);
1444             mExpandedRemoteInput = expandedData.mView;
1445             mExpandedRemoteInputController = expandedData.mController;
1446             if (mExpandedRemoteInputController != null) {
1447                 mExpandedRemoteInputController.bind();
1448             }
1449         } else {
1450             mExpandedRemoteInput = null;
1451             if (mExpandedRemoteInputController != null) {
1452                 mExpandedRemoteInputController.unbind();
1453             }
1454             mExpandedRemoteInputController = null;
1455         }
1456         if (mCachedExpandedRemoteInput != null
1457                 && mCachedExpandedRemoteInput != mExpandedRemoteInput) {
1458             // We had a cached remote input but didn't reuse it. Clean up required.
1459             mCachedExpandedRemoteInput.dispatchFinishTemporaryDetach();
1460         }
1461         mCachedExpandedRemoteInput = null;
1462         mCachedExpandedRemoteInputViewController = null;
1463 
1464         if (ExpandHeadsUpOnInlineReply.isEnabled()) {
1465             mHeadsUpRemoteInput = null;
1466             mHeadsUpRemoteInputController = null;
1467             mCachedHeadsUpRemoteInput = null;
1468             mCachedHeadsUpRemoteInputViewController = null;
1469         } else {
1470             ExpandHeadsUpOnInlineReply.assertInLegacyMode();
1471             if (mHeadsUpChild != null) {
1472                 RemoteInputViewData headsUpData = applyRemoteInput(mHeadsUpChild,
1473                         mNotificationEntry,
1474                         hasFreeformRemoteInput, mPreviousHeadsUpRemoteInputIntent,
1475                         mCachedHeadsUpRemoteInput, mCachedHeadsUpRemoteInputViewController,
1476                         mHeadsUpWrapper);
1477                 mHeadsUpRemoteInput = headsUpData.mView;
1478                 mHeadsUpRemoteInputController = headsUpData.mController;
1479                 if (mHeadsUpRemoteInputController != null) {
1480                     mHeadsUpRemoteInputController.bind();
1481                 }
1482             } else {
1483                 mHeadsUpRemoteInput = null;
1484                 if (mHeadsUpRemoteInputController != null) {
1485                     mHeadsUpRemoteInputController.unbind();
1486                 }
1487                 mHeadsUpRemoteInputController = null;
1488             }
1489             if (mCachedHeadsUpRemoteInput != null
1490                     && mCachedHeadsUpRemoteInput != mHeadsUpRemoteInput) {
1491                 // We had a cached remote input but didn't reuse it. Clean up required.
1492                 mCachedHeadsUpRemoteInput.dispatchFinishTemporaryDetach();
1493             }
1494             mCachedHeadsUpRemoteInput = null;
1495             mCachedHeadsUpRemoteInputViewController = null;
1496         }
1497     }
1498 
1499     private RemoteInputViewData applyRemoteInput(View view, NotificationEntry entry,
1500             boolean hasRemoteInput, PendingIntent existingPendingIntent, RemoteInputView cachedView,
1501             RemoteInputViewController cachedController, NotificationViewWrapper wrapper) {
1502         RemoteInputViewData result = new RemoteInputViewData();
1503         View actionContainerCandidate = view.findViewById(
1504                 com.android.internal.R.id.actions_container);
1505         if (actionContainerCandidate instanceof FrameLayout) {
1506             result.mView = view.findViewWithTag(RemoteInputView.VIEW_TAG);
1507 
1508             if (result.mView != null) {
1509                 result.mView.onNotificationUpdateOrReset();
1510                 result.mController = result.mView.getController();
1511             }
1512 
1513             if (result.mView == null && hasRemoteInput) {
1514                 ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
1515                 if (cachedView == null) {
1516                     RemoteInputView riv = RemoteInputView.inflate(
1517                             mContext, actionContainer, entry, mRemoteInputController);
1518 
1519                     riv.setVisibility(View.GONE);
1520                     actionContainer.addView(riv, new LayoutParams(
1521                             ViewGroup.LayoutParams.MATCH_PARENT,
1522                             ViewGroup.LayoutParams.MATCH_PARENT)
1523                     );
1524                     result.mView = riv;
1525                     // Create a new controller for the view. The lifetime of the controller is 1:1
1526                     // with that of the view.
1527                     RemoteInputViewSubcomponent subcomponent = mRemoteInputSubcomponentFactory
1528                             .create(result.mView, mRemoteInputController);
1529                     result.mController = subcomponent.getController();
1530                     result.mView.setController(result.mController);
1531                 } else {
1532                     actionContainer.addView(cachedView);
1533                     cachedView.dispatchFinishTemporaryDetach();
1534                     cachedView.requestFocus();
1535                     result.mView = cachedView;
1536                     result.mController = cachedController;
1537                 }
1538             }
1539             if (hasRemoteInput) {
1540                 result.mView.setWrapper(wrapper);
1541                 result.mView.setOnVisibilityChangedListener(this::setRemoteInputVisible);
1542 
1543                 if (existingPendingIntent != null || result.mView.isActive()) {
1544                     // The current action could be gone, or the pending intent no longer valid.
1545                     // If we find a matching action in the new notification, focus, otherwise close.
1546                     Notification.Action[] actions = entry.getSbn().getNotification().actions;
1547                     if (existingPendingIntent != null) {
1548                         result.mController.setPendingIntent(existingPendingIntent);
1549                     }
1550                     if (result.mController.updatePendingIntentFromActions(actions)) {
1551                         if (!result.mController.isActive()) {
1552                             result.mController.focus();
1553                         }
1554                     } else {
1555                         if (result.mController.isActive()) {
1556                             result.mController.close();
1557                         }
1558                     }
1559                 }
1560             }
1561             if (result.mView != null) {
1562                 int backgroundColor = entry.getRow().getCurrentBackgroundTint();
1563                 boolean colorized = entry.getSbn().getNotification().isColorized();
1564                 result.mView.setBackgroundTintColor(backgroundColor, colorized);
1565             }
1566         }
1567         return result;
1568     }
1569 
1570     /**
1571      * Call to update state of the bubble button (i.e. does it show bubble or unbubble or no
1572      * icon at all).
1573      *
1574      * @param entry the new entry to use.
1575      */
1576     public void updateBubbleButton(NotificationEntry entry) {
1577         applyBubbleAction(mExpandedChild, entry);
1578     }
1579 
1580     /**
1581      * Setup icon buttons provided by System UI.
1582      */
1583     private void applySystemActions(View layout, NotificationEntry entry) {
1584         applySnoozeAction(layout);
1585         applyBubbleAction(layout, entry);
1586     }
1587 
1588     private void applyBubbleAction(View layout, NotificationEntry entry) {
1589         if (layout == null || mContainingNotification == null || mPeopleIdentifier == null) {
1590             return;
1591         }
1592         ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
1593         // With the new design, the actions_container should always be visible to act as padding
1594         // when there are no actions. We're making its child visible/invisible instead.
1595         View actionsContainerForVisibilityChange = layout.findViewById(
1596                 notificationsRedesignTemplates()
1597                         ? com.android.internal.R.id.actions_container_layout
1598                         : com.android.internal.R.id.actions_container);
1599         if (bubbleButton == null || actionsContainerForVisibilityChange == null) {
1600             return;
1601         }
1602 
1603         if (shouldShowBubbleButton(entry)) {
1604             boolean isBubble = NotificationBundleUi.isEnabled()
1605                     ? mContainingNotification.getEntryAdapter().isBubble()
1606                     : entry.isBubble();
1607             // explicitly resolve drawable resource using SystemUI's theme
1608             Drawable d = mContext.getDrawable(isBubble
1609                     ? com.android.wm.shell.R.drawable.bubble_ic_stop_bubble
1610                     : com.android.wm.shell.R.drawable.bubble_ic_create_bubble);
1611 
1612             String contentDescription = mContext.getResources().getString(isBubble
1613                     ? R.string.notification_conversation_unbubble
1614                     : R.string.notification_conversation_bubble);
1615 
1616             bubbleButton.setContentDescription(contentDescription);
1617             bubbleButton.setImageDrawable(d);
1618             bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
1619             bubbleButton.setVisibility(VISIBLE);
1620             actionsContainerForVisibilityChange.setVisibility(VISIBLE);
1621             if (!notificationsRedesignTemplates()) {
1622                 // Set notification_action_list_margin_target's bottom margin to 0 when showing
1623                 // bubble
1624                 ViewGroup actionListMarginTarget = layout.findViewById(
1625                         com.android.internal.R.id.notification_action_list_margin_target);
1626                 if (actionListMarginTarget != null) {
1627                     removeBottomMargin(actionListMarginTarget);
1628                 }
1629             }
1630         } else  {
1631             bubbleButton.setVisibility(GONE);
1632         }
1633     }
1634 
1635     private static void removeBottomMargin(ViewGroup actionListMarginTarget) {
1636         ViewGroup.LayoutParams lp = actionListMarginTarget.getLayoutParams();
1637         if (lp instanceof MarginLayoutParams) {
1638             final MarginLayoutParams mlp = (MarginLayoutParams) lp;
1639             if (mlp.bottomMargin > 0) {
1640                 mlp.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, 0);
1641             }
1642         }
1643     }
1644 
1645     @MainThread
1646     public void setBubblesEnabledForUser(boolean enabled) {
1647         mBubblesEnabledForUser = enabled;
1648 
1649         applyBubbleAction(mExpandedChild, mNotificationEntry);
1650         applyBubbleAction(mHeadsUpChild, mNotificationEntry);
1651     }
1652 
1653     @VisibleForTesting
1654     boolean shouldShowBubbleButton(NotificationEntry entry) {
1655         int peopleType = NotificationBundleUi.isEnabled()
1656                 ? mContainingNotification.getEntryAdapter().getPeopleNotificationType()
1657                 : mPeopleIdentifier.getPeopleNotificationType(entry);
1658         Notification.BubbleMetadata bubbleMetadata = NotificationBundleUi.isEnabled()
1659                 ? mContainingNotification.getEntryAdapter().getSbn().getNotification()
1660                         .getBubbleMetadata()
1661                 : entry.getBubbleMetadata();
1662         boolean isPersonWithShortcut = peopleType
1663                 >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
1664         return mBubblesEnabledForUser
1665                 && isPersonWithShortcut
1666                 && bubbleMetadata != null;
1667     }
1668 
1669     private void applySnoozeAction(View layout) {
1670         if (layout == null || mContainingNotification == null) {
1671             return;
1672         }
1673         ImageView snoozeButton = layout.findViewById(com.android.internal.R.id.snooze_button);
1674         // With the new design, the actions_container should always be visible to act as padding
1675         // when there are no actions. We're making its child visible/invisible instead.
1676         View actionsContainerForVisibilityChange = layout.findViewById(
1677                 notificationsRedesignTemplates()
1678                         ? com.android.internal.R.id.actions_container_layout
1679                         : com.android.internal.R.id.actions_container);
1680         if (snoozeButton == null || actionsContainerForVisibilityChange == null) {
1681             return;
1682         }
1683         // Notification.Builder can 'disable' the snooze button to prevent it from being shown here
1684         boolean snoozeDisabled = !snoozeButton.isEnabled();
1685         if (!mContainingNotification.getShowSnooze() || snoozeDisabled) {
1686             snoozeButton.setVisibility(GONE);
1687             return;
1688         }
1689 
1690         // explicitly resolve drawable resource using SystemUI's theme
1691         Drawable snoozeDrawable = mContext.getDrawable(R.drawable.ic_snooze);
1692         snoozeButton.setImageDrawable(snoozeDrawable);
1693 
1694         final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext)
1695                 .inflate(R.layout.notification_snooze, null, false);
1696         final String snoozeDescription = mContext.getString(
1697                 R.string.notification_menu_snooze_description);
1698         final NotificationMenuRowPlugin.MenuItem snoozeMenuItem =
1699                 new NotificationMenuRow.NotificationMenuItem(
1700                         mContext, snoozeDescription, snoozeGuts, R.drawable.ic_snooze);
1701         snoozeButton.setContentDescription(
1702                 mContext.getResources().getString(R.string.notification_menu_snooze_description));
1703         snoozeButton.setOnClickListener(
1704                 mContainingNotification.getSnoozeClickListener(snoozeMenuItem));
1705         snoozeButton.setVisibility(VISIBLE);
1706         actionsContainerForVisibilityChange.setVisibility(VISIBLE);
1707     }
1708 
1709     private void applySmartReplyView() {
1710         if (mContractedChild != null) {
1711             applyExternalSmartReplyState(mContractedChild, mCurrentSmartReplyState);
1712         }
1713         if (mExpandedChild != null) {
1714             applyExternalSmartReplyState(mExpandedChild, mCurrentSmartReplyState);
1715             mExpandedSmartReplyView = applySmartReplyView(mExpandedChild, mCurrentSmartReplyState,
1716                     mNotificationEntry, mExpandedInflatedSmartReplies);
1717             if (mExpandedSmartReplyView != null) {
1718                 SmartReplyView.SmartReplies smartReplies =
1719                         mCurrentSmartReplyState.getSmartReplies();
1720                 SmartReplyView.SmartActions smartActions =
1721                         mCurrentSmartReplyState.getSmartActions();
1722                 if (smartReplies != null || smartActions != null) {
1723                     int numSmartReplies = smartReplies == null ? 0 : smartReplies.choices.size();
1724                     int numSmartActions = smartActions == null ? 0 : smartActions.actions.size();
1725                     boolean fromAssistant = smartReplies == null
1726                             ? smartActions.fromAssistant
1727                             : smartReplies.fromAssistant;
1728                     boolean editBeforeSending = smartReplies != null
1729                             && mSmartReplyConstants.getEffectiveEditChoicesBeforeSending(
1730                             smartReplies.remoteInput.getEditChoicesBeforeSending());
1731 
1732                     mSmartReplyController.smartSuggestionsAdded(mNotificationEntry, numSmartReplies,
1733                             numSmartActions, fromAssistant, editBeforeSending);
1734                 }
1735             }
1736         }
1737         if (mHeadsUpChild != null) {
1738             applyExternalSmartReplyState(mHeadsUpChild, mCurrentSmartReplyState);
1739             if (mSmartReplyConstants.getShowInHeadsUp()) {
1740                 mHeadsUpSmartReplyView = applySmartReplyView(mHeadsUpChild, mCurrentSmartReplyState,
1741                         mNotificationEntry, mHeadsUpInflatedSmartReplies);
1742             }
1743         }
1744     }
1745 
1746     private void applyExternalSmartReplyState(View view, InflatedSmartReplyState state) {
1747         boolean hasPhishingAlert = state != null && state.getHasPhishingAction();
1748         View phishingAlertIcon = view.findViewById(com.android.internal.R.id.phishing_alert);
1749         if (phishingAlertIcon != null) {
1750             if (DEBUG) {
1751                 Log.d(TAG, "Setting 'phishing_alert' view visible=" + hasPhishingAlert + ".");
1752             }
1753             phishingAlertIcon.setVisibility(hasPhishingAlert ? View.VISIBLE : View.GONE);
1754         }
1755         List<Integer> suppressedActionIndices = state != null
1756                 ? state.getSuppressedActionIndices()
1757                 : Collections.emptyList();
1758         ViewGroup actionsList = view.findViewById(com.android.internal.R.id.actions);
1759         if (actionsList != null) {
1760             if (DEBUG && !suppressedActionIndices.isEmpty()) {
1761                 Log.d(TAG, "Suppressing actions with indices: " + suppressedActionIndices);
1762             }
1763             for (int i = 0; i < actionsList.getChildCount(); i++) {
1764                 View actionBtn = actionsList.getChildAt(i);
1765                 Object actionIndex =
1766                         actionBtn.getTag(com.android.internal.R.id.notification_action_index_tag);
1767                 boolean suppressAction = actionIndex instanceof Integer
1768                         && suppressedActionIndices.contains(actionIndex);
1769                 actionBtn.setVisibility(suppressAction ? View.GONE : View.VISIBLE);
1770             }
1771         }
1772     }
1773 
1774     @Nullable
1775     private static SmartReplyView applySmartReplyView(View view,
1776             InflatedSmartReplyState smartReplyState,
1777             NotificationEntry entry, InflatedSmartReplyViewHolder inflatedSmartReplyViewHolder) {
1778         View smartReplyContainerCandidate = view.findViewById(
1779                 com.android.internal.R.id.smart_reply_container);
1780         if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
1781             return null;
1782         }
1783 
1784         LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
1785         if (!SmartReplyStateInflaterKt.shouldShowSmartReplyView(entry, smartReplyState)) {
1786             smartReplyContainer.setVisibility(View.GONE);
1787             return null;
1788         }
1789 
1790         // Search for an existing SmartReplyView
1791         int index = 0;
1792         final int childCount = smartReplyContainer.getChildCount();
1793         for (; index < childCount; index++) {
1794             View child = smartReplyContainer.getChildAt(index);
1795             if (child.getId() == R.id.smart_reply_view && child instanceof SmartReplyView) {
1796                 break;
1797             }
1798         }
1799 
1800         if (index < childCount) {
1801             // If we already have a SmartReplyView - replace it with the newly inflated one. The
1802             // newly inflated one is connected to the new inflated smart reply/action buttons.
1803             smartReplyContainer.removeViewAt(index);
1804         }
1805         SmartReplyView smartReplyView = null;
1806         if (inflatedSmartReplyViewHolder != null
1807                 && inflatedSmartReplyViewHolder.getSmartReplyView() != null) {
1808             smartReplyView = inflatedSmartReplyViewHolder.getSmartReplyView();
1809             smartReplyContainer.addView(smartReplyView, index);
1810         }
1811         if (smartReplyView != null) {
1812             smartReplyView.resetSmartSuggestions(smartReplyContainer);
1813             smartReplyView.addPreInflatedButtons(
1814                     inflatedSmartReplyViewHolder.getSmartSuggestionButtons());
1815             // Ensure the colors of the smart suggestion buttons are up-to-date.
1816             int backgroundColor = entry.getRow().getCurrentBackgroundTint();
1817             boolean colorized = entry.getSbn().getNotification().isColorized();
1818             smartReplyView.setBackgroundTintColor(backgroundColor, colorized);
1819             smartReplyContainer.setVisibility(View.VISIBLE);
1820         }
1821         return smartReplyView;
1822     }
1823 
1824     /**
1825      * Set pre-inflated views necessary to display smart replies and actions in the expanded
1826      * notification state.
1827      *
1828      * @param inflatedSmartReplies the pre-inflated state to add to this view. If null the existing
1829      * {@link SmartReplyView} related to the expanded notification state is cleared.
1830      */
1831     public void setExpandedInflatedSmartReplies(
1832             @Nullable InflatedSmartReplyViewHolder inflatedSmartReplies) {
1833         mExpandedInflatedSmartReplies = inflatedSmartReplies;
1834         if (inflatedSmartReplies == null) {
1835             mExpandedSmartReplyView = null;
1836         }
1837     }
1838 
1839     /**
1840      * Set pre-inflated views necessary to display smart replies and actions in the heads-up
1841      * notification state.
1842      *
1843      * @param inflatedSmartReplies the pre-inflated state to add to this view. If null the existing
1844      * {@link SmartReplyView} related to the heads-up notification state is cleared.
1845      */
1846     public void setHeadsUpInflatedSmartReplies(
1847             @Nullable InflatedSmartReplyViewHolder inflatedSmartReplies) {
1848         mHeadsUpInflatedSmartReplies = inflatedSmartReplies;
1849         if (inflatedSmartReplies == null) {
1850             mHeadsUpSmartReplyView = null;
1851         }
1852     }
1853 
1854     /**
1855      * Set pre-inflated replies and actions for the notification.
1856      * This can be relevant to any state of the notification, even contracted, because smart actions
1857      * may cause a phishing alert to be made visible.
1858      * @param smartReplyState the pre-inflated list of replies and actions
1859      */
1860     public void setInflatedSmartReplyState(
1861             @NonNull InflatedSmartReplyState smartReplyState) {
1862         mCurrentSmartReplyState = smartReplyState;
1863     }
1864 
1865     /**
1866      * Returns the smart replies and actions currently shown in the notification.
1867      */
1868     @Nullable public InflatedSmartReplyState getCurrentSmartReplyState() {
1869         return mCurrentSmartReplyState;
1870     }
1871 
1872     public void closeRemoteInput() {
1873         if (mHeadsUpRemoteInput != null) {
1874             mHeadsUpRemoteInput.close();
1875         }
1876         if (mExpandedRemoteInput != null) {
1877             mExpandedRemoteInput.close();
1878         }
1879     }
1880 
1881     public void setGroupMembershipManager(GroupMembershipManager groupMembershipManager) {
1882     }
1883 
1884     public void setRemoteInputController(RemoteInputController r) {
1885         mRemoteInputController = r;
1886     }
1887 
1888     public void setExpandClickListener(OnClickListener expandClickListener) {
1889         mExpandClickListener = expandClickListener;
1890     }
1891 
1892     public void updateExpandButtons(boolean expandable) {
1893         updateExpandButtonsDuringLayout(expandable, false /* duringLayout */);
1894     }
1895 
1896     private void updateExpandButtonsDuringLayout(boolean expandable, boolean duringLayout) {
1897         mExpandable = expandable;
1898         // if the expanded child has the same height as the collapsed one we hide it.
1899         if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
1900             if ((!mIsHeadsUp && !mHeadsUpAnimatingAway)
1901                     || mHeadsUpChild == null || !mContainingNotification.canShowHeadsUp()) {
1902                 if (mContractedChild == null
1903                         || mExpandedChild.getHeight() <= mContractedChild.getHeight()) {
1904                     expandable = false;
1905                 }
1906             } else if (mExpandedChild.getHeight() <= mHeadsUpChild.getHeight()) {
1907                 expandable = false;
1908             }
1909         }
1910         boolean requestLayout = duringLayout && mIsContentExpandable != expandable;
1911         if (mExpandedChild != null) {
1912             mExpandedWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
1913         }
1914         if (mContractedChild != null) {
1915             mContractedWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
1916         }
1917         if (mHeadsUpChild != null) {
1918             mHeadsUpWrapper.updateExpandability(expandable,  mExpandClickListener, requestLayout);
1919         }
1920         mIsContentExpandable = expandable;
1921     }
1922 
1923     /**
1924      * @return a view wrapper for one of the inflated states of the notification.
1925      */
1926     public NotificationViewWrapper getNotificationViewWrapper() {
1927         if (mContractedChild != null && mContractedWrapper != null) {
1928             return mContractedWrapper;
1929         }
1930         if (mExpandedChild != null && mExpandedWrapper != null) {
1931             return mExpandedWrapper;
1932         }
1933         if (mHeadsUpChild != null && mHeadsUpWrapper != null) {
1934             return mHeadsUpWrapper;
1935         }
1936         return null;
1937     }
1938 
1939     /** Shows the given feedback icon, or hides the icon if null. */
1940     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
1941         if (mContractedChild != null) {
1942             mContractedWrapper.setFeedbackIcon(icon);
1943         }
1944         if (mExpandedChild != null) {
1945             mExpandedWrapper.setFeedbackIcon(icon);
1946         }
1947         if (mHeadsUpChild != null) {
1948             mHeadsUpWrapper.setFeedbackIcon(icon);
1949         }
1950     }
1951 
1952     /** Sets whether the notification being displayed audibly alerted the user. */
1953     public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
1954         if (mContractedChild != null) {
1955             mContractedWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
1956         }
1957         if (mExpandedChild != null) {
1958             mExpandedWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
1959         }
1960         if (mHeadsUpChild != null) {
1961             mHeadsUpWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
1962         }
1963     }
1964 
1965     public void setContainingNotification(ExpandableNotificationRow containingNotification) {
1966         mContainingNotification = containingNotification;
1967     }
1968 
1969     public void requestSelectLayout(boolean needsAnimation) {
1970         selectLayout(needsAnimation, false);
1971     }
1972 
1973     public void reInflateViews() {
1974         if (mIsChildInGroup && mSingleLineView != null) {
1975             removeView(mSingleLineView);
1976             mSingleLineView = null;
1977             updateAllSingleLineViews();
1978         }
1979     }
1980 
1981     public void setUserExpanding(boolean userExpanding) {
1982         mUserExpanding = userExpanding;
1983         if (userExpanding) {
1984             mTransformationStartVisibleType = mVisibleType;
1985         } else {
1986             mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
1987             mVisibleType = calculateVisibleType();
1988             updateViewVisibilities(mVisibleType);
1989             updateBackgroundColor(false);
1990         }
1991     }
1992 
1993     /**
1994      * Set by how much the single line view should be indented. Used when a overflow indicator is
1995      * present and only during measuring
1996      */
1997     public void setSingleLineWidthIndention(int singleLineWidthIndention) {
1998         if (singleLineWidthIndention != mSingleLineWidthIndention) {
1999             mSingleLineWidthIndention = singleLineWidthIndention;
2000             mContainingNotification.forceLayout();
2001             forceLayout();
2002         }
2003     }
2004 
2005     public HybridNotificationView getSingleLineView() {
2006         return mSingleLineView;
2007     }
2008 
2009     public void setRemoved() {
2010         if (mExpandedRemoteInput != null) {
2011             mExpandedRemoteInput.setRemoved();
2012         }
2013         if (mHeadsUpRemoteInput != null) {
2014             mHeadsUpRemoteInput.setRemoved();
2015         }
2016         if (mExpandedWrapper != null) {
2017             mExpandedWrapper.setRemoved();
2018         }
2019         if (mContractedWrapper != null) {
2020             mContractedWrapper.setRemoved();
2021         }
2022         if (mHeadsUpWrapper != null) {
2023             mHeadsUpWrapper.setRemoved();
2024         }
2025     }
2026 
2027     public void setContentHeightAnimating(boolean animating) {
2028         //TODO: It's odd that this does nothing when animating is true
2029         if (!animating) {
2030             mContentHeightAtAnimationStart = UNDEFINED;
2031         }
2032     }
2033 
2034     @VisibleForTesting
2035     boolean isAnimatingVisibleType() {
2036         return mAnimationStartVisibleType != VISIBLE_TYPE_NONE;
2037     }
2038 
2039     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
2040         mHeadsUpAnimatingAway = headsUpAnimatingAway;
2041         selectLayout(false /* animate */, true /* force */);
2042     }
2043 
2044     public void setFocusOnVisibilityChange() {
2045         mFocusOnVisibilityChange = true;
2046     }
2047 
2048     @Override
2049     public void onVisibilityAggregated(boolean isVisible) {
2050         super.onVisibilityAggregated(isVisible);
2051         updateShownWrapper(mVisibleType);
2052         if (isVisible) {
2053             fireExpandedVisibleListenerIfVisible();
2054         }
2055     }
2056 
2057     /**
2058      * Sets a one-shot listener for when the expanded view becomes visible.
2059      *
2060      * This will fire the listener immediately if the expanded view is already visible.
2061      */
2062     public void setOnExpandedVisibleListener(Runnable r) {
2063         mExpandedVisibleListener = r;
2064         fireExpandedVisibleListenerIfVisible();
2065     }
2066 
2067     /**
2068      * Set a one-shot listener to run when a given content view becomes inactive.
2069      *
2070      * @param visibleType visible type corresponding to the content view to listen
2071      * @param listener runnable to run once when the content view becomes inactive
2072      */
2073     void performWhenContentInactive(int visibleType, Runnable listener) {
2074         View view = getViewForVisibleType(visibleType);
2075         // View is already inactive
2076         if (view == null || isContentViewInactive(visibleType)) {
2077             listener.run();
2078             return;
2079         }
2080         mOnContentViewInactiveListeners.put(view, listener);
2081     }
2082 
2083     /**
2084      * Remove content inactive listeners for a given content view . See
2085      * {@link #performWhenContentInactive}.
2086      *
2087      * @param visibleType visible type corresponding to the content type
2088      */
2089     void removeContentInactiveRunnable(int visibleType) {
2090         View view = getViewForVisibleType(visibleType);
2091         // View is already inactive
2092         if (view == null) {
2093             return;
2094         }
2095 
2096         mOnContentViewInactiveListeners.remove(view);
2097     }
2098 
2099     /**
2100      * Whether or not the content view is inactive.  This means it should not be visible
2101      * or the showing content as removing it would cause visual jank.
2102      *
2103      * @param visibleType visible type corresponding to the content view to be removed
2104      * @return true if the content view is inactive, false otherwise
2105      */
2106     public boolean isContentViewInactive(int visibleType) {
2107         View view = getViewForVisibleType(visibleType);
2108         return isContentViewInactive(view);
2109     }
2110 
2111     /**
2112      * Whether or not the content view is inactive.
2113      *
2114      * @param view view to see if its inactive
2115      * @return true if the view is inactive, false o/w
2116      */
2117     private boolean isContentViewInactive(View view) {
2118         if (view == null) {
2119             return true;
2120         }
2121         return !isShown()
2122                 || (view.getVisibility() != VISIBLE && getViewForVisibleType(mVisibleType) != view);
2123     }
2124 
2125     @Override
2126     protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
2127         super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
2128         if (isContentViewInactive(child)) {
2129             Runnable listener = mOnContentViewInactiveListeners.remove(child);
2130             if (listener != null) {
2131                 listener.run();
2132             }
2133         }
2134     }
2135 
2136     public void setIsLowPriority(boolean isLowPriority) {
2137     }
2138 
2139     public boolean isDimmable() {
2140         return mContractedWrapper != null && mContractedWrapper.isDimmable();
2141     }
2142 
2143     /**
2144      * Should a single click be disallowed on this view when on the keyguard?
2145      */
2146     public boolean disallowSingleClick(float x, float y) {
2147         NotificationViewWrapper visibleWrapper = getVisibleWrapper(getVisibleType());
2148         if (visibleWrapper != null) {
2149             return visibleWrapper.disallowSingleClick(x, y);
2150         }
2151         return false;
2152     }
2153 
2154     public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
2155         boolean needsPaddings = shouldClipToRounding(getVisibleType(), topRounded, bottomRounded);
2156         if (mUserExpanding) {
2157             needsPaddings |= shouldClipToRounding(mTransformationStartVisibleType, topRounded,
2158                     bottomRounded);
2159         }
2160         return needsPaddings;
2161     }
2162 
2163     private boolean shouldClipToRounding(int visibleType, boolean topRounded,
2164             boolean bottomRounded) {
2165         NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
2166         if (visibleWrapper == null) {
2167             return false;
2168         }
2169         return visibleWrapper.shouldClipToRounding(topRounded, bottomRounded);
2170     }
2171 
2172     public CharSequence getActiveRemoteInputText() {
2173         if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
2174             return mExpandedRemoteInput.getText();
2175         }
2176         if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
2177             return mHeadsUpRemoteInput.getText();
2178         }
2179         return null;
2180     }
2181 
2182     @Override
2183     public boolean dispatchTouchEvent(MotionEvent ev) {
2184         float y = ev.getY();
2185         // We still want to distribute touch events to the remote input even if it's outside the
2186         // view boundary. We're therefore manually dispatching these events to the remote view
2187         RemoteInputView riv = getRemoteInputForView(getViewForVisibleType(mVisibleType));
2188         if (riv != null && riv.getVisibility() == VISIBLE) {
2189             int inputStart = mUnrestrictedContentHeight - riv.getHeight();
2190             if (y <= mUnrestrictedContentHeight && y >= inputStart) {
2191                 ev.offsetLocation(0, -inputStart);
2192                 return riv.dispatchTouchEvent(ev);
2193             }
2194         }
2195         return super.dispatchTouchEvent(ev);
2196     }
2197 
2198     /**
2199      * Overridden to make sure touches to the reply action bar actually go through to this view
2200      */
2201     @Override
2202     public boolean pointInView(float localX, float localY, float slop) {
2203         float top = mClipTopAmount;
2204         float bottom = mUnrestrictedContentHeight;
2205         return localX >= -slop && localY >= top - slop && localX < ((mRight - mLeft) + slop) &&
2206                 localY < (bottom + slop);
2207     }
2208 
2209     private RemoteInputView getRemoteInputForView(View child) {
2210         if (child == mExpandedChild) {
2211             return mExpandedRemoteInput;
2212         } else if (child == mHeadsUpChild) {
2213             return mHeadsUpRemoteInput;
2214         }
2215         return null;
2216     }
2217 
2218     public int getExpandHeight() {
2219         int viewType;
2220         if (mExpandedChild != null) {
2221             viewType = VISIBLE_TYPE_EXPANDED;
2222         } else if (mContractedChild != null) {
2223             viewType = VISIBLE_TYPE_CONTRACTED;
2224         } else {
2225             return getMinHeight();
2226         }
2227         return getViewHeight(viewType) + getExtraRemoteInputHeight(mExpandedRemoteInput);
2228     }
2229 
2230     public int getHeadsUpHeight(boolean forceNoHeader) {
2231         int viewType;
2232         if (mHeadsUpChild != null) {
2233             viewType = VISIBLE_TYPE_HEADSUP;
2234         } else if (mContractedChild != null) {
2235             viewType = VISIBLE_TYPE_CONTRACTED;
2236         } else {
2237             return getMinHeight();
2238         }
2239         // The headsUp remote input quickly switches to the expanded one, so lets also include that
2240         // one
2241         return getViewHeight(viewType, forceNoHeader)
2242                 + getExtraRemoteInputHeight(mHeadsUpRemoteInput)
2243                 + getExtraRemoteInputHeight(mExpandedRemoteInput);
2244     }
2245 
2246     public void setRemoteInputVisible(boolean remoteInputVisible) {
2247         mRemoteInputVisible = remoteInputVisible;
2248         setClipChildren(!remoteInputVisible);
2249         setActionsImportanceForAccessibility(
2250                 remoteInputVisible ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
2251                         : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
2252     }
2253 
2254     private void setActionsImportanceForAccessibility(int mode) {
2255         if (mExpandedChild != null) {
2256             setActionsImportanceForAccessibility(mode, mExpandedChild);
2257         }
2258         if (mHeadsUpChild != null) {
2259             setActionsImportanceForAccessibility(mode, mHeadsUpChild);
2260         }
2261     }
2262 
2263     private void setActionsImportanceForAccessibility(int mode, View child) {
2264         View actionsCandidate = child.findViewById(com.android.internal.R.id.actions);
2265         if (actionsCandidate != null) {
2266             actionsCandidate.setImportantForAccessibility(mode);
2267         }
2268     }
2269 
2270     @Override
2271     public void setClipChildren(boolean clipChildren) {
2272         clipChildren = clipChildren && !mRemoteInputVisible;
2273         super.setClipChildren(clipChildren);
2274     }
2275 
2276     public void setHeaderVisibleAmount(float headerVisibleAmount) {
2277         if (mContractedWrapper != null) {
2278             mContractedWrapper.setHeaderVisibleAmount(headerVisibleAmount);
2279         }
2280         if (mHeadsUpWrapper != null) {
2281             mHeadsUpWrapper.setHeaderVisibleAmount(headerVisibleAmount);
2282         }
2283         if (mExpandedWrapper != null) {
2284             mExpandedWrapper.setHeaderVisibleAmount(headerVisibleAmount);
2285         }
2286     }
2287 
2288     public void dump(PrintWriter pw, String[] args) {
2289         pw.print("contentView visibility: " + getVisibility());
2290         pw.print(", alpha: " + getAlpha());
2291         pw.print(", clipBounds: " + getClipBounds());
2292         pw.print(", contentHeight: " + mContentHeight);
2293         pw.print(", visibleType: " + mVisibleType);
2294         View view = getViewForVisibleType(mVisibleType);
2295         pw.print(", visibleView ");
2296         if (view != null) {
2297             pw.print(" visibility: " + view.getVisibility());
2298             pw.print(", alpha: " + view.getAlpha());
2299             pw.print(", clipBounds: " + view.getClipBounds());
2300         } else {
2301             pw.print("null");
2302         }
2303         pw.println();
2304 
2305         if (INCLUDE_HEIGHTS_TO_DUMP) {
2306             dumpContentDimensions(DumpUtilsKt.asIndenting(pw));
2307         }
2308 
2309         pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser);
2310 
2311         pw.print("RemoteInputViews { ");
2312         pw.print(" visibleType: " + mVisibleType);
2313         if (mHeadsUpRemoteInputController != null) {
2314             pw.print(", headsUpRemoteInputController.isActive: "
2315                     + mHeadsUpRemoteInputController.isActive());
2316         } else {
2317             pw.print(", headsUpRemoteInputController: null");
2318         }
2319 
2320         if (mExpandedRemoteInputController != null) {
2321             pw.print(", expandedRemoteInputController.isActive: "
2322                     + mExpandedRemoteInputController.isActive());
2323         } else {
2324             pw.print(", expandedRemoteInputController: null");
2325         }
2326         pw.println(" }");
2327     }
2328 
2329     private String visibleTypeToString(int visibleType) {
2330         return switch (visibleType) {
2331             case VISIBLE_TYPE_CONTRACTED -> "CONTRACTED";
2332             case VISIBLE_TYPE_EXPANDED -> "EXPANDED";
2333             case VISIBLE_TYPE_HEADSUP -> "HEADSUP";
2334             case VISIBLE_TYPE_SINGLELINE -> "SINGLELINE";
2335             default -> "NONE";
2336         };
2337     }
2338 
2339     /** Add content views to dump */
2340     private void dumpContentDimensions(IndentingPrintWriter pw) {
2341         pw.print("ContentDimensions: ");
2342         pw.print("visibleType(String)", visibleTypeToString(mVisibleType));
2343         pw.print("measured width", getMeasuredWidth());
2344         pw.print("measured height", getMeasuredHeight());
2345         pw.print("maxHeight", getMaxHeight());
2346         pw.print("minHeight", getMinHeight());
2347         pw.println();
2348         pw.println("ChildViews:");
2349         DumpUtilsKt.withIncreasedIndent(pw, () -> {
2350             final View contractedChild = mContractedChild;
2351             if (contractedChild != null) {
2352                 dumpChildViewDimensions(pw, contractedChild, "Contracted Child:");
2353                 pw.println();
2354             }
2355 
2356             final View expandedChild = mExpandedChild;
2357             if (expandedChild != null) {
2358                 dumpChildViewDimensions(pw, expandedChild, "Expanded Child:");
2359                 pw.println();
2360             }
2361 
2362             final View headsUpChild = mHeadsUpChild;
2363             if (headsUpChild != null) {
2364                 dumpChildViewDimensions(pw, headsUpChild, "HeadsUp Child:");
2365                 pw.println();
2366             }
2367             final View singleLineView = mSingleLineView;
2368             if (singleLineView != null) {
2369                 dumpChildViewDimensions(pw, singleLineView, "Single Line  View:");
2370                 pw.println();
2371             }
2372 
2373         });
2374         final int expandedRemoteInputHeight = getExtraRemoteInputHeight(mExpandedRemoteInput);
2375         final int headsUpRemoteInputHeight = getExtraRemoteInputHeight(mHeadsUpRemoteInput);
2376         pw.print("expandedRemoteInputHeight", expandedRemoteInputHeight);
2377         pw.print("headsUpRemoteInputHeight", headsUpRemoteInputHeight);
2378         pw.println();
2379     }
2380 
2381     private void dumpChildViewDimensions(IndentingPrintWriter pw, View view,
2382             String name) {
2383         pw.print(name + " ");
2384         DumpUtilsKt.withIncreasedIndent(pw, () -> {
2385             pw.print("width", view.getWidth());
2386             pw.print("height", view.getHeight());
2387             pw.print("measuredWidth", view.getMeasuredWidth());
2388             pw.print("measuredHeight", view.getMeasuredHeight());
2389         });
2390     }
2391 
2392     /** Add any existing SmartReplyView to the dump */
2393     public void dumpSmartReplies(IndentingPrintWriter pw) {
2394         if (mHeadsUpSmartReplyView != null) {
2395             pw.println("HeadsUp SmartReplyView:");
2396             pw.increaseIndent();
2397             mHeadsUpSmartReplyView.dump(pw);
2398             pw.decreaseIndent();
2399         }
2400         if (mExpandedSmartReplyView != null) {
2401             pw.println("Expanded SmartReplyView:");
2402             pw.increaseIndent();
2403             mExpandedSmartReplyView.dump(pw);
2404             pw.decreaseIndent();
2405         }
2406     }
2407 
2408     public RemoteInputView getExpandedRemoteInput() {
2409         return mExpandedRemoteInput;
2410     }
2411 
2412     /**
2413      * @return get the transformation target of the shelf, which usually is the icon
2414      */
2415     public View getShelfTransformationTarget() {
2416         NotificationViewWrapper visibleWrapper = getVisibleWrapper(mVisibleType);
2417         if (visibleWrapper != null) {
2418             return visibleWrapper.getShelfTransformationTarget();
2419         }
2420         return null;
2421     }
2422 
2423     public int getOriginalIconColor() {
2424         NotificationViewWrapper visibleWrapper = getVisibleWrapper(mVisibleType);
2425         if (visibleWrapper != null) {
2426             return visibleWrapper.getOriginalIconColor();
2427         }
2428         return Notification.COLOR_INVALID;
2429     }
2430 
2431     /**
2432      * Delegate the faded state to the notification content views which actually
2433      * need to have overlapping contents render precisely.
2434      */
2435     @Override
2436     public void setNotificationFaded(boolean faded) {
2437         if (mContractedWrapper != null) {
2438             mContractedWrapper.setNotificationFaded(faded);
2439         }
2440         if (mHeadsUpWrapper != null) {
2441             mHeadsUpWrapper.setNotificationFaded(faded);
2442         }
2443         if (mExpandedWrapper != null) {
2444             mExpandedWrapper.setNotificationFaded(faded);
2445         }
2446         if (mSingleLineView != null) {
2447             mSingleLineView.setNotificationFaded(faded);
2448         }
2449     }
2450 
2451     /**
2452      * @return true if a visible view has a remote input active, as this requires that the entire
2453      * row report that it has overlapping rendering.
2454      */
2455     public boolean requireRowToHaveOverlappingRendering() {
2456         // This inexpensive check is done on both states to avoid state invalidating the result.
2457         if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
2458             return true;
2459         }
2460         if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
2461             return true;
2462         }
2463         return false;
2464     }
2465 
2466     /**
2467      * Starts and stops animations in the underlying views.
2468      * Avoids restarting the animations by checking whether they're already running first.
2469      * Return value is used for testing.
2470      *
2471      * @param running whether to start animations running, or stop them.
2472      * @return true if the state of animations changed.
2473      */
2474     public boolean setContentAnimationRunning(boolean running) {
2475         boolean stateChangeRequired = (running != mContentAnimating);
2476         if (stateChangeRequired) {
2477             // Starts or stops the animations in the potential views.
2478             if (mContractedWrapper != null) {
2479                 mContractedWrapper.setAnimationsRunning(running);
2480             }
2481             if (mExpandedWrapper != null) {
2482                 mExpandedWrapper.setAnimationsRunning(running);
2483             }
2484             if (mHeadsUpWrapper != null) {
2485                 mHeadsUpWrapper.setAnimationsRunning(running);
2486             }
2487             // Updates the state tracker.
2488             mContentAnimating = running;
2489             return true;
2490         }
2491         return false;
2492     }
2493 
2494     public void setNotificationWhen(long whenMillis) {
2495         NotificationViewWrapper wrapper = getNotificationViewWrapper();
2496         if (wrapper instanceof NotificationHeaderViewWrapper headerViewWrapper) {
2497             headerViewWrapper.setNotificationWhen(whenMillis);
2498         }
2499     }
2500 
2501     private static class RemoteInputViewData {
2502         @Nullable RemoteInputView mView;
2503         @Nullable RemoteInputViewController mController;
2504     }
2505 
2506     @VisibleForTesting
2507     protected NotificationViewWrapper getContractedWrapper() {
2508         return mContractedWrapper;
2509     }
2510 
2511     @VisibleForTesting
2512     protected NotificationViewWrapper getExpandedWrapper() {
2513         return mExpandedWrapper;
2514     }
2515 
2516     @VisibleForTesting
2517     protected NotificationViewWrapper getHeadsUpWrapper() {
2518         return mHeadsUpWrapper;
2519     }
2520 
2521     @VisibleForTesting
2522     protected void setContractedWrapper(NotificationViewWrapper contractedWrapper) {
2523         mContractedWrapper = contractedWrapper;
2524     }
2525     @VisibleForTesting
2526     protected void setExpandedWrapper(NotificationViewWrapper expandedWrapper) {
2527         mExpandedWrapper = expandedWrapper;
2528     }
2529     @VisibleForTesting
2530     protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) {
2531         mHeadsUpWrapper = headsUpWrapper;
2532     }
2533 
2534     @VisibleForTesting
2535     protected void setAnimationStartVisibleType(int animationStartVisibleType) {
2536         mAnimationStartVisibleType = animationStartVisibleType;
2537     }
2538 
2539     @Override
2540     protected void dispatchDraw(Canvas canvas) {
2541         try {
2542             super.dispatchDraw(canvas);
2543         } catch (Exception e) {
2544             // Catch draw exceptions that may be caused by RemoteViews
2545             Log.e(TAG, "Drawing view failed: " + e);
2546             cancelNotification(e);
2547         }
2548     }
2549 
2550     private void cancelNotification(Exception exception) {
2551         try {
2552             setVisibility(GONE);
2553             final StatusBarNotification sbn = mNotificationEntry.getSbn();
2554             if (mStatusBarService != null) {
2555                 // report notification inflation errors back up
2556                 // to notification delegates
2557                 mStatusBarService.onNotificationError(
2558                         sbn.getPackageName(),
2559                         sbn.getTag(),
2560                         sbn.getId(),
2561                         sbn.getUid(),
2562                         sbn.getInitialPid(),
2563                         exception.getMessage(),
2564                         sbn.getUser().getIdentifier());
2565             }
2566         } catch (RemoteException ex) {
2567             Log.e(TAG, "cancelNotification failed: " + ex);
2568         }
2569     }
2570 }
2571