1 /*
2  * Copyright (C) 2013 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.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY;
20 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
21 
22 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
23 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
24 import static com.android.systemui.util.ColorUtilKt.hexColorString;
25 
26 import android.animation.Animator;
27 import android.animation.AnimatorListenerAdapter;
28 import android.animation.ObjectAnimator;
29 import android.animation.ValueAnimator.AnimatorUpdateListener;
30 import android.app.Notification;
31 import android.content.Context;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.graphics.Canvas;
35 import android.graphics.Path;
36 import android.graphics.Point;
37 import android.graphics.drawable.AnimatedVectorDrawable;
38 import android.graphics.drawable.AnimationDrawable;
39 import android.graphics.drawable.Drawable;
40 import android.os.Build;
41 import android.os.Bundle;
42 import android.os.SystemProperties;
43 import android.os.Trace;
44 import android.os.UserHandle;
45 import android.util.AttributeSet;
46 import android.util.FloatProperty;
47 import android.util.IndentingPrintWriter;
48 import android.util.Log;
49 import android.util.MathUtils;
50 import android.view.KeyEvent;
51 import android.view.LayoutInflater;
52 import android.view.MotionEvent;
53 import android.view.NotificationHeaderView;
54 import android.view.View;
55 import android.view.ViewGroup;
56 import android.view.ViewParent;
57 import android.view.ViewStub;
58 import android.view.accessibility.AccessibilityEvent;
59 import android.view.accessibility.AccessibilityNodeInfo;
60 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
61 import android.widget.Chronometer;
62 import android.widget.FrameLayout;
63 import android.widget.ImageView;
64 
65 import androidx.annotation.NonNull;
66 import androidx.annotation.Nullable;
67 
68 import com.android.app.animation.Interpolators;
69 import com.android.internal.annotations.VisibleForTesting;
70 import com.android.internal.logging.MetricsLogger;
71 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
72 import com.android.internal.statusbar.IStatusBarService;
73 import com.android.internal.util.ContrastColorUtil;
74 import com.android.internal.widget.CachingIconView;
75 import com.android.internal.widget.CallLayout;
76 import com.android.systemui.flags.FeatureFlags;
77 import com.android.systemui.flags.Flags;
78 import com.android.systemui.flags.RefactorFlag;
79 import com.android.systemui.plugins.FalsingManager;
80 import com.android.systemui.plugins.PluginListener;
81 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
82 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
83 import com.android.systemui.plugins.statusbar.StatusBarStateController;
84 import com.android.systemui.res.R;
85 import com.android.systemui.statusbar.RemoteInputController;
86 import com.android.systemui.statusbar.SmartReplyController;
87 import com.android.systemui.statusbar.StatusBarIconView;
88 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
89 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
90 import com.android.systemui.statusbar.notification.FeedbackIcon;
91 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
92 import com.android.systemui.statusbar.notification.NotificationFadeAware;
93 import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
94 import com.android.systemui.statusbar.notification.NotificationUtils;
95 import com.android.systemui.statusbar.notification.SourceType;
96 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
97 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
98 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
99 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
100 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
101 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
102 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
103 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
104 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
105 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
106 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
107 import com.android.systemui.statusbar.notification.stack.AmbientState;
108 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
109 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
110 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
111 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
112 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
113 import com.android.systemui.statusbar.notification.stack.SwipeableView;
114 import com.android.systemui.statusbar.phone.KeyguardBypassController;
115 import com.android.systemui.statusbar.policy.HeadsUpManager;
116 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
117 import com.android.systemui.statusbar.policy.SmartReplyConstants;
118 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
119 import com.android.systemui.util.Compile;
120 import com.android.systemui.util.DumpUtilsKt;
121 import com.android.systemui.wmshell.BubblesManager;
122 
123 import java.io.PrintWriter;
124 import java.util.ArrayList;
125 import java.util.Arrays;
126 import java.util.List;
127 import java.util.Map;
128 import java.util.Optional;
129 import java.util.concurrent.TimeUnit;
130 import java.util.function.BooleanSupplier;
131 import java.util.function.Consumer;
132 
133 /**
134  * View representing a notification item - this can be either the individual child notification or
135  * the group summary (which contains 1 or more child notifications).
136  */
137 public class ExpandableNotificationRow extends ActivatableNotificationView
138         implements PluginListener<NotificationMenuRowPlugin>, SwipeableView,
139         NotificationFadeAware.FadeOptimizedNotification {
140 
141     private static final String TAG = "ExpandableNotifRow";
142     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
143     private static final boolean DEBUG_ONMEASURE =
144             Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
145     private static final int MENU_VIEW_INDEX = 0;
146     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
147     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
148     private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
149     private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
150 
151     // We don't correctly track dark mode until the content views are inflated, so always update
152     // the background on first content update just in case it happens to be during a theme change.
153     private boolean mUpdateSelfBackgroundOnUpdate = true;
154     private boolean mIsSnoozed;
155     private boolean mShowSnooze = false;
156     private boolean mIsFaded;
157 
158     /**
159      * Listener for when {@link ExpandableNotificationRow} is laid out.
160      */
161     public interface LayoutListener {
onLayout()162         void onLayout();
163     }
164 
165     /**
166      * Listens for changes to the expansion state of this row.
167      */
168     public interface OnExpansionChangedListener {
onExpansionChanged(boolean isExpanded)169         void onExpansionChanged(boolean isExpanded);
170     }
171 
172     private StatusBarStateController mStatusBarStateController;
173     private KeyguardBypassController mBypassController;
174     private LayoutListener mLayoutListener;
175     private RowContentBindStage mRowContentBindStage;
176     private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
177     private Optional<BubblesManager> mBubblesManagerOptional;
178     private MetricsLogger mMetricsLogger;
179     private NotificationChildrenContainerLogger mChildrenContainerLogger;
180     private ColorUpdateLogger mColorUpdateLogger;
181     private NotificationDismissibilityProvider mDismissibilityProvider;
182     private FeatureFlags mFeatureFlags;
183     private int mIconTransformContentShift;
184     private int mMaxHeadsUpHeightBeforeN;
185     private int mMaxHeadsUpHeightBeforeP;
186     private int mMaxHeadsUpHeightBeforeS;
187     private int mMaxHeadsUpHeight;
188     private int mMaxHeadsUpHeightIncreased;
189     private int mMaxSmallHeightBeforeN;
190     private int mMaxSmallHeightBeforeP;
191     private int mMaxSmallHeightBeforeS;
192     private int mMaxSmallHeight;
193     private int mMaxSmallHeightLarge;
194     private int mMaxExpandedHeight;
195     private int mNotificationLaunchHeight;
196     private boolean mMustStayOnScreen;
197 
198     /**
199      * Does this row contain layouts that can adapt to row expansion
200      */
201     private boolean mExpandable;
202     /**
203      * Has the user actively changed the expansion state of this row
204      */
205     private boolean mHasUserChangedExpansion;
206     /**
207      * If {@link #mHasUserChangedExpansion}, has the user expanded this row
208      */
209     private boolean mUserExpanded;
210     /**
211      * Has this notification been expanded while it was pinned
212      */
213     private boolean mExpandedWhenPinned;
214     /**
215      * Is the user touching this row
216      */
217     private boolean mUserLocked;
218     /**
219      * Are we showing the "public" version
220      */
221     private boolean mShowingPublic;
222     private boolean mSensitive;
223     private boolean mSensitiveHiddenInGeneral;
224     private boolean mShowPublicExpander = true;
225     private boolean mShowingPublicInitialized;
226     private boolean mHideSensitiveForIntrinsicHeight;
227     private float mHeaderVisibleAmount = DEFAULT_HEADER_VISIBLE_AMOUNT;
228 
229     /**
230      * Is this notification expanded by the system. The expansion state can be overridden by the
231      * user expansion.
232      */
233     private boolean mIsSystemExpanded;
234 
235     /**
236      * Whether the notification is on the keyguard and the expansion is disabled.
237      */
238     private boolean mOnKeyguard;
239 
240     private Animator mTranslateAnim;
241     private ArrayList<View> mTranslateableViews;
242     private NotificationContentView mPublicLayout;
243     private NotificationContentView mPrivateLayout;
244     private NotificationContentView[] mLayouts;
245     private int mNotificationColor;
246     private ExpandableNotificationRowLogger mLogger;
247     private String mLoggingKey;
248     private NotificationGuts mGuts;
249     private NotificationEntry mEntry;
250     private String mAppName;
251     private FalsingManager mFalsingManager;
252 
253     /**
254      * Whether or not the notification is using the heads up view and should peek from the top.
255      */
256     private boolean mIsHeadsUp;
257 
258     private boolean mLastChronometerRunning = true;
259     private ViewStub mChildrenContainerStub;
260     private GroupMembershipManager mGroupMembershipManager;
261     private GroupExpansionManager mGroupExpansionManager;
262     private boolean mChildrenExpanded;
263     private boolean mIsSummaryWithChildren;
264     private NotificationChildrenContainer mChildrenContainer;
265     private NotificationMenuRowPlugin mMenuRow;
266     private ViewStub mGutsStub;
267     private boolean mIsSystemChildExpanded;
268     private boolean mIsPinned;
269     private boolean mExpandAnimationRunning;
270     private AboveShelfChangedListener mAboveShelfChangedListener;
271     private HeadsUpManager mHeadsUpManager;
272     private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
273     private boolean mChildIsExpanding;
274 
275     private boolean mJustClicked;
276     private boolean mAnimationRunning;
277     private boolean mShowNoBackground;
278     private ExpandableNotificationRow mNotificationParent;
279     private OnExpandClickListener mOnExpandClickListener;
280     private View.OnClickListener mOnFeedbackClickListener;
281     private Path mExpandingClipPath;
282 
shouldSimulateSlowMeasure()283     private static boolean shouldSimulateSlowMeasure() {
284         return Compile.IS_DEBUG && RefactorFlag.forView(
285                 Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE).isEnabled();
286     }
287 
288     private static final String SLOW_MEASURE_SIMULATE_DELAY_PROPERTY =
289             "persist.notifications.extra_measure_delay_ms";
290     private static final int SLOW_MEASURE_SIMULATE_DELAY_MS =
291             SystemProperties.getInt(SLOW_MEASURE_SIMULATE_DELAY_PROPERTY, 150);
292 
293     // Listener will be called when receiving a long click event.
294     // Use #setLongPressPosition to optionally assign positional data with the long press.
295     private LongPressListener mLongPressListener;
296 
297     private ExpandableNotificationRowDragController mDragController;
298 
299     private boolean mGroupExpansionChanging;
300 
301     /**
302      * A supplier that returns true if keyguard is secure.
303      */
304     private BooleanSupplier mSecureStateProvider;
305 
306     /**
307      * Whether or not a notification that is not part of a group of notifications can be manually
308      * expanded by the user.
309      */
310     private boolean mEnableNonGroupedNotificationExpand;
311 
312     /**
313      * Whether or not to update the background of the header of the notification when its expanded.
314      * If {@code true}, the header background will disappear when expanded.
315      */
316     private boolean mShowGroupBackgroundWhenExpanded;
317 
318     /**
319      * True if we always show the collapsed layout on lockscreen because vertical space is low.
320      */
321     private boolean mSaveSpaceOnLockscreen;
322 
323     /**
324      * True if we use intrinsic height regardless of vertical space available on lockscreen.
325      */
326     private boolean mIgnoreLockscreenConstraints;
327 
328     private OnClickListener mExpandClickListener = new OnClickListener() {
329         @Override
330         public void onClick(View v) {
331             if (!shouldShowPublic() && (!mIsMinimized || isExpanded())
332                     && mGroupMembershipManager.isGroupSummary(mEntry)) {
333                 mGroupExpansionChanging = true;
334                 final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
335                 boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(mEntry);
336                 mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
337                 mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, nowExpanded);
338                 onExpansionChanged(true /* userAction */, wasExpanded);
339             } else if (mEnableNonGroupedNotificationExpand) {
340                 if (v.isAccessibilityFocused()) {
341                     mPrivateLayout.setFocusOnVisibilityChange();
342                 }
343                 boolean nowExpanded;
344                 if (isPinned()) {
345                     nowExpanded = !mExpandedWhenPinned;
346                     mExpandedWhenPinned = nowExpanded;
347                     // Also notify any expansion changed listeners. This is necessary since the
348                     // expansion doesn't actually change (it's already system expanded) but it
349                     // changes visually
350                     if (mExpansionChangedListener != null) {
351                         mExpansionChangedListener.onExpansionChanged(nowExpanded);
352                     }
353                 } else {
354                     nowExpanded = !isExpanded();
355                     setUserExpanded(nowExpanded);
356                 }
357                 notifyHeightChanged(/* needsAnimation= */ true);
358                 mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
359                 mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded);
360             }
361         }
362     };
363     private boolean mKeepInParentForDismissAnimation;
364     private boolean mRemoved;
365     public static final FloatProperty<ExpandableNotificationRow> TRANSLATE_CONTENT =
366             new FloatProperty<>("translate") {
367                 @Override
368                 public void setValue(ExpandableNotificationRow object, float value) {
369                     object.setTranslation(value);
370                 }
371 
372                 @Override
373                 public Float get(ExpandableNotificationRow object) {
374                     return object.getTranslation();
375                 }
376             };
377 
378     private OnClickListener mOnClickListener;
379     @Nullable
380     private OnClickListener mBubbleClickListener;
381     private OnDragSuccessListener mOnDragSuccessListener;
382     private boolean mHeadsupDisappearRunning;
383     private View mChildAfterViewWhenDismissed;
384     private View mGroupParentWhenDismissed;
385     private boolean mAboveShelf;
386     private OnUserInteractionCallback mOnUserInteractionCallback;
387     private NotificationGutsManager mNotificationGutsManager;
388     private boolean mIsMinimized;
389     private boolean mUseIncreasedCollapsedHeight;
390     private boolean mUseIncreasedHeadsUpHeight;
391     private float mTranslationWhenRemoved;
392     private boolean mWasChildInGroupWhenRemoved;
393     private final NotificationInlineImageResolver mImageResolver;
394     private BigPictureIconManager mBigPictureIconManager;
395     @Nullable
396     private OnExpansionChangedListener mExpansionChangedListener;
397     @Nullable
398     private Runnable mOnIntrinsicHeightReachedRunnable;
399 
400     private float mTopRoundnessDuringLaunchAnimation;
401     private float mBottomRoundnessDuringLaunchAnimation;
402     private float mSmallRoundness;
403 
getLayouts()404     public NotificationContentView[] getLayouts() {
405         return Arrays.copyOf(mLayouts, mLayouts.length);
406     }
407 
408     /**
409      * Is this entry pinned and was expanded while doing so
410      */
isPinnedAndExpanded()411     public boolean isPinnedAndExpanded() {
412         if (!isPinned()) {
413             return false;
414         }
415         return mExpandedWhenPinned;
416     }
417 
418     @Override
isGroupExpansionChanging()419     public boolean isGroupExpansionChanging() {
420         if (isChildInGroup()) {
421             return mNotificationParent.isGroupExpansionChanging();
422         }
423         return mGroupExpansionChanging;
424     }
425 
setSaveSpaceOnLockscreen(boolean saveSpace)426     public void setSaveSpaceOnLockscreen(boolean saveSpace) {
427         mSaveSpaceOnLockscreen = saveSpace;
428     }
429 
getSaveSpaceOnLockscreen()430     public boolean getSaveSpaceOnLockscreen() {
431         return mSaveSpaceOnLockscreen;
432     }
433 
setGroupExpansionChanging(boolean changing)434     public void setGroupExpansionChanging(boolean changing) {
435         mGroupExpansionChanging = changing;
436     }
437 
438     @Override
setActualHeightAnimating(boolean animating)439     public void setActualHeightAnimating(boolean animating) {
440         if (mPrivateLayout != null) {
441             mPrivateLayout.setContentHeightAnimating(animating);
442         }
443     }
444 
getPrivateLayout()445     public NotificationContentView getPrivateLayout() {
446         return mPrivateLayout;
447     }
448 
getPublicLayout()449     public NotificationContentView getPublicLayout() {
450         return mPublicLayout;
451     }
452 
453     /**
454      * Sets animations running in the layouts of this row, including public, private, and children.
455      *
456      * @param running whether the animations should be started running or stopped.
457      */
setAnimationRunning(boolean running)458     public void setAnimationRunning(boolean running) {
459         // Sets animations running in the private/public layouts.
460         for (NotificationContentView l : mLayouts) {
461             if (l != null) {
462                 l.setContentAnimationRunning(running);
463                 setIconAnimationRunning(running, l);
464             }
465         }
466         // For groups summaries with children, we want to set the children containers
467         // animating as well.
468         if (mIsSummaryWithChildren) {
469             NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper();
470             if (viewWrapper != null) {
471                 setIconAnimationRunningForChild(running, viewWrapper.getIcon());
472             }
473             NotificationViewWrapper lowPriWrapper = mChildrenContainer
474                     .getMinimizedGroupHeaderWrapper();
475             if (lowPriWrapper != null) {
476                 setIconAnimationRunningForChild(running, lowPriWrapper.getIcon());
477             }
478             List<ExpandableNotificationRow> notificationChildren =
479                     mChildrenContainer.getAttachedChildren();
480             for (int i = 0; i < notificationChildren.size(); i++) {
481                 ExpandableNotificationRow child = notificationChildren.get(i);
482                 child.setAnimationRunning(running);
483             }
484         }
485         mAnimationRunning = running;
486     }
487 
488     /**
489      * Starts or stops animations of the icons in all potential content views (regardless of
490      * whether they're contracted, expanded, etc).
491      *
492      * @param running whether to start or stop the icon's animation.
493      */
setIconAnimationRunning(boolean running, NotificationContentView layout)494     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
495         if (layout != null) {
496             View contractedChild = layout.getContractedChild();
497             View expandedChild = layout.getExpandedChild();
498             View headsUpChild = layout.getHeadsUpChild();
499             setIconAnimationRunningForChild(running, contractedChild);
500             setIconAnimationRunningForChild(running, expandedChild);
501             setIconAnimationRunningForChild(running, headsUpChild);
502         }
503     }
504 
505     /**
506      * Starts or stops animations of the icon in the provided view's icon and right icon.
507      *
508      * @param running whether to start or stop the icon's animation.
509      * @param child   the view with the icon to start or stop.
510      */
setIconAnimationRunningForChild(boolean running, View child)511     private void setIconAnimationRunningForChild(boolean running, View child) {
512         if (child != null) {
513             ImageView icon = child.findViewById(com.android.internal.R.id.icon);
514             setImageViewAnimationRunning(icon, running);
515             ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
516             setImageViewAnimationRunning(rightIcon, running);
517         }
518     }
519 
520     /**
521      * Starts or stops the animation of a provided image view if it's an AnimationDrawable or an
522      * AnimatedVectorDrawable.
523      *
524      * @param imageView the image view on which to start/stop animation.
525      * @param running   whether to start or stop the view's animation.
526      */
setImageViewAnimationRunning(ImageView imageView, boolean running)527     private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
528         if (imageView != null) {
529             Drawable drawable = imageView.getDrawable();
530             if (drawable instanceof AnimationDrawable animationDrawable) {
531                 if (running) {
532                     animationDrawable.start();
533                 } else {
534                     animationDrawable.stop();
535                 }
536             } else if (drawable instanceof AnimatedVectorDrawable animationDrawable) {
537                 if (running) {
538                     animationDrawable.start();
539                 } else {
540                     animationDrawable.stop();
541                 }
542             }
543         }
544     }
545 
546     /**
547      * Marks a content view as freeable, setting it so that future inflations do not reinflate
548      * and ensuring that the view is freed when it is safe to remove.
549      *
550      * @param inflationFlag flag corresponding to the content view to be freed
551      * @deprecated By default, {@link NotificationRowContentBinder#unbindContent} will tell the
552      * view hierarchy to only free when the view is safe to remove so this method is no longer
553      * needed. Will remove when all uses are gone.
554      */
555     @Deprecated
freeContentViewWhenSafe(@nflationFlag int inflationFlag)556     public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
557         RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
558         params.markContentViewsFreeable(inflationFlag);
559         mRowContentBindStage.requestRebind(mEntry, null /* callback */);
560     }
561 
562     /**
563      * Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif
564      * or is in an allowList).
565      */
getIsNonblockable()566     public boolean getIsNonblockable() {
567         if (mEntry == null) {
568             return true;
569         }
570         return !mEntry.isBlockable();
571     }
572 
isConversation()573     private boolean isConversation() {
574         return mPeopleNotificationIdentifier.getPeopleNotificationType(mEntry)
575                 != PeopleNotificationIdentifier.TYPE_NON_PERSON;
576     }
577 
onNotificationUpdated()578     public void onNotificationUpdated() {
579         if (mIsSummaryWithChildren) {
580             Trace.beginSection("ExpNotRow#onNotifUpdated (summary)");
581         } else {
582             Trace.beginSection("ExpNotRow#onNotifUpdated (leaf)");
583         }
584         for (NotificationContentView l : mLayouts) {
585             l.onNotificationUpdated(mEntry);
586         }
587         mShowingPublicInitialized = false;
588         updateNotificationColor();
589         if (mMenuRow != null) {
590             mMenuRow.onNotificationUpdated(mEntry.getSbn());
591             mMenuRow.setAppName(mAppName);
592         }
593         if (mIsSummaryWithChildren) {
594             if (AsyncGroupHeaderViewInflation.isEnabled()) {
595                 mChildrenContainer.updateGroupHeaderExpandState();
596             } else {
597                 // We create the header from the background thread instead
598                 mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
599                         isConversation());
600             }
601             mChildrenContainer.onNotificationUpdated();
602         }
603         if (mAnimationRunning) {
604             setAnimationRunning(true);
605         }
606         if (mLastChronometerRunning) {
607             setChronometerRunning(true);
608         }
609         if (mNotificationParent != null) {
610             mNotificationParent.updateChildrenAppearance();
611         }
612         onAttachedChildrenCountChanged();
613         mPublicLayout.updateExpandButtons(mShowPublicExpander);
614         updateLimits();
615         updateShelfIconColor();
616         if (mUpdateSelfBackgroundOnUpdate) {
617             // Because this is triggered by UiMode change which we already propagated to children,
618             // we know that child rows will receive the same event, and will update their own
619             // backgrounds when they finish inflating, so propagating again would be redundant.
620             mUpdateSelfBackgroundOnUpdate = false;
621             updateBackgroundColorsOfSelf();
622         }
623         Trace.endSection();
624     }
625 
updateBackgroundColorsOfSelf()626     private void updateBackgroundColorsOfSelf() {
627         super.updateBackgroundColors();
628         if (mColorUpdateLogger.isEnabled()) {
629             mColorUpdateLogger.logNotificationEvent("ENR.updateBackgroundColorsOfSelf()",
630                     mLoggingKey,
631                     "normalBgColor=" + hexColorString(getNormalBgColor())
632                             + " background=" + mBackgroundNormal.toDumpString());
633         }
634     }
635 
636     @Override
updateBackgroundColors()637     public void updateBackgroundColors() {
638         // Because this call is made by the NSSL only on attached rows at the moment of the
639         // UiMode or Theme change, we have to propagate to our child views.
640         updateBackgroundColorsOfSelf();
641         if (mIsSummaryWithChildren) {
642             for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) {
643                 child.updateBackgroundColors();
644             }
645         }
646     }
647 
648     /**
649      * Called when the notification's ranking was changed (but nothing else changed).
650      */
onNotificationRankingUpdated()651     public void onNotificationRankingUpdated() {
652         if (mMenuRow != null) {
653             mMenuRow.onNotificationUpdated(mEntry.getSbn());
654         }
655     }
656 
657     /**
658      * Call when bubble state has changed and the button on the notification should be updated.
659      */
updateBubbleButton()660     public void updateBubbleButton() {
661         for (NotificationContentView l : mLayouts) {
662             l.updateBubbleButton(mEntry);
663         }
664     }
665 
666     @VisibleForTesting
updateShelfIconColor()667     void updateShelfIconColor() {
668         StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();
669         boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
670         boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
671                 ContrastColorUtil.getInstance(mContext));
672         int color = StatusBarIconView.NO_COLOR;
673         if (colorize) {
674             color = getOriginalIconColor();
675         }
676         expandedIcon.setStaticDrawableColor(color);
677     }
678 
getOriginalIconColor()679     public int getOriginalIconColor() {
680         if (mIsSummaryWithChildren && !shouldShowPublic()) {
681             if (!AsyncGroupHeaderViewInflation.isEnabled()) {
682                 return mChildrenContainer.getVisibleWrapper().getOriginalIconColor();
683             }
684         }
685         int color = getShowingLayout().getOriginalIconColor();
686         if (color != Notification.COLOR_INVALID) {
687             return color;
688         } else {
689             return mEntry.getContrastedColor(mContext, mIsMinimized && !isExpanded(),
690                     getBackgroundColorWithoutTint());
691         }
692     }
693 
setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener)694     public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) {
695         mAboveShelfChangedListener = aboveShelfChangedListener;
696     }
697 
698     /**
699      * Sets a supplier that can determine whether the keyguard is secure or not.
700      *
701      * @param secureStateProvider A function that returns true if keyguard is secure.
702      */
setSecureStateProvider(BooleanSupplier secureStateProvider)703     public void setSecureStateProvider(BooleanSupplier secureStateProvider) {
704         mSecureStateProvider = secureStateProvider;
705     }
706 
updateLimits()707     private void updateLimits() {
708         for (NotificationContentView l : mLayouts) {
709             updateLimitsForView(l);
710         }
711     }
712 
updateLimitsForView(NotificationContentView layout)713     private void updateLimitsForView(NotificationContentView layout) {
714         View contractedView = layout.getContractedChild();
715         boolean customView = contractedView != null
716                 && contractedView.getId()
717                 != com.android.internal.R.id.status_bar_latest_event_content;
718         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
719         boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
720         boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S;
721         int smallHeight;
722 
723         boolean isCallLayout = contractedView instanceof CallLayout;
724 
725         if (customView && beforeS && !mIsSummaryWithChildren) {
726             if (beforeN) {
727                 smallHeight = mMaxSmallHeightBeforeN;
728             } else if (beforeP) {
729                 smallHeight = mMaxSmallHeightBeforeP;
730             } else {
731                 smallHeight = mMaxSmallHeightBeforeS;
732             }
733         } else if (isCallLayout) {
734             smallHeight = mMaxExpandedHeight;
735         } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
736             smallHeight = mMaxSmallHeightLarge;
737         } else {
738             smallHeight = mMaxSmallHeight;
739         }
740         boolean headsUpCustom = layout.getHeadsUpChild() != null &&
741                 layout.getHeadsUpChild().getId()
742                         != com.android.internal.R.id.status_bar_latest_event_content;
743         int headsUpHeight;
744         if (headsUpCustom && beforeS) {
745             if (beforeN) {
746                 headsUpHeight = mMaxHeadsUpHeightBeforeN;
747             } else if (beforeP) {
748                 headsUpHeight = mMaxHeadsUpHeightBeforeP;
749             } else {
750                 headsUpHeight = mMaxHeadsUpHeightBeforeS;
751             }
752         } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
753             headsUpHeight = mMaxHeadsUpHeightIncreased;
754         } else {
755             headsUpHeight = mMaxHeadsUpHeight;
756         }
757         NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
758                 VISIBLE_TYPE_HEADSUP);
759         if (headsUpWrapper != null) {
760             headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight());
761         }
762         layout.setHeights(smallHeight, headsUpHeight, mMaxExpandedHeight);
763     }
764 
765     @NonNull
766     public NotificationEntry getEntry() {
767         return mEntry;
768     }
769 
770     @Override
771     public boolean isHeadsUp() {
772         return mIsHeadsUp;
773     }
774 
775     public void setHeadsUp(boolean isHeadsUp) {
776         boolean wasAboveShelf = isAboveShelf();
777         int intrinsicBefore = getIntrinsicHeight();
778         mIsHeadsUp = isHeadsUp;
779         mPrivateLayout.setHeadsUp(isHeadsUp);
780         if (mIsSummaryWithChildren) {
781             // The overflow might change since we allow more lines as HUN.
782             mChildrenContainer.updateGroupOverflow();
783         }
784         if (intrinsicBefore != getIntrinsicHeight()) {
785             notifyHeightChanged(/* needsAnimation= */ false);
786         }
787         if (isHeadsUp) {
788             mMustStayOnScreen = true;
789             setAboveShelf(true);
790         } else if (isAboveShelf() != wasAboveShelf) {
791             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
792         }
793     }
794 
795     @Override
796     public boolean showingPulsing() {
797         return isHeadsUpState() && (isDozing() || (mOnKeyguard && isBypassEnabled()));
798     }
799 
800     /**
801      * @return if the view is in heads up state, i.e either still heads upped or it's disappearing.
802      */
803     @Override
804     public boolean isHeadsUpState() {
805         return mIsHeadsUp || mHeadsupDisappearRunning;
806     }
807 
808     public void setRemoteInputController(RemoteInputController r) {
809         mPrivateLayout.setRemoteInputController(r);
810     }
811 
812     public void addChildNotification(ExpandableNotificationRow row) {
813         addChildNotification(row, -1);
814     }
815 
816     /**
817      * Set the how much the header should be visible. A value of 0 will make the header fully gone
818      * and a value of 1 will make the notification look just like normal.
819      * This is being used for heads up notifications, when they are pinned to the top of the screen
820      * and the header content is extracted to the statusbar.
821      *
822      * @param headerVisibleAmount the amount the header should be visible.
823      */
824     public void setHeaderVisibleAmount(float headerVisibleAmount) {
825         if (mHeaderVisibleAmount != headerVisibleAmount) {
826             mHeaderVisibleAmount = headerVisibleAmount;
827             for (NotificationContentView l : mLayouts) {
828                 l.setHeaderVisibleAmount(headerVisibleAmount);
829             }
830             if (mChildrenContainer != null) {
831                 mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount);
832             }
833             notifyHeightChanged(/* needsAnimation= */ false);
834         }
835     }
836 
837     @Override
838     public float getHeaderVisibleAmount() {
839         return mHeaderVisibleAmount;
840     }
841 
842     @Override
843     public void setHeadsUpIsVisible() {
844         super.setHeadsUpIsVisible();
845         mMustStayOnScreen = false;
846     }
847 
848     /**
849      *
850      * @return true when compact version of Heads Up is on the screen.
851      */
852     public boolean isCompactConversationHeadsUpOnScreen() {
853         final NotificationViewWrapper viewWrapper =
854                 getVisibleNotificationViewWrapper();
855 
856         return viewWrapper instanceof NotificationCompactMessagingTemplateViewWrapper;
857     }
858     /**
859      * @see NotificationChildrenContainer#setUntruncatedChildCount(int)
860      */
861     public void setUntruncatedChildCount(int childCount) {
862         if (mChildrenContainer == null) {
863             mChildrenContainerStub.inflate();
864         }
865         mChildrenContainer.setUntruncatedChildCount(childCount);
866     }
867 
868     /**
869      * @see NotificationChildrenContainer#setNotificationGroupWhen(long)
870      */
871     public void setNotificationGroupWhen(long whenMillis) {
872         if (mIsSummaryWithChildren) {
873             mChildrenContainer.setNotificationGroupWhen(whenMillis);
874             mPublicLayout.setNotificationWhen(whenMillis);
875         } else {
876             Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")"
877                     + " mIsSummaryWithChildren: false"
878                     + " mChildrenContainer has not been inflated yet.");
879         }
880     }
881 
882     /**
883      * Called after children have been attached to set the expansion states
884      */
885     public void resetChildSystemExpandedStates() {
886         if (isSummaryWithChildren()) {
887             mChildrenContainer.updateExpansionStates();
888         }
889     }
890 
891     /**
892      * Add a child notification to this view.
893      *
894      * @param row        the row to add
895      * @param childIndex the index to add it at, if -1 it will be added at the end
896      */
897     public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
898         if (mChildrenContainer == null) {
899             mChildrenContainerStub.inflate();
900         }
901 
902         if (row.keepInParentForDismissAnimation()) {
903             logSkipAttachingKeepInParentChild(row);
904             return;
905         }
906 
907         mChildrenContainer.addNotification(row, childIndex);
908         onAttachedChildrenCountChanged();
909         row.setIsChildInGroup(true, this);
910     }
911 
912     public void removeChildNotification(ExpandableNotificationRow row) {
913         if (mChildrenContainer != null) {
914             mChildrenContainer.removeNotification(row);
915             row.setKeepInParentForDismissAnimation(false);
916         }
917         onAttachedChildrenCountChanged();
918         row.setIsChildInGroup(false, null);
919     }
920 
921     /**
922      * Removes the children notifications which were marked to keep for the dismissal animation.
923      */
924     public void removeChildrenWithKeepInParent() {
925         if (mChildrenContainer == null) return;
926 
927         List<ExpandableNotificationRow> clonedList = new ArrayList<>(
928                 mChildrenContainer.getAttachedChildren());
929         boolean childCountChanged = false;
930         for (ExpandableNotificationRow child : clonedList) {
931             if (child.keepInParentForDismissAnimation()) {
932                 mChildrenContainer.removeNotification(child);
933                 child.setIsChildInGroup(false, null);
934                 child.setKeepInParentForDismissAnimation(false);
935                 logKeepInParentChildDetached(child);
936                 childCountChanged = true;
937             }
938         }
939 
940         if (childCountChanged) {
941             onAttachedChildrenCountChanged();
942         }
943     }
944 
945     /**
946      * Returns the child notification at [index], or null if no such child.
947      */
948     @Nullable
949     public ExpandableNotificationRow getChildNotificationAt(int index) {
950         if (mChildrenContainer == null
951                 || mChildrenContainer.getAttachedChildren().size() <= index) {
952             return null;
953         } else {
954             return mChildrenContainer.getAttachedChildren().get(index);
955         }
956     }
957 
958     @Override
959     public boolean isChildInGroup() {
960         return mNotificationParent != null;
961     }
962 
963     public ExpandableNotificationRow getNotificationParent() {
964         return mNotificationParent;
965     }
966 
967     /**
968      * @param isChildInGroup Is this notification now in a group
969      * @param parent         the new parent notification
970      */
971     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
972         if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
973             mNotificationParent.setChildIsExpanding(false);
974             mNotificationParent.setExpandingClipPath(null);
975             mNotificationParent.setExtraWidthForClipping(0.0f);
976             mNotificationParent.setMinimumHeightForClipping(0);
977         }
978         mNotificationParent = isChildInGroup ? parent : null;
979         mPrivateLayout.setIsChildInGroup(isChildInGroup);
980 
981         updateBackgroundForGroupState();
982         updateClickAndFocus();
983         if (mNotificationParent != null) {
984             setOverrideTintColor(NO_COLOR, 0.0f);
985             mNotificationParent.updateBackgroundForGroupState();
986         }
987         updateBackgroundClipping();
988         updateBaseRoundness();
989     }
990 
991     @Override
992     public boolean onInterceptTouchEvent(MotionEvent ev) {
993         // Other parts of the system may intercept and handle all the falsing.
994         // Otherwise, if we see motion and follow-on events, try to classify them as a tap.
995         if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
996             mFalsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY);
997         }
998         return super.onInterceptTouchEvent(ev);
999     }
1000 
1001     @Override
1002     public boolean onTouchEvent(MotionEvent event) {
1003         if (event.getActionMasked() != MotionEvent.ACTION_DOWN
1004                 || !isChildInGroup() || isGroupExpanded()) {
1005             return super.onTouchEvent(event);
1006         } else {
1007             return false;
1008         }
1009     }
1010 
1011     @Override
1012     public boolean isSummaryWithChildren() {
1013         return mIsSummaryWithChildren;
1014     }
1015 
1016     @Override
1017     public boolean areChildrenExpanded() {
1018         return mChildrenExpanded;
1019     }
1020 
1021     public List<ExpandableNotificationRow> getAttachedChildren() {
1022         return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();
1023     }
1024 
1025     /**
1026      * Recursively collects the [{@link ExpandableViewState#location}]s populating the provided
1027      * map.
1028      * The visibility of each child is determined by the {@link View#getVisibility()}.
1029      * Locations are added to the provided map including locations from child views, that are
1030      * visible.
1031      */
1032     public void collectVisibleLocations(Map<String, Integer> locationsMap) {
1033         if (getVisibility() == View.VISIBLE) {
1034             locationsMap.put(getEntry().getKey(), getViewState().location);
1035             if (mChildrenContainer != null) {
1036                 List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren();
1037                 for (int i = 0; i < children.size(); i++) {
1038                     children.get(i).collectVisibleLocations(locationsMap);
1039                 }
1040             }
1041         }
1042     }
1043 
1044     /**
1045      * Updates states of all children.
1046      */
1047     public void updateChildrenStates() {
1048         if (mIsSummaryWithChildren) {
1049             ExpandableViewState parentState = getViewState();
1050             mChildrenContainer.updateState(parentState);
1051         }
1052     }
1053 
1054     /**
1055      * Applies children states.
1056      */
1057     public void applyChildrenState() {
1058         if (mIsSummaryWithChildren) {
1059             mChildrenContainer.applyState();
1060         }
1061     }
1062 
1063     /**
1064      * Prepares expansion changed.
1065      */
1066     public void prepareExpansionChanged() {
1067         if (mIsSummaryWithChildren) {
1068             mChildrenContainer.prepareExpansionChanged();
1069         }
1070     }
1071 
1072     /**
1073      * Starts child animations.
1074      */
1075     public void startChildAnimation(AnimationProperties properties) {
1076         if (mIsSummaryWithChildren) {
1077             mChildrenContainer.startAnimationToState(properties);
1078         }
1079     }
1080 
1081     public ExpandableNotificationRow getViewAtPosition(float y) {
1082         if (!mIsSummaryWithChildren || !mChildrenExpanded) {
1083             return this;
1084         } else {
1085             ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
1086             return view == null ? this : view;
1087         }
1088     }
1089 
1090     public NotificationGuts getGuts() {
1091         return mGuts;
1092     }
1093 
1094     /**
1095      * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
1096      * the notification will be rendered on top of the screen.
1097      *
1098      * @param pinned whether it is pinned
1099      */
1100     public void setPinned(boolean pinned) {
1101         int intrinsicHeight = getIntrinsicHeight();
1102         boolean wasAboveShelf = isAboveShelf();
1103         mIsPinned = pinned;
1104         if (intrinsicHeight != getIntrinsicHeight()) {
1105             notifyHeightChanged(/* needsAnimation= */ false);
1106         }
1107         if (pinned) {
1108             setAnimationRunning(true);
1109             mExpandedWhenPinned = false;
1110         } else if (mExpandedWhenPinned) {
1111             setUserExpanded(true);
1112         }
1113         setChronometerRunning(mLastChronometerRunning);
1114         if (isAboveShelf() != wasAboveShelf) {
1115             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
1116         }
1117     }
1118 
1119     @Override
1120     public boolean isPinned() {
1121         return mIsPinned;
1122     }
1123 
1124     @Override
1125     public int getPinnedHeadsUpHeight() {
1126         return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
1127     }
1128 
1129     /**
1130      * @param atLeastMinHeight should the value returned be at least the minimum height.
1131      *                         Used to avoid cyclic calls
1132      * @return the height of the heads up notification when pinned
1133      */
1134     private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
1135         if (mIsSummaryWithChildren) {
1136             return mChildrenContainer.getIntrinsicHeight();
1137         }
1138         if (mExpandedWhenPinned) {
1139             return Math.max(getMaxExpandHeight(), getHeadsUpHeight());
1140         } else if (atLeastMinHeight) {
1141             return Math.max(getCollapsedHeight(), getHeadsUpHeight());
1142         } else {
1143             return getHeadsUpHeight();
1144         }
1145     }
1146 
1147     /**
1148      * Mark whether this notification was just clicked, i.e. the user has just clicked this
1149      * notification in this frame.
1150      */
1151     public void setJustClicked(boolean justClicked) {
1152         mJustClicked = justClicked;
1153     }
1154 
1155     /**
1156      * @return true if this notification has been clicked in this frame, false otherwise
1157      */
1158     public boolean wasJustClicked() {
1159         return mJustClicked;
1160     }
1161 
1162     public void setChronometerRunning(boolean running) {
1163         mLastChronometerRunning = running;
1164         setChronometerRunning(running, mPrivateLayout);
1165         setChronometerRunning(running, mPublicLayout);
1166         if (mChildrenContainer != null) {
1167             List<ExpandableNotificationRow> notificationChildren =
1168                     mChildrenContainer.getAttachedChildren();
1169             for (int i = 0; i < notificationChildren.size(); i++) {
1170                 ExpandableNotificationRow child = notificationChildren.get(i);
1171                 child.setChronometerRunning(running);
1172             }
1173         }
1174     }
1175 
1176     private void setChronometerRunning(boolean running, NotificationContentView layout) {
1177         if (layout != null) {
1178             running = running || isPinned();
1179             View contractedChild = layout.getContractedChild();
1180             View expandedChild = layout.getExpandedChild();
1181             View headsUpChild = layout.getHeadsUpChild();
1182             setChronometerRunningForChild(running, contractedChild);
1183             setChronometerRunningForChild(running, expandedChild);
1184             setChronometerRunningForChild(running, headsUpChild);
1185         }
1186     }
1187 
1188     private void setChronometerRunningForChild(boolean running, View child) {
1189         if (child != null) {
1190             View chronometer = child.findViewById(com.android.internal.R.id.chronometer);
1191             if (chronometer instanceof Chronometer) {
1192                 ((Chronometer) chronometer).setStarted(running);
1193             }
1194         }
1195     }
1196 
1197     /**
1198      * @return the main notification view wrapper.
1199      */
1200     public NotificationViewWrapper getNotificationViewWrapper() {
1201         if (mIsSummaryWithChildren) {
1202             return mChildrenContainer.getNotificationViewWrapper();
1203         }
1204         return mPrivateLayout.getNotificationViewWrapper();
1205     }
1206 
1207     /**
1208      * @return the currently visible notification view wrapper. This can be different from
1209      * {@link #getNotificationViewWrapper()} in case it is a low-priority group.
1210      */
1211     public NotificationViewWrapper getVisibleNotificationViewWrapper() {
1212         if (mIsSummaryWithChildren && !shouldShowPublic()) {
1213             return mChildrenContainer.getVisibleWrapper();
1214         }
1215         return getShowingLayout().getVisibleWrapper();
1216     }
1217 
1218     /**
1219      * @return whether the notification row is long clickable or not.
1220      */
1221     public boolean isNotificationRowLongClickable() {
1222         if (mLongPressListener == null) {
1223             return false;
1224         }
1225 
1226         if (!areGutsExposed()) { // guts is not opened
1227             return true;
1228         }
1229 
1230         // if it is leave behind, it shouldn't be long clickable.
1231         return !isGutsLeaveBehind();
1232     }
1233 
1234     public void setLongPressListener(LongPressListener longPressListener) {
1235         mLongPressListener = longPressListener;
1236     }
1237 
1238     public void setDragController(ExpandableNotificationRowDragController dragController) {
1239         mDragController = dragController;
1240     }
1241 
1242     @Override
1243     public void setOnClickListener(@Nullable OnClickListener l) {
1244         super.setOnClickListener(l);
1245         mOnClickListener = l;
1246         updateClickAndFocus();
1247     }
1248 
1249     /**
1250      * The click listener for the bubble button.
1251      */
1252     @Nullable
1253     public View.OnClickListener getBubbleClickListener() {
1254         return mBubbleClickListener;
1255     }
1256 
1257     /**
1258      * Sets the click listener for the bubble button.
1259      */
1260     public void setBubbleClickListener(@Nullable OnClickListener l) {
1261         mBubbleClickListener = l;
1262         // ensure listener is passed to the content views
1263         mPrivateLayout.updateBubbleButton(mEntry);
1264         mPublicLayout.updateBubbleButton(mEntry);
1265     }
1266 
1267     /**
1268      * The click listener for the snooze button.
1269      */
1270     public View.OnClickListener getSnoozeClickListener(MenuItem item) {
1271         return v -> {
1272             // Dismiss a snoozed notification if one is still left behind
1273             mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
1274                     false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
1275                     false /* resetMenu */);
1276             mNotificationGutsManager.openGuts(this, 0, 0, item);
1277             mIsSnoozed = true;
1278         };
1279     }
1280 
1281     private void updateClickAndFocus() {
1282         boolean normalChild = !isChildInGroup() || isGroupExpanded();
1283         boolean clickable = mOnClickListener != null && normalChild;
1284         if (isFocusable() != normalChild) {
1285             setFocusable(normalChild);
1286         }
1287         if (isClickable() != clickable) {
1288             setClickable(clickable);
1289         }
1290     }
1291 
1292     public void setGutsView(MenuItem item) {
1293         if (getGuts() != null && item.getGutsView() instanceof NotificationGuts.GutsContent) {
1294             getGuts().setGutsContent((NotificationGuts.GutsContent) item.getGutsView());
1295         }
1296     }
1297 
1298     @Override
1299     public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) {
1300         boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null;
1301         if (existed) {
1302             removeView(mMenuRow.getMenuView());
1303         }
1304         if (plugin == null) {
1305             return;
1306         }
1307         mMenuRow = plugin;
1308         if (mMenuRow.shouldUseDefaultMenuItems()) {
1309             ArrayList<MenuItem> items = new ArrayList<>();
1310             items.add(NotificationMenuRow.createConversationItem(mContext));
1311             items.add(NotificationMenuRow.createPartialConversationItem(mContext));
1312             items.add(NotificationMenuRow.createInfoItem(mContext));
1313             items.add(NotificationMenuRow.createSnoozeItem(mContext));
1314             mMenuRow.setMenuItems(items);
1315         }
1316         if (existed) {
1317             createMenu();
1318         }
1319     }
1320 
1321     @Override
1322     public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
1323         boolean existed = mMenuRow.getMenuView() != null;
1324         mMenuRow = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
1325         if (existed) {
1326             createMenu();
1327         }
1328     }
1329 
1330     @Override
1331     public boolean hasFinishedInitialization() {
1332         return getEntry().hasFinishedInitialization();
1333     }
1334 
1335     /**
1336      * Get a handle to a NotificationMenuRowPlugin whose menu view has been added to our hierarchy,
1337      * or null if there is no menu row
1338      *
1339      * @return a {@link NotificationMenuRowPlugin}, or null
1340      */
1341     @Nullable
1342     public NotificationMenuRowPlugin createMenu() {
1343         if (mMenuRow == null) {
1344             return null;
1345         }
1346         if (mMenuRow.getMenuView() == null) {
1347             mMenuRow.createMenu(this, mEntry.getSbn());
1348             mMenuRow.setAppName(mAppName);
1349             FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
1350                     LayoutParams.MATCH_PARENT);
1351             addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp);
1352         }
1353         return mMenuRow;
1354     }
1355 
1356     @Nullable
1357     public NotificationMenuRowPlugin getProvider() {
1358         return mMenuRow;
1359     }
1360 
1361     @Override
1362     public void onDensityOrFontScaleChanged() {
1363         super.onDensityOrFontScaleChanged();
1364         initDimens();
1365         initBackground();
1366         reInflateViews();
1367     }
1368 
1369     private void reInflateViews() {
1370         Trace.beginSection("ExpandableNotificationRow#reInflateViews");
1371         // Let's update our childrencontainer. This is intentionally not guarded with
1372         // mIsSummaryWithChildren since we might have had children but not anymore.
1373         if (mChildrenContainer != null) {
1374             mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.getSbn());
1375         }
1376         if (mGuts != null) {
1377             NotificationGuts oldGuts = mGuts;
1378             int index = indexOfChild(oldGuts);
1379             removeView(oldGuts);
1380             mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate(
1381                     R.layout.notification_guts, this, false);
1382             mGuts.setVisibility(oldGuts.isExposed() ? VISIBLE : GONE);
1383             addView(mGuts, index);
1384         }
1385         View oldMenu = mMenuRow == null ? null : mMenuRow.getMenuView();
1386         if (oldMenu != null) {
1387             int menuIndex = indexOfChild(oldMenu);
1388             removeView(oldMenu);
1389             mMenuRow.createMenu(ExpandableNotificationRow.this, mEntry.getSbn());
1390             mMenuRow.setAppName(mAppName);
1391             addView(mMenuRow.getMenuView(), menuIndex);
1392         }
1393         for (NotificationContentView l : mLayouts) {
1394             l.reinflate();
1395             l.reInflateViews();
1396         }
1397         mEntry.getSbn().clearPackageContext();
1398         // TODO: Move content inflation logic out of this call
1399         RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
1400         params.setNeedsReinflation(true);
1401         mRowContentBindStage.requestRebind(mEntry, null /* callback */);
1402         Trace.endSection();
1403     }
1404 
1405     @Override
1406     public void onConfigurationChanged(Configuration newConfig) {
1407         super.onConfigurationChanged(newConfig);
1408         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
1409             mMenuRow.onConfigurationChanged();
1410         }
1411         if (mImageResolver != null) {
1412             mImageResolver.updateMaxImageSizes();
1413         }
1414         if (mBigPictureIconManager != null) {
1415             mBigPictureIconManager.updateMaxImageSizes();
1416         }
1417     }
1418 
1419     public void onUiModeChanged() {
1420         mUpdateSelfBackgroundOnUpdate = true;
1421         reInflateViews();
1422         if (mChildrenContainer != null) {
1423             for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) {
1424                 child.onUiModeChanged();
1425             }
1426         }
1427     }
1428 
1429     public void setContentBackground(int customBackgroundColor, boolean animate,
1430             NotificationContentView notificationContentView) {
1431         if (getShowingLayout() == notificationContentView) {
1432             setTintColor(customBackgroundColor, animate);
1433         }
1434     }
1435 
1436     @Override
1437     protected void setBackgroundTintColor(int color) {
1438         super.setBackgroundTintColor(color);
1439         NotificationContentView view = getShowingLayout();
1440         if (view != null) {
1441             view.setBackgroundTintColor(color);
1442         }
1443     }
1444 
1445     public void closeRemoteInput() {
1446         for (NotificationContentView l : mLayouts) {
1447             l.closeRemoteInput();
1448         }
1449     }
1450 
1451     /**
1452      * Set by how much the single line view should be indented.
1453      */
1454     public void setSingleLineWidthIndention(int indention) {
1455         mPrivateLayout.setSingleLineWidthIndention(indention);
1456     }
1457 
1458     public int getNotificationColor() {
1459         return mNotificationColor;
1460     }
1461 
1462     public void updateNotificationColor() {
1463         Configuration currentConfig = getResources().getConfiguration();
1464         boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
1465                 == Configuration.UI_MODE_NIGHT_YES;
1466 
1467         mNotificationColor = ContrastColorUtil.resolveContrastColor(mContext,
1468                 mEntry.getSbn().getNotification().color,
1469                 getBackgroundColorWithoutTint(), nightMode);
1470     }
1471 
1472     public HybridNotificationView getSingleLineView() {
1473         return mPrivateLayout.getSingleLineView();
1474     }
1475 
1476     public boolean isOnKeyguard() {
1477         return mOnKeyguard;
1478     }
1479 
1480     @Override
1481     public void dismiss(boolean refocusOnDismiss) {
1482         super.dismiss(refocusOnDismiss);
1483         setLongPressListener(null);
1484         setDragController(null);
1485         mGroupParentWhenDismissed = mNotificationParent;
1486         mChildAfterViewWhenDismissed = null;
1487         mEntry.getIcons().getStatusBarIcon().setDismissed();
1488         if (isChildInGroup()) {
1489             List<ExpandableNotificationRow> notificationChildren =
1490                     mNotificationParent.getAttachedChildren();
1491             int i = notificationChildren.indexOf(this);
1492             if (i != -1 && i < notificationChildren.size() - 1) {
1493                 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1);
1494             }
1495         }
1496     }
1497 
1498     /**
1499      * @return if this entry should be kept in its parent during removal.
1500      */
1501     public boolean keepInParentForDismissAnimation() {
1502         return mKeepInParentForDismissAnimation;
1503     }
1504 
1505     public void setKeepInParentForDismissAnimation(boolean keepInParent) {
1506         mKeepInParentForDismissAnimation = keepInParent;
1507     }
1508 
1509     /** @return true if the User has dismissed this notif's parent */
1510     public boolean isParentDismissed() {
1511         return getEntry().getDismissState() == PARENT_DISMISSED;
1512     }
1513 
1514     @Override
1515     public boolean isRemoved() {
1516         return mRemoved;
1517     }
1518 
1519     public void setRemoved() {
1520         mRemoved = true;
1521         mTranslationWhenRemoved = getTranslationY();
1522         mWasChildInGroupWhenRemoved = isChildInGroup();
1523         if (isChildInGroup()) {
1524             mTranslationWhenRemoved += getNotificationParent().getTranslationY();
1525         }
1526         for (NotificationContentView l : mLayouts) {
1527             l.setRemoved();
1528         }
1529     }
1530 
1531     public boolean wasChildInGroupWhenRemoved() {
1532         return mWasChildInGroupWhenRemoved;
1533     }
1534 
1535     public float getTranslationWhenRemoved() {
1536         return mTranslationWhenRemoved;
1537     }
1538 
1539     public NotificationChildrenContainer getChildrenContainer() {
1540         return mChildrenContainer;
1541     }
1542 
1543     /**
1544      * @return An non-null instance of mChildrenContainer, inflate it if not yet.
1545      */
1546     public @NonNull NotificationChildrenContainer getChildrenContainerNonNull() {
1547         if (mChildrenContainer == null) {
1548             mChildrenContainerStub.inflate();
1549         }
1550         return mChildrenContainer;
1551     }
1552 
1553     /**
1554      * Set the group notification header view
1555      * @param headerView header view to set
1556      */
1557     public void setGroupHeader(NotificationHeaderView headerView) {
1558         NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull();
1559         childrenContainer.setGroupHeader(
1560                 /* headerView= */ headerView,
1561                 /* onClickListener= */ mExpandClickListener
1562         );
1563     }
1564 
1565     /**
1566      * Set the low-priority group notification header view
1567      * @param headerView header view to set
1568      */
1569     public void setMinimizedGroupHeader(NotificationHeaderView headerView) {
1570         NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull();
1571         childrenContainer.setLowPriorityGroupHeader(
1572                 /* headerViewLowPriority= */ headerView,
1573                 /* onClickListener= */ mExpandClickListener
1574         );
1575     }
1576 
1577     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
1578         boolean wasAboveShelf = isAboveShelf();
1579         boolean changed = headsUpAnimatingAway != mHeadsupDisappearRunning;
1580         mHeadsupDisappearRunning = headsUpAnimatingAway;
1581         mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway);
1582         if (changed && mHeadsUpAnimatingAwayListener != null) {
1583             mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway);
1584         }
1585         if (isAboveShelf() != wasAboveShelf) {
1586             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
1587         }
1588     }
1589 
1590     public void setHeadsUpAnimatingAwayListener(Consumer<Boolean> listener) {
1591         mHeadsUpAnimatingAwayListener = listener;
1592     }
1593 
1594     /**
1595      * @return if the view was just heads upped and is now animating away. During such a time the
1596      * layout needs to be kept consistent
1597      */
1598     @Override
1599     public boolean isHeadsUpAnimatingAway() {
1600         return mHeadsupDisappearRunning;
1601     }
1602 
1603     public View getChildAfterViewWhenDismissed() {
1604         return mChildAfterViewWhenDismissed;
1605     }
1606 
1607     public View getGroupParentWhenDismissed() {
1608         return mGroupParentWhenDismissed;
1609     }
1610 
1611     /**
1612      * Dismisses the notification.
1613      *
1614      * @param fromAccessibility whether this dismiss is coming from an accessibility action
1615      */
1616     public void performDismiss(boolean fromAccessibility) {
1617         mMetricsLogger.count(NotificationCounters.NOTIFICATION_DISMISSED, 1);
1618         dismiss(fromAccessibility);
1619         if (canEntryBeDismissed()) {
1620             if (mOnUserInteractionCallback != null) {
1621                 mOnUserInteractionCallback.registerFutureDismissal(mEntry, REASON_CANCEL).run();
1622             }
1623         }
1624     }
1625 
1626     @Override
1627     public View getShelfTransformationTarget() {
1628         if (mIsSummaryWithChildren && !shouldShowPublic()) {
1629             NotificationViewWrapper viewWrapper = mChildrenContainer.getVisibleWrapper();
1630             if (AsyncGroupHeaderViewInflation.isEnabled() && viewWrapper == null) {
1631                 return null;
1632             }
1633             return viewWrapper.getShelfTransformationTarget();
1634         }
1635         return getShowingLayout().getShelfTransformationTarget();
1636     }
1637 
1638     /**
1639      * @return whether the notification is currently showing a view with an icon.
1640      */
1641     public boolean isShowingIcon() {
1642         if (areGutsExposed()) {
1643             return false;
1644         }
1645         return getShelfTransformationTarget() != null;
1646     }
1647 
1648     @Override
1649     protected void updateContentTransformation() {
1650         if (mExpandAnimationRunning) {
1651             return;
1652         }
1653         super.updateContentTransformation();
1654     }
1655 
1656     @Override
1657     protected void applyContentTransformation(float contentAlpha, float translationY) {
1658         super.applyContentTransformation(contentAlpha, translationY);
1659         if (!mIsLastChild) {
1660             // Don't fade views unless we're last
1661             contentAlpha = 1.0f;
1662         }
1663         for (NotificationContentView l : mLayouts) {
1664             l.setAlpha(contentAlpha);
1665             l.setTranslationY(translationY);
1666         }
1667         if (mChildrenContainer != null) {
1668             mChildrenContainer.setAlpha(contentAlpha);
1669             mChildrenContainer.setTranslationY(translationY);
1670             // TODO: handle children fade out better
1671         }
1672     }
1673 
1674     /**
1675      * Sets the alpha on the content, while leaving the background of the row itself as is.
1676      *
1677      * @param alpha alpha value to apply to the notification content
1678      */
1679     public void setContentAlpha(float alpha) {
1680         for (NotificationContentView l : mLayouts) {
1681             l.setAlpha(alpha);
1682         }
1683         if (mChildrenContainer != null) {
1684             mChildrenContainer.setContentAlpha(alpha);
1685         }
1686     }
1687 
1688     /**
1689      * Set if the row is minimized.
1690      */
1691     public void setIsMinimized(boolean isMinimized) {
1692         mIsMinimized = isMinimized;
1693         mPrivateLayout.setIsLowPriority(isMinimized);
1694         if (mChildrenContainer != null) {
1695             mChildrenContainer.setIsMinimized(isMinimized);
1696         }
1697     }
1698 
1699     public boolean isMinimized() {
1700         return mIsMinimized;
1701     }
1702 
1703     public void setUsesIncreasedCollapsedHeight(boolean use) {
1704         mUseIncreasedCollapsedHeight = use;
1705     }
1706 
1707     public void setUsesIncreasedHeadsUpHeight(boolean use) {
1708         mUseIncreasedHeadsUpHeight = use;
1709     }
1710 
1711     /**
1712      * Interface for logging {{@link ExpandableNotificationRow} events.}
1713      */
1714     public interface ExpandableNotificationRowLogger {
1715         /**
1716          * Called when the notification is expanded / collapsed.
1717          */
1718         void logNotificationExpansion(String key, int location, boolean userAction,
1719                 boolean expanded);
1720 
1721         /**
1722          * Called when a notification which was previously kept in its parent for the
1723          * dismiss animation is finally detached from its parent.
1724          */
1725         void logKeepInParentChildDetached(NotificationEntry child, NotificationEntry oldParent);
1726 
1727         /**
1728          * Called when we want to attach a notification to a new parent,
1729          * but it still has the keepInParent flag set, so we skip it.
1730          */
1731         void logSkipAttachingKeepInParentChild(
1732                 NotificationEntry child,
1733                 NotificationEntry newParent
1734         );
1735 
1736         /**
1737          * Called when an ExpandableNotificationRow transient view is removed from the
1738          * NotificationChildrenContainer
1739          */
1740         void logRemoveTransientFromContainer(
1741                 NotificationEntry childEntry,
1742                 NotificationEntry containerEntry
1743         );
1744 
1745         /**
1746          * Called when an ExpandableNotificationRow transient view is removed from the
1747          * NotificationStackScrollLayout
1748          */
1749         void logRemoveTransientFromNssl(
1750                 NotificationEntry childEntry
1751         );
1752 
1753         /**
1754          * Called when an ExpandableNotificationRow transient view is removed from a ViewGroup that
1755          * is not NotificationChildrenContainer or NotificationStackScrollLayout
1756          */
1757         void logRemoveTransientFromViewGroup(
1758                 NotificationEntry childEntry,
1759                 ViewGroup containerView
1760         );
1761 
1762         /**
1763          * Called when an ExpandableNotificationRow transient view is added to this
1764          * ExpandableNotificationRow
1765          */
1766         void logAddTransientRow(
1767                 NotificationEntry childEntry,
1768                 NotificationEntry containerEntry,
1769                 int index
1770         );
1771 
1772         /**
1773          * Called when an ExpandableNotificationRow transient view is removed from this
1774          * ExpandableNotificationRow
1775          */
1776         void logRemoveTransientRow(
1777                 NotificationEntry childEntry,
1778                 NotificationEntry containerEntry
1779         );
1780     }
1781 
1782     /**
1783      * Constructs an ExpandableNotificationRow. Used by layout inflation.
1784      *
1785      * @param context passed to image resolver
1786      * @param attrs   attributes used to initialize parent view
1787      */
1788     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
1789         this(context, attrs, context);
1790         // NOTE(b/317503801): Always crash when using the insecure constructor.
1791         throw new UnsupportedOperationException("Insecure constructor");
1792     }
1793 
1794     /**
1795      * Constructs an ExpandableNotificationRow. Used by layout inflation (with a custom {@code
1796      * AsyncLayoutFactory} in {@link RowInflaterTask}.
1797      *
1798      * @param context context context of the view
1799      * @param attrs   attributes used to initialize parent view
1800      * @param entry   notification that the row will be associated to (determines the user for the
1801      *                ImageResolver)
1802      */
1803     public ExpandableNotificationRow(Context context, AttributeSet attrs, NotificationEntry entry) {
1804         this(context, attrs, userContextForEntry(context, entry));
1805     }
1806 
1807     private static Context userContextForEntry(Context base, NotificationEntry entry) {
1808         if (base.getUserId() == entry.getSbn().getNormalizedUserId()) {
1809             return base;
1810         }
1811         return base.createContextAsUser(
1812                 UserHandle.of(entry.getSbn().getNormalizedUserId()), /* flags= */ 0);
1813     }
1814 
1815     private ExpandableNotificationRow(Context sysUiContext, AttributeSet attrs,
1816             Context userContext) {
1817         super(sysUiContext, attrs);
1818         mImageResolver = new NotificationInlineImageResolver(userContext,
1819                 new NotificationInlineImageCache());
1820         float radius = getResources().getDimension(R.dimen.notification_corner_radius_small);
1821         mSmallRoundness = radius / getMaxRadius();
1822         initDimens();
1823     }
1824 
1825     /**
1826      * Initialize row.
1827      */
1828     public void initialize(
1829             NotificationEntry entry,
1830             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
1831             String appName,
1832             String notificationKey,
1833             ExpandableNotificationRowLogger logger,
1834             KeyguardBypassController bypassController,
1835             GroupMembershipManager groupMembershipManager,
1836             GroupExpansionManager groupExpansionManager,
1837             HeadsUpManager headsUpManager,
1838             RowContentBindStage rowContentBindStage,
1839             OnExpandClickListener onExpandClickListener,
1840             CoordinateOnClickListener onFeedbackClickListener,
1841             FalsingManager falsingManager,
1842             StatusBarStateController statusBarStateController,
1843             PeopleNotificationIdentifier peopleNotificationIdentifier,
1844             OnUserInteractionCallback onUserInteractionCallback,
1845             Optional<BubblesManager> bubblesManagerOptional,
1846             NotificationGutsManager gutsManager,
1847             NotificationDismissibilityProvider dismissibilityProvider,
1848             MetricsLogger metricsLogger,
1849             NotificationChildrenContainerLogger childrenContainerLogger,
1850             ColorUpdateLogger colorUpdateLogger,
1851             SmartReplyConstants smartReplyConstants,
1852             SmartReplyController smartReplyController,
1853             FeatureFlags featureFlags,
1854             IStatusBarService statusBarService) {
1855         mEntry = entry;
1856         mAppName = appName;
1857         if (mMenuRow == null) {
1858             mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier);
1859         }
1860         if (mMenuRow.getMenuView() != null) {
1861             mMenuRow.setAppName(mAppName);
1862         }
1863         mLogger = logger;
1864         mLoggingKey = notificationKey;
1865         mBypassController = bypassController;
1866         mGroupMembershipManager = groupMembershipManager;
1867         mGroupExpansionManager = groupExpansionManager;
1868         mPrivateLayout.setGroupMembershipManager(groupMembershipManager);
1869         mHeadsUpManager = headsUpManager;
1870         mRowContentBindStage = rowContentBindStage;
1871         mOnExpandClickListener = onExpandClickListener;
1872         setOnFeedbackClickListener(onFeedbackClickListener);
1873         mFalsingManager = falsingManager;
1874         mStatusBarStateController = statusBarStateController;
1875         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
1876         for (NotificationContentView l : mLayouts) {
1877             l.initialize(
1878                     mPeopleNotificationIdentifier,
1879                     rivSubcomponentFactory,
1880                     smartReplyConstants,
1881                     smartReplyController,
1882                     statusBarService);
1883         }
1884         mOnUserInteractionCallback = onUserInteractionCallback;
1885         mBubblesManagerOptional = bubblesManagerOptional;
1886         mNotificationGutsManager = gutsManager;
1887         mMetricsLogger = metricsLogger;
1888         mChildrenContainerLogger = childrenContainerLogger;
1889         mColorUpdateLogger = colorUpdateLogger;
1890         mDismissibilityProvider = dismissibilityProvider;
1891         mFeatureFlags = featureFlags;
1892     }
1893 
1894     private void initDimens() {
1895         mMaxSmallHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
1896                 R.dimen.notification_min_height_legacy);
1897         mMaxSmallHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
1898                 R.dimen.notification_min_height_before_p);
1899         mMaxSmallHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext,
1900                 R.dimen.notification_min_height_before_s);
1901         mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
1902                 R.dimen.notification_min_height);
1903         mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
1904                 R.dimen.notification_min_height_increased);
1905         mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
1906                 R.dimen.notification_max_height);
1907         mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
1908                 R.dimen.notification_max_heads_up_height_legacy);
1909         mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
1910                 R.dimen.notification_max_heads_up_height_before_p);
1911         mMaxHeadsUpHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext,
1912                 R.dimen.notification_max_heads_up_height_before_s);
1913         mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
1914                 R.dimen.notification_max_heads_up_height);
1915         mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
1916                 R.dimen.notification_max_heads_up_height_increased);
1917 
1918         Resources res = getResources();
1919         mEnableNonGroupedNotificationExpand =
1920                 res.getBoolean(R.bool.config_enableNonGroupedNotificationExpand);
1921         mShowGroupBackgroundWhenExpanded =
1922                 res.getBoolean(R.bool.config_showGroupNotificationBgWhenExpanded);
1923     }
1924 
1925     NotificationInlineImageResolver getImageResolver() {
1926         return mImageResolver;
1927     }
1928 
1929     public BigPictureIconManager getBigPictureIconManager() {
1930         return mBigPictureIconManager;
1931     }
1932 
1933     public void setBigPictureIconManager(
1934             BigPictureIconManager bigPictureIconManager) {
1935         mBigPictureIconManager = bigPictureIconManager;
1936     }
1937 
1938 
1939     /**
1940      * Resets this view so it can be re-used for an updated notification.
1941      */
1942     public void reset() {
1943         mShowingPublicInitialized = false;
1944         unDismiss();
1945         if (mMenuRow == null || !mMenuRow.isMenuVisible()) {
1946             resetTranslation();
1947         }
1948         onHeightReset();
1949         requestLayout();
1950 
1951         setTargetPoint(null);
1952     }
1953 
1954     /**
1955      * Shows the given feedback icon, or hides the icon if null.
1956      */
1957     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
1958         if (mIsSummaryWithChildren) {
1959             mChildrenContainer.setFeedbackIcon(icon);
1960         }
1961         mPrivateLayout.setFeedbackIcon(icon);
1962         mPublicLayout.setFeedbackIcon(icon);
1963     }
1964 
1965     /**
1966      * Sets the last time the notification being displayed audibly alerted the user.
1967      */
1968     public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
1969         long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
1970         boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
1971 
1972         applyAudiblyAlertedRecently(alertedRecently);
1973 
1974         removeCallbacks(mExpireRecentlyAlertedFlag);
1975         if (alertedRecently) {
1976             long timeUntilNoLongerRecent = RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly;
1977             postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent);
1978         }
1979     }
1980 
1981     @VisibleForTesting
1982     protected void setEntry(NotificationEntry entry) {
1983         mEntry = entry;
1984     }
1985 
1986     private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
1987 
1988     private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
1989         if (mIsSummaryWithChildren) {
1990             mChildrenContainer.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
1991         }
1992         mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
1993         mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
1994     }
1995 
1996     public View.OnClickListener getFeedbackOnClickListener() {
1997         return mOnFeedbackClickListener;
1998     }
1999 
2000     void setOnFeedbackClickListener(CoordinateOnClickListener l) {
2001         mOnFeedbackClickListener = v -> {
2002             createMenu();
2003             NotificationMenuRowPlugin provider = getProvider();
2004             if (provider == null) {
2005                 return;
2006             }
2007             MenuItem menuItem = provider.getFeedbackMenuItem(mContext);
2008             if (menuItem != null) {
2009                 l.onClick(this, v.getWidth() / 2, v.getHeight() / 2, menuItem);
2010             }
2011         };
2012     }
2013 
2014     @Override
2015     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2016         Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
2017         if (DEBUG_ONMEASURE) {
2018             Log.d(TAG, "onMeasure("
2019                     + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
2020                     + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
2021         }
2022         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
2023 
2024         if (shouldSimulateSlowMeasure()) {
2025             simulateExtraMeasureDelay();
2026         }
2027         Trace.endSection();
2028     }
2029 
2030     private void simulateExtraMeasureDelay() {
2031         // Add extra delay in a notification row instead of NotificationStackScrollLayout
2032         // to make sure that when the measure cache is used we won't add this delay
2033         try {
2034             Trace.beginSection("ExtraDebugMeasureDelay");
2035             Thread.sleep(SLOW_MEASURE_SIMULATE_DELAY_MS);
2036         } catch (InterruptedException e) {
2037             throw new RuntimeException(e);
2038         } finally {
2039             Trace.endSection();
2040         }
2041     }
2042 
2043     /**
2044      * Generates and appends "(MessagingStyle)" type tag to passed string for tracing.
2045      */
2046     @NonNull
2047     private String appendTraceStyleTag(@NonNull String traceTag) {
2048         if (!Trace.isEnabled()) {
2049             return traceTag;
2050         }
2051 
2052         return traceTag + "(" + getEntry().getNotificationStyle() + ")";
2053     }
2054 
2055     @Override
2056     protected void onFinishInflate() {
2057         super.onFinishInflate();
2058         mPublicLayout = findViewById(R.id.expandedPublic);
2059         mPrivateLayout = findViewById(R.id.expanded);
2060         mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
2061 
2062         for (NotificationContentView l : mLayouts) {
2063             l.setExpandClickListener(mExpandClickListener);
2064             l.setContainingNotification(this);
2065         }
2066         mGutsStub = findViewById(R.id.notification_guts_stub);
2067         mGutsStub.setOnInflateListener((stub, inflated) -> {
2068             mGuts = (NotificationGuts) inflated;
2069             mGuts.setClipTopAmount(getClipTopAmount());
2070             mGuts.setActualHeight(getActualHeight());
2071             mGutsStub = null;
2072         });
2073         mChildrenContainerStub = findViewById(R.id.child_container_stub);
2074         mChildrenContainerStub.setOnInflateListener((stub, inflated) -> {
2075             mChildrenContainer = (NotificationChildrenContainer) inflated;
2076             mChildrenContainer.setIsMinimized(mIsMinimized);
2077             mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
2078             mChildrenContainer.onNotificationUpdated();
2079             mChildrenContainer.setLogger(mChildrenContainerLogger);
2080 
2081             mTranslateableViews.add(mChildrenContainer);
2082         });
2083 
2084         // Add the views that we translate to reveal the menu
2085         mTranslateableViews = new ArrayList<>();
2086         for (int i = 0; i < getChildCount(); i++) {
2087             mTranslateableViews.add(getChildAt(i));
2088         }
2089         // Remove views that don't translate
2090         mTranslateableViews.remove(mChildrenContainerStub);
2091         mTranslateableViews.remove(mGutsStub);
2092         // We don't handle focus highlight in this view, it's done in background drawable instead
2093         setDefaultFocusHighlightEnabled(false);
2094     }
2095 
2096     /**
2097      * Called once when starting drag motion after opening notification guts,
2098      * in case of notification that has {@link android.app.Notification#contentIntent}
2099      * and it is to start an activity.
2100      */
2101     public void doDragCallback(float x, float y) {
2102         if (mDragController != null) {
2103             setTargetPoint(new Point((int) x, (int) y));
2104             mDragController.startDragAndDrop(this);
2105         }
2106     }
2107 
2108     public void setOnDragSuccessListener(OnDragSuccessListener listener) {
2109         mOnDragSuccessListener = listener;
2110     }
2111 
2112     /**
2113      * Called when a notification is dropped on proper target window.
2114      */
2115     public void dragAndDropSuccess() {
2116         if (mOnDragSuccessListener != null) {
2117             mOnDragSuccessListener.onDragSuccess(getEntry());
2118         }
2119     }
2120 
2121     private void doLongClickCallback() {
2122         doLongClickCallback(getWidth() / 2, getHeight() / 2);
2123     }
2124 
2125     public void doLongClickCallback(int x, int y) {
2126         createMenu();
2127         NotificationMenuRowPlugin provider = getProvider();
2128         MenuItem menuItem = null;
2129         if (provider != null) {
2130             menuItem = provider.getLongpressMenuItem(mContext);
2131         }
2132         doLongClickCallback(x, y, menuItem);
2133     }
2134 
2135     /**
2136      * Perform a smart action which triggers a longpress (expose guts).
2137      * Based on the semanticAction passed, may update the state of the guts view.
2138      *
2139      * @param semanticAction associated with this smart action click
2140      */
2141     public void doSmartActionClick(int x, int y, int semanticAction) {
2142         createMenu();
2143         NotificationMenuRowPlugin provider = getProvider();
2144         MenuItem menuItem = null;
2145         if (provider != null) {
2146             menuItem = provider.getLongpressMenuItem(mContext);
2147         }
2148         if (SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == semanticAction
2149                 && menuItem.getGutsView() instanceof NotificationConversationInfo) {
2150             NotificationConversationInfo info =
2151                     (NotificationConversationInfo) menuItem.getGutsView();
2152             info.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
2153         }
2154         doLongClickCallback(x, y, menuItem);
2155     }
2156 
2157     private void doLongClickCallback(int x, int y, MenuItem menuItem) {
2158         if (mLongPressListener != null && menuItem != null) {
2159             mLongPressListener.onLongPress(this, x, y, menuItem);
2160         }
2161     }
2162 
2163     @Override
2164     public boolean onKeyDown(int keyCode, KeyEvent event) {
2165         if (KeyEvent.isConfirmKey(keyCode)) {
2166             event.startTracking();
2167             return true;
2168         }
2169         return super.onKeyDown(keyCode, event);
2170     }
2171 
2172     @Override
2173     public boolean onKeyUp(int keyCode, KeyEvent event) {
2174         if (KeyEvent.isConfirmKey(keyCode)) {
2175             if (!event.isCanceled()) {
2176                 performClick();
2177             }
2178             return true;
2179         }
2180         return super.onKeyUp(keyCode, event);
2181     }
2182 
2183     @Override
2184     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
2185         if (KeyEvent.isConfirmKey(keyCode)) {
2186             doLongClickCallback();
2187             return true;
2188         }
2189         return false;
2190     }
2191 
2192     public void resetTranslation() {
2193         if (mTranslateAnim != null) {
2194             mTranslateAnim.cancel();
2195         }
2196 
2197         if (mDismissUsingRowTranslationX) {
2198             setTranslationX(0);
2199         } else if (mTranslateableViews != null) {
2200             for (int i = 0; i < mTranslateableViews.size(); i++) {
2201                 mTranslateableViews.get(i).setTranslationX(0);
2202             }
2203             invalidateOutline();
2204             getEntry().getIcons().getShelfIcon().setScrollX(0);
2205         }
2206 
2207         if (mMenuRow != null) {
2208             mMenuRow.resetMenu();
2209         }
2210     }
2211 
2212     void onGutsOpened() {
2213         resetTranslation();
2214         updateContentAccessibilityImportanceForGuts(false /* isEnabled */);
2215     }
2216 
2217     void onGutsClosed() {
2218         updateContentAccessibilityImportanceForGuts(true /* isEnabled */);
2219         mIsSnoozed = false;
2220     }
2221 
2222     /**
2223      * Updates whether all the non-guts content inside this row is important for accessibility.
2224      *
2225      * @param isEnabled whether the content views should be enabled for accessibility
2226      */
2227     private void updateContentAccessibilityImportanceForGuts(boolean isEnabled) {
2228         updateAccessibilityImportance(isEnabled);
2229 
2230         if (mChildrenContainer != null) {
2231             updateChildAccessibilityImportance(mChildrenContainer, isEnabled);
2232         }
2233         if (mLayouts != null) {
2234             for (View view : mLayouts) {
2235                 updateChildAccessibilityImportance(view, isEnabled);
2236             }
2237         }
2238 
2239         if (isEnabled) {
2240             this.requestAccessibilityFocus();
2241         }
2242     }
2243 
2244     /**
2245      * Updates whether this view is important for accessibility based on {@code isEnabled}.
2246      */
2247     private void updateAccessibilityImportance(boolean isEnabled) {
2248         setImportantForAccessibility(isEnabled
2249                 ? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
2250                 : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
2251     }
2252 
2253     /**
2254      * Updates whether the given childView is important for accessibility based on
2255      * {@code isEnabled}.
2256      */
2257     private void updateChildAccessibilityImportance(View childView, boolean isEnabled) {
2258         childView.setImportantForAccessibility(isEnabled
2259                 ? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
2260                 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
2261     }
2262 
2263     public CharSequence getActiveRemoteInputText() {
2264         return mPrivateLayout.getActiveRemoteInputText();
2265     }
2266 
2267     /**
2268      * Reset the translation with an animation.
2269      */
2270     public void animateResetTranslation() {
2271         if (mTranslateAnim != null) {
2272             mTranslateAnim.cancel();
2273         }
2274         mTranslateAnim = getTranslateViewAnimator(0, null /* updateListener */);
2275         if (mTranslateAnim != null) {
2276             mTranslateAnim.start();
2277         }
2278     }
2279 
2280     /**
2281      * Set the dismiss behavior of the view.
2282      *
2283      * @param usingRowTranslationX {@code true} if the view should translate using regular
2284      *                             translationX, otherwise the contents will be
2285      *                             translated.
2286      */
2287     @Override
2288     public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
2289         if (usingRowTranslationX != mDismissUsingRowTranslationX) {
2290             // In case we were already transitioning, let's switch over!
2291             float previousTranslation = getTranslation();
2292             if (previousTranslation != 0) {
2293                 setTranslation(0);
2294             }
2295             super.setDismissUsingRowTranslationX(usingRowTranslationX);
2296             if (previousTranslation != 0) {
2297                 setTranslation(previousTranslation);
2298             }
2299             if (mChildrenContainer != null) {
2300                 List<ExpandableNotificationRow> notificationChildren =
2301                         mChildrenContainer.getAttachedChildren();
2302                 for (int i = 0; i < notificationChildren.size(); i++) {
2303                     ExpandableNotificationRow child = notificationChildren.get(i);
2304                     child.setDismissUsingRowTranslationX(usingRowTranslationX);
2305                 }
2306             }
2307         }
2308     }
2309 
2310     @Override
2311     public void setTranslation(float translationX) {
2312         invalidate();
2313         if (mDismissUsingRowTranslationX) {
2314             setTranslationX(translationX);
2315         } else if (mTranslateableViews != null) {
2316             // Translate the group of views
2317             for (int i = 0; i < mTranslateableViews.size(); i++) {
2318                 if (mTranslateableViews.get(i) != null) {
2319                     mTranslateableViews.get(i).setTranslationX(translationX);
2320                 }
2321             }
2322             invalidateOutline();
2323 
2324             // In order to keep the shelf in sync with this swiping, we're simply translating
2325             // it's icon by the same amount. The translation is already being used for the normal
2326             // positioning, so we can use the scrollX instead.
2327             getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX);
2328         }
2329 
2330         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
2331             mMenuRow.onParentTranslationUpdate(translationX);
2332         }
2333     }
2334 
2335     @Override
2336     public float getTranslation() {
2337         if (mDismissUsingRowTranslationX) {
2338             return getTranslationX();
2339         }
2340 
2341         if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
2342             // All of the views in the list should have same translation, just use first one.
2343             return mTranslateableViews.get(0).getTranslationX();
2344         }
2345 
2346         return 0;
2347     }
2348 
2349     public Animator getTranslateViewAnimator(final float leftTarget,
2350             AnimatorUpdateListener listener) {
2351         if (mTranslateAnim != null) {
2352             mTranslateAnim.cancel();
2353         }
2354 
2355         final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT,
2356                 leftTarget);
2357         if (listener != null) {
2358             translateAnim.addUpdateListener(listener);
2359         }
2360         translateAnim.addListener(new AnimatorListenerAdapter() {
2361             boolean cancelled = false;
2362 
2363             @Override
2364             public void onAnimationCancel(Animator anim) {
2365                 cancelled = true;
2366             }
2367 
2368             @Override
2369             public void onAnimationEnd(Animator anim) {
2370                 if (!cancelled && leftTarget == 0) {
2371                     if (mMenuRow != null) {
2372                         mMenuRow.resetMenu();
2373                     }
2374                 }
2375                 mTranslateAnim = null;
2376             }
2377         });
2378         mTranslateAnim = translateAnim;
2379         return translateAnim;
2380     }
2381 
2382     /** Cancels the ongoing translate animation if there is any. */
2383     public void cancelTranslateAnimation() {
2384         if (mTranslateAnim != null) {
2385             mTranslateAnim.cancel();
2386         }
2387     }
2388 
2389     void ensureGutsInflated() {
2390         if (mGuts == null) {
2391             mGutsStub.inflate();
2392         }
2393     }
2394 
2395     private void updateChildrenVisibility() {
2396         boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null
2397                 && mGuts.isExposed();
2398         mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren
2399                 && !hideContentWhileLaunching ? VISIBLE : INVISIBLE);
2400         if (mChildrenContainer != null) {
2401             mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren
2402                     && !hideContentWhileLaunching ? VISIBLE
2403                     : INVISIBLE);
2404         }
2405         // The limits might have changed if the view suddenly became a group or vice versa
2406         updateLimits();
2407     }
2408 
2409     @Override
2410     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
2411         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
2412             // Add a record for the entire layout since its content is somehow small.
2413             // The event comes from a leaf view that is interacted with.
2414             AccessibilityEvent record = AccessibilityEvent.obtain();
2415             onInitializeAccessibilityEvent(record);
2416             dispatchPopulateAccessibilityEvent(record);
2417             event.appendRecord(record);
2418             return true;
2419         }
2420         return false;
2421     }
2422 
2423 
2424     public void applyLaunchAnimationParams(LaunchAnimationParameters params) {
2425         if (params == null) {
2426             // `null` params indicates the animation is over, which means we can't access
2427             // params.getParentStartClipTopAmount() which has the value we want to restore.
2428             // Fortunately, only NotificationShelf actually uses these values for anything other
2429             // than this launch animation, so we can restore the value to 0 and it's right for now.
2430             if (mNotificationParent != null) {
2431                 mNotificationParent.setClipTopAmount(0);
2432             }
2433             setTranslationX(0);
2434             return;
2435         }
2436 
2437         if (!params.getVisible()) {
2438             if (getVisibility() == View.VISIBLE) {
2439                 setVisibility(View.INVISIBLE);
2440             }
2441             return;
2442         }
2443 
2444         float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
2445                 params.getProgress(0, 50));
2446         float translationZ = MathUtils.lerp(params.getStartTranslationZ(),
2447                 mNotificationLaunchHeight,
2448                 zProgress);
2449         setTranslationZ(translationZ);
2450         float extraWidthForClipping = params.getWidth() - getWidth();
2451         setExtraWidthForClipping(extraWidthForClipping);
2452 
2453         int top;
2454         if (params.getStartRoundedTopClipping() > 0) {
2455             // If we were clipping initially, let's interpolate from the start position to the
2456             // top. Otherwise, we just take the top directly.
2457             float expandProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
2458                     params.getProgress(0,
2459                             NotificationTransitionAnimatorController
2460                                     .ANIMATION_DURATION_TOP_ROUNDING));
2461             int startTop = params.getStartNotificationTop();
2462             top = (int) Math.min(MathUtils.lerp(startTop, params.getTop(), expandProgress),
2463                     startTop);
2464         } else {
2465             top = params.getTop();
2466         }
2467         int actualHeight = params.getBottom() - top;
2468         setActualHeight(actualHeight);
2469 
2470         int notificationStackTop = params.getNotificationParentTop();
2471         top -= notificationStackTop;
2472         int startClipTopAmount = params.getStartClipTopAmount();
2473         int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, params.getProgress());
2474         if (mNotificationParent != null) {
2475             float parentTranslationY = mNotificationParent.getTranslationY();
2476             top -= parentTranslationY;
2477             mNotificationParent.setTranslationZ(translationZ);
2478 
2479             // When the expanding notification is below its parent, the parent must be clipped
2480             // exactly how it was clipped before the animation. When the expanding notification is
2481             // on or above its parent (top <= 0), then the parent must be clipped exactly 'top'
2482             // pixels to show the expanding notification, while still taking the decreasing
2483             // notification clipTopAmount into consideration, so 'top + clipTopAmount'.
2484             int parentStartClipTopAmount = params.getParentStartClipTopAmount();
2485             int parentClipTopAmount = Math.min(parentStartClipTopAmount, top + clipTopAmount);
2486             mNotificationParent.setClipTopAmount(parentClipTopAmount);
2487 
2488             mNotificationParent.setExtraWidthForClipping(extraWidthForClipping);
2489             float clipBottom = Math.max(params.getBottom() - notificationStackTop,
2490                     parentTranslationY + mNotificationParent.getActualHeight()
2491                             - mNotificationParent.getClipBottomAmount());
2492             float clipTop = Math.min(params.getTop() - notificationStackTop, parentTranslationY);
2493             int minimumHeightForClipping = (int) (clipBottom - clipTop);
2494             mNotificationParent.setMinimumHeightForClipping(minimumHeightForClipping);
2495         } else if (startClipTopAmount != 0) {
2496             setClipTopAmount(clipTopAmount);
2497         }
2498         setTranslationY(top);
2499 
2500         float absoluteCenterX = getLocationOnScreen()[0] + getWidth() / 2f - getTranslationX();
2501         setTranslationX(params.getCenterX() - absoluteCenterX);
2502 
2503         final float maxRadius = getMaxRadius();
2504         mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / maxRadius;
2505         mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / maxRadius;
2506         invalidateOutline();
2507 
2508         mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight);
2509     }
2510 
2511     public void setExpandAnimationRunning(boolean expandAnimationRunning) {
2512         if (expandAnimationRunning) {
2513             setAboveShelf(true);
2514             mExpandAnimationRunning = true;
2515             getViewState().cancelAnimations(this);
2516             mNotificationLaunchHeight = AmbientState.getNotificationLaunchHeight(getContext());
2517         } else {
2518             mExpandAnimationRunning = false;
2519             setAboveShelf(isAboveShelf());
2520             setVisibility(View.VISIBLE);
2521             if (mGuts != null) {
2522                 mGuts.setAlpha(1.0f);
2523             }
2524             resetAllContentAlphas();
2525             setExtraWidthForClipping(0.0f);
2526             if (mNotificationParent != null) {
2527                 mNotificationParent.setExtraWidthForClipping(0.0f);
2528                 mNotificationParent.setMinimumHeightForClipping(0);
2529             }
2530         }
2531         if (mNotificationParent != null) {
2532             mNotificationParent.setChildIsExpanding(mExpandAnimationRunning);
2533         }
2534         updateChildrenVisibility();
2535         updateClipping();
2536         mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning);
2537     }
2538 
2539     private void setChildIsExpanding(boolean isExpanding) {
2540         mChildIsExpanding = isExpanding;
2541         updateClipping();
2542         invalidate();
2543     }
2544 
2545     @Override
2546     public boolean hasExpandingChild() {
2547         return mChildIsExpanding;
2548     }
2549 
2550     @Override
2551     public @NonNull StatusBarIconView getShelfIcon() {
2552         return getEntry().getIcons().getShelfIcon();
2553     }
2554 
2555     @Override
2556     protected boolean shouldClipToActualHeight() {
2557         return super.shouldClipToActualHeight() && !mExpandAnimationRunning;
2558     }
2559 
2560     @Override
2561     public boolean isExpandAnimationRunning() {
2562         return mExpandAnimationRunning;
2563     }
2564 
2565     /**
2566      * Tap sounds should not be played when we're unlocking.
2567      * Doing so would cause audio collision and the system would feel unpolished.
2568      */
2569     @Override
2570     public boolean isSoundEffectsEnabled() {
2571         final boolean mute = mStatusBarStateController != null
2572                 && mStatusBarStateController.isDozing()
2573                 && mSecureStateProvider != null &&
2574                 !mSecureStateProvider.getAsBoolean();
2575         return !mute && super.isSoundEffectsEnabled();
2576     }
2577 
2578     public boolean isExpandable() {
2579         if (mIsSummaryWithChildren && !shouldShowPublic()) {
2580             return !mChildrenExpanded;
2581         }
2582         return mEnableNonGroupedNotificationExpand && mExpandable;
2583     }
2584 
2585     public void setExpandable(boolean expandable) {
2586         mExpandable = expandable;
2587         mPrivateLayout.updateExpandButtons(isExpandable());
2588     }
2589 
2590     @Override
2591     public void setClipToActualHeight(boolean clipToActualHeight) {
2592         super.setClipToActualHeight(clipToActualHeight || isUserLocked());
2593         getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked());
2594     }
2595 
2596     /**
2597      * @return whether the user has changed the expansion state
2598      */
2599     public boolean hasUserChangedExpansion() {
2600         return mHasUserChangedExpansion;
2601     }
2602 
2603     public boolean isUserExpanded() {
2604         return mUserExpanded;
2605     }
2606 
2607     /**
2608      * Set this notification to be expanded by the user
2609      *
2610      * @param userExpanded whether the user wants this notification to be expanded
2611      */
2612     public void setUserExpanded(boolean userExpanded) {
2613         setUserExpanded(userExpanded, false /* allowChildExpansion */);
2614     }
2615 
2616     /**
2617      * Set this notification to be expanded by the user
2618      *
2619      * @param userExpanded        whether the user wants this notification to be expanded
2620      * @param allowChildExpansion whether a call to this method allows expanding children
2621      */
2622     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
2623         if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
2624                 && !mChildrenContainer.showingAsLowPriority()) {
2625             final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
2626             mGroupExpansionManager.setGroupExpanded(mEntry, userExpanded);
2627             onExpansionChanged(true /* userAction */, wasExpanded);
2628             return;
2629         }
2630         if (userExpanded && !mExpandable) return;
2631         final boolean wasExpanded = isExpanded();
2632         mHasUserChangedExpansion = true;
2633         mUserExpanded = userExpanded;
2634         onExpansionChanged(true /* userAction */, wasExpanded);
2635         if (!wasExpanded && isExpanded()
2636                 && getActualHeight() != getIntrinsicHeight()) {
2637             notifyHeightChanged(/* needsAnimation= */ true);
2638         }
2639     }
2640 
2641     public void resetUserExpansion() {
2642         boolean wasExpanded = isExpanded();
2643         mHasUserChangedExpansion = false;
2644         mUserExpanded = false;
2645         if (wasExpanded != isExpanded()) {
2646             if (mIsSummaryWithChildren) {
2647                 mChildrenContainer.onExpansionChanged();
2648             }
2649             notifyHeightChanged(/* needsAnimation= */ false);
2650         }
2651         updateShelfIconColor();
2652     }
2653 
2654     public boolean isUserLocked() {
2655         return mUserLocked;
2656     }
2657 
2658     public void setUserLocked(boolean userLocked) {
2659         mUserLocked = userLocked;
2660         mPrivateLayout.setUserExpanding(userLocked);
2661         // This is intentionally not guarded with mIsSummaryWithChildren since we might have had
2662         // children but not anymore.
2663         if (mChildrenContainer != null) {
2664             mChildrenContainer.setUserLocked(userLocked);
2665             if (mIsSummaryWithChildren && (userLocked || !isGroupExpanded())) {
2666                 updateBackgroundForGroupState();
2667             }
2668         }
2669     }
2670 
2671     /**
2672      * @return has the system set this notification to be expanded
2673      */
2674     public boolean isSystemExpanded() {
2675         return mIsSystemExpanded;
2676     }
2677 
2678     /**
2679      * Set this notification to be expanded by the system.
2680      *
2681      * @param expand whether the system wants this notification to be expanded.
2682      */
2683     public void setSystemExpanded(boolean expand) {
2684         if (expand != mIsSystemExpanded) {
2685             final boolean wasExpanded = isExpanded();
2686             mIsSystemExpanded = expand;
2687             notifyHeightChanged(/* needsAnimation= */ false);
2688             onExpansionChanged(false /* userAction */, wasExpanded);
2689             if (mIsSummaryWithChildren) {
2690                 mChildrenContainer.updateGroupOverflow();
2691                 resetChildSystemExpandedStates();
2692             }
2693         }
2694     }
2695 
2696     void setOnKeyguard(boolean onKeyguard) {
2697         if (onKeyguard != mOnKeyguard) {
2698             boolean wasAboveShelf = isAboveShelf();
2699             final boolean wasExpanded = isExpanded();
2700             mOnKeyguard = onKeyguard;
2701             onExpansionChanged(false /* userAction */, wasExpanded);
2702             if (wasExpanded != isExpanded()) {
2703                 if (mIsSummaryWithChildren) {
2704                     mChildrenContainer.updateGroupOverflow();
2705                 }
2706                 notifyHeightChanged(/* needsAnimation= */ false);
2707             }
2708             if (isAboveShelf() != wasAboveShelf) {
2709                 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
2710             }
2711         }
2712     }
2713 
2714 
2715     @Override
2716     public int getHeightWithoutLockscreenConstraints() {
2717         mIgnoreLockscreenConstraints = true;
2718         final int height = getIntrinsicHeight();
2719         mIgnoreLockscreenConstraints = false;
2720         return height;
2721     }
2722 
2723     @Override
2724     public int getIntrinsicHeight() {
2725         if (isUserLocked()) {
2726             return getActualHeight();
2727         } else if (mGuts != null && mGuts.isExposed()) {
2728             return mGuts.getIntrinsicHeight();
2729         } else if ((isChildInGroup() && !isGroupExpanded())) {
2730             return mPrivateLayout.getMinHeight();
2731         } else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
2732             return getMinHeight();
2733         } else if (mIsSummaryWithChildren) {
2734             return mChildrenContainer.getIntrinsicHeight();
2735         } else if (canShowHeadsUp() && isHeadsUpState()) {
2736             if (isPinned() || mHeadsupDisappearRunning) {
2737                 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
2738             } else if (isExpanded()) {
2739                 return Math.max(getMaxExpandHeight(), getHeadsUpHeight());
2740             } else {
2741                 return Math.max(getCollapsedHeight(), getHeadsUpHeight());
2742             }
2743         } else if (isExpanded()) {
2744             return getMaxExpandHeight();
2745         } else {
2746             return getCollapsedHeight();
2747         }
2748     }
2749 
2750     /**
2751      * @return {@code true} if the notification can show it's heads up layout. This is mostly true
2752      * except for legacy use cases.
2753      */
2754     public boolean canShowHeadsUp() {
2755         if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
2756                 (!mEntry.isStickyAndNotDemoted()
2757                         || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
2758             return false;
2759         }
2760         return true;
2761     }
2762 
2763     private boolean isBypassEnabled() {
2764         return mBypassController == null || mBypassController.getBypassEnabled();
2765     }
2766 
2767     private boolean isDozing() {
2768         return mStatusBarStateController != null && mStatusBarStateController.isDozing();
2769     }
2770 
2771     @Override
2772     public boolean isGroupExpanded() {
2773         return mGroupExpansionManager.isGroupExpanded(mEntry);
2774     }
2775 
2776     private void onAttachedChildrenCountChanged() {
2777         final boolean wasSummary = mIsSummaryWithChildren;
2778         mIsSummaryWithChildren = mChildrenContainer != null
2779                 && mChildrenContainer.getNotificationChildCount() > 0;
2780         if (mIsSummaryWithChildren) {
2781             Trace.beginSection("ExpNotRow#onChildCountChanged (summary)");
2782             if (!AsyncGroupHeaderViewInflation.isEnabled()) {
2783                 NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper();
2784                 if (wrapper == null || wrapper.getNotificationHeader() == null) {
2785                     mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
2786                             isConversation());
2787                 }
2788             }
2789         }
2790         if (!mIsSummaryWithChildren && wasSummary) {
2791             // Reset the 'when' once the row stops being a summary
2792             mPublicLayout.setNotificationWhen(mEntry.getSbn().getNotification().getWhen());
2793         }
2794         getShowingLayout().updateBackgroundColor(false /* animate */);
2795         mPrivateLayout.updateExpandButtons(isExpandable());
2796         updateChildrenAppearance();
2797         updateChildrenVisibility();
2798         applyChildrenRoundness();
2799         if (mIsSummaryWithChildren) {
2800             Trace.endSection();
2801         }
2802     }
2803 
2804     /**
2805      * Triggers expand click listener to expand the notification.
2806      */
2807     public void expandNotification() {
2808         mExpandClickListener.onClick(this);
2809     }
2810 
2811     /**
2812      * If this is a group, update the appearance of the children.
2813      */
2814     public void updateChildrenAppearance() {
2815         if (mIsSummaryWithChildren) {
2816             mChildrenContainer.updateChildrenAppearance();
2817         }
2818     }
2819 
2820     /**
2821      * Check whether the view state is currently expanded. This is given by the system in {@link
2822      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
2823      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
2824      * view can differ from this state, if layout params are modified from outside.
2825      *
2826      * @return whether the view state is currently expanded.
2827      */
2828     public boolean isExpanded() {
2829         return isExpanded(false /* allowOnKeyguard */);
2830     }
2831 
2832     public boolean isExpanded(boolean allowOnKeyguard) {
2833         return (!shouldShowPublic()) && (!mOnKeyguard || allowOnKeyguard)
2834                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
2835                 || isUserExpanded());
2836     }
2837 
2838     private boolean isSystemChildExpanded() {
2839         return mIsSystemChildExpanded;
2840     }
2841 
2842     public void setSystemChildExpanded(boolean expanded) {
2843         mIsSystemChildExpanded = expanded;
2844     }
2845 
2846     public void setLayoutListener(@Nullable LayoutListener listener) {
2847         mLayoutListener = listener;
2848     }
2849 
2850     @Override
2851     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
2852         Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout"));
2853         int intrinsicBefore = getIntrinsicHeight();
2854         super.onLayout(changed, left, top, right, bottom);
2855         if (intrinsicBefore != getIntrinsicHeight()
2856                 && (intrinsicBefore != 0 || getActualHeight() > 0)) {
2857             notifyHeightChanged(/* needsAnimation= */ true);
2858         }
2859         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
2860             mMenuRow.onParentHeightUpdate();
2861         }
2862         updateContentShiftHeight();
2863         if (mLayoutListener != null) {
2864             mLayoutListener.onLayout();
2865         }
2866         Trace.endSection();
2867     }
2868 
2869     /**
2870      * Updates the content shift height such that the header is completely hidden when coming from
2871      * the top.
2872      */
2873     private void updateContentShiftHeight() {
2874         NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper();
2875         CachingIconView icon = wrapper == null ? null : wrapper.getIcon();
2876         if (icon != null) {
2877             mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight();
2878         } else {
2879             mIconTransformContentShift = mContentShift;
2880         }
2881     }
2882 
2883     @Override
2884     protected float getContentTransformationShift() {
2885         return mIconTransformContentShift;
2886     }
2887 
2888     @Override
2889     public void notifyHeightChanged(boolean needsAnimation) {
2890         super.notifyHeightChanged(needsAnimation);
2891         getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
2892     }
2893 
2894     public void setSensitive(boolean sensitive, boolean hideSensitive) {
2895         int intrinsicBefore = getIntrinsicHeight();
2896         mSensitive = sensitive;
2897         mSensitiveHiddenInGeneral = hideSensitive;
2898         int intrinsicAfter = getIntrinsicHeight();
2899         if (intrinsicBefore != intrinsicAfter) {
2900             notifyHeightChanged(/* needsAnimation= */ true);
2901         }
2902     }
2903 
2904     /** Sets whether this notification row should show the notification expander or not */
2905     public void setPublicExpanderVisible(boolean showPublicExpander) {
2906         if (mShowPublicExpander != showPublicExpander) {
2907             mShowPublicExpander = showPublicExpander;
2908             mPublicLayout.updateExpandButtons(mShowPublicExpander);
2909         }
2910     }
2911 
2912     @Override
2913     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
2914         mHideSensitiveForIntrinsicHeight = hideSensitive;
2915         if (mIsSummaryWithChildren) {
2916             List<ExpandableNotificationRow> notificationChildren =
2917                     mChildrenContainer.getAttachedChildren();
2918             for (int i = 0; i < notificationChildren.size(); i++) {
2919                 ExpandableNotificationRow child = notificationChildren.get(i);
2920                 child.setHideSensitiveForIntrinsicHeight(hideSensitive);
2921             }
2922         }
2923     }
2924 
2925     @Override
2926     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
2927             long duration) {
2928         if (getVisibility() == GONE) {
2929             // If we are GONE, the hideSensitive parameter will not be calculated and always be
2930             // false, which is incorrect, let's wait until a real call comes in later.
2931             return;
2932         }
2933         boolean oldShowingPublic = mShowingPublic;
2934         mShowingPublic = mSensitive && hideSensitive;
2935         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
2936             return;
2937         }
2938         float oldAlpha = getContentView().getAlpha();
2939 
2940         if (!animated) {
2941             mPublicLayout.animate().cancel();
2942             mPrivateLayout.animate().cancel();
2943             if (mChildrenContainer != null) {
2944                 mChildrenContainer.animate().cancel();
2945             }
2946             resetAllContentAlphas();
2947             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
2948             updateChildrenVisibility();
2949             if (NotificationContentAlphaOptimization.isEnabled()) {
2950                 // We want to set the old alpha to the now-showing layout to avoid breaking an
2951                 // on-going animation
2952                 if (oldAlpha != 1f) {
2953                     setAlphaAndLayerType(mShowingPublic ? mPublicLayout : mPrivateLayout, oldAlpha);
2954                 }
2955             }
2956         } else {
2957             animateShowingPublic(delay, duration, mShowingPublic);
2958         }
2959         NotificationContentView showingLayout = getShowingLayout();
2960         showingLayout.updateBackgroundColor(animated);
2961         mPrivateLayout.updateExpandButtons(isExpandable());
2962         updateShelfIconColor();
2963         mShowingPublicInitialized = true;
2964     }
2965 
2966     private void animateShowingPublic(long delay, long duration, boolean showingPublic) {
2967         View[] privateViews = mIsSummaryWithChildren
2968                 ? new View[]{mChildrenContainer}
2969                 : new View[]{mPrivateLayout};
2970         View[] publicViews = new View[]{mPublicLayout};
2971         View[] hiddenChildren = showingPublic ? privateViews : publicViews;
2972         View[] shownChildren = showingPublic ? publicViews : privateViews;
2973         // disappear/appear overlap: 10 percent of duration
2974         long overlap = duration / 10;
2975         // disappear duration: 1/3 of duration + half of overlap
2976         long disappearDuration = duration / 3 + overlap / 2;
2977         // appear duration: 2/3 of duration + half of overlap
2978         long appearDuration = (duration - disappearDuration) + overlap / 2;
2979         for (final View hiddenView : hiddenChildren) {
2980             hiddenView.setVisibility(View.VISIBLE);
2981             hiddenView.animate().cancel();
2982             hiddenView.animate()
2983                     .alpha(0f)
2984                     .setStartDelay(delay)
2985                     .setDuration(disappearDuration)
2986                     .withEndAction(() -> {
2987                         hiddenView.setVisibility(View.INVISIBLE);
2988                         resetAllContentAlphas();
2989                     });
2990         }
2991         for (View showView : shownChildren) {
2992             showView.setVisibility(View.VISIBLE);
2993             showView.setAlpha(0f);
2994             showView.animate().cancel();
2995             showView.animate()
2996                     .alpha(1f)
2997                     .setStartDelay(delay + duration - appearDuration)
2998                     .setDuration(appearDuration);
2999         }
3000     }
3001 
3002     @Override
3003     public boolean mustStayOnScreen() {
3004         return mIsHeadsUp && mMustStayOnScreen;
3005     }
3006 
3007     /**
3008      * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
3009      * otherwise some state might not be updated.
3010      */
3011     public boolean canViewBeDismissed() {
3012         return canEntryBeDismissed() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
3013     }
3014 
3015     private boolean canEntryBeDismissed() {
3016         return mDismissibilityProvider.isDismissable(mEntry);
3017     }
3018 
3019     /**
3020      * @return Whether this view is allowed to be cleared with clear all. Only valid for visible
3021      * notifications as otherwise some state might not be updated. To request about the general
3022      * clearability see {@link NotificationEntry#isClearable()}.
3023      */
3024     public boolean canViewBeCleared() {
3025         return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
3026     }
3027 
3028     private boolean shouldShowPublic() {
3029         return mSensitive && mHideSensitiveForIntrinsicHeight;
3030     }
3031 
3032     public void makeActionsVisibile() {
3033         setUserExpanded(true, true);
3034         if (isChildInGroup()) {
3035             mGroupExpansionManager.setGroupExpanded(mEntry, true);
3036         }
3037         notifyHeightChanged(/* needsAnimation= */ false);
3038     }
3039 
3040     public void setChildrenExpanded(boolean expanded, boolean animate) {
3041         mChildrenExpanded = expanded;
3042         if (mChildrenContainer != null) {
3043             mChildrenContainer.setChildrenExpanded(expanded);
3044         }
3045         updateBackgroundForGroupState();
3046         updateClickAndFocus();
3047     }
3048 
3049     public int getMaxExpandHeight() {
3050         return mPrivateLayout.getExpandHeight();
3051     }
3052 
3053 
3054     private int getHeadsUpHeight() {
3055         return getShowingLayout().getHeadsUpHeight(false /* forceNoHeader */);
3056     }
3057 
3058     public boolean areGutsExposed() {
3059         return (mGuts != null && mGuts.isExposed());
3060     }
3061 
3062     private boolean isGutsLeaveBehind() {
3063         return (mGuts != null && mGuts.isLeavebehind());
3064     }
3065 
3066     @Override
3067     public boolean isContentExpandable() {
3068         if (mIsSummaryWithChildren && !shouldShowPublic()) {
3069             return true;
3070         }
3071         NotificationContentView showingLayout = getShowingLayout();
3072         return showingLayout.isContentExpandable();
3073     }
3074 
3075     @Override
3076     protected View getContentView() {
3077         if (mIsSummaryWithChildren && !shouldShowPublic()) {
3078             return mChildrenContainer;
3079         }
3080         return getShowingLayout();
3081     }
3082 
3083     @Override
3084     public long performRemoveAnimation(
3085             long duration,
3086             long delay,
3087             float translationDirection,
3088             boolean isHeadsUpAnimation,
3089             Runnable onStartedRunnable,
3090             Runnable onFinishedRunnable,
3091             AnimatorListenerAdapter animationListener, ClipSide clipSide) {
3092         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
3093             Animator anim = getTranslateViewAnimator(0f, null /* listener */);
3094             if (anim != null) {
3095                 anim.addListener(new AnimatorListenerAdapter() {
3096                     @Override
3097                     public void onAnimationStart(Animator animation) {
3098                         if (onStartedRunnable != null) {
3099                             onStartedRunnable.run();
3100                         }
3101                     }
3102 
3103                     @Override
3104                     public void onAnimationEnd(Animator animation) {
3105                         ExpandableNotificationRow.super.performRemoveAnimation(
3106                                 duration, delay, translationDirection, isHeadsUpAnimation,
3107                                 null, onFinishedRunnable, animationListener, ClipSide.BOTTOM);
3108                     }
3109                 });
3110                 anim.start();
3111                 return anim.getDuration();
3112             }
3113         }
3114         return super.performRemoveAnimation(duration, delay, translationDirection,
3115                 isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener,
3116                 clipSide);
3117     }
3118 
3119     @Override
3120     protected void onAppearAnimationFinished(boolean wasAppearing) {
3121         super.onAppearAnimationFinished(wasAppearing);
3122         if (wasAppearing) {
3123             // During the animation the visible view might have changed, so let's make sure all
3124             // alphas are reset
3125             resetAllContentAlphas();
3126             if (FADE_LAYER_OPTIMIZATION_ENABLED) {
3127                 setNotificationFaded(false);
3128             } else {
3129                 setNotificationFadedOnChildren(false);
3130             }
3131         } else {
3132             setHeadsUpAnimatingAway(false);
3133         }
3134     }
3135 
3136     @Override
3137     protected void resetAllContentAlphas() {
3138         mPrivateLayout.setAlpha(1f);
3139         mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null);
3140         mPublicLayout.setAlpha(1f);
3141         mPublicLayout.setLayerType(LAYER_TYPE_NONE, null);
3142         if (mChildrenContainer != null) {
3143             mChildrenContainer.setAlpha(1f);
3144             mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
3145         }
3146     }
3147 
3148     /**
3149      * Gets the last value set with {@link #setNotificationFaded(boolean)}
3150      */
3151     @Override
3152     public boolean isNotificationFaded() {
3153         return mIsFaded;
3154     }
3155 
3156     /**
3157      * This class needs to delegate the faded state set on it by
3158      * {@link com.android.systemui.statusbar.notification.stack.ViewState} to its children.
3159      * Having each notification use layerType of HARDWARE anytime it fades in/out can result in
3160      * extremely large layers (in the case of groups, it can even exceed the device height).
3161      * Because these large renders can cause serious jank when rendering, we instead have
3162      * notifications return false from {@link #hasOverlappingRendering()} and delegate the
3163      * layerType to child views which really need it in order to render correctly, such as icon
3164      * views or the conversation face pile.
3165      * <p>
3166      * Another compounding factor for notifications is that we change clipping on each frame of the
3167      * animation, so the hardware layer isn't able to do any caching at the top level, but the
3168      * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we
3169      * never invalidate them.
3170      */
3171     @Override
3172     public void setNotificationFaded(boolean faded) {
3173         mIsFaded = faded;
3174         if (childrenRequireOverlappingRendering()) {
3175             // == Simple Scenario ==
3176             // If a child (like remote input) needs this to have overlapping rendering, then set
3177             // the layerType of this view and reset the children to render as if the notification is
3178             // not fading.
3179             NotificationFadeAware.setLayerTypeForFaded(this, faded);
3180             setNotificationFadedOnChildren(false);
3181         } else {
3182             // == Delegating Scenario ==
3183             // This is the new normal for alpha: Explicitly reset this view's layer type to NONE,
3184             // and require that all children use their own hardware layer if they have bad
3185             // overlapping rendering.
3186             NotificationFadeAware.setLayerTypeForFaded(this, false);
3187             setNotificationFadedOnChildren(faded);
3188         }
3189     }
3190 
3191     /**
3192      * Private helper for iterating over the layouts and children containers to set faded state
3193      */
3194     private void setNotificationFadedOnChildren(boolean faded) {
3195         delegateNotificationFaded(mChildrenContainer, faded);
3196         for (NotificationContentView layout : mLayouts) {
3197             delegateNotificationFaded(layout, faded);
3198         }
3199     }
3200 
3201     private static void delegateNotificationFaded(@Nullable View view, boolean faded) {
3202         if (FADE_LAYER_OPTIMIZATION_ENABLED && view instanceof NotificationFadeAware) {
3203             ((NotificationFadeAware) view).setNotificationFaded(faded);
3204         } else {
3205             NotificationFadeAware.setLayerTypeForFaded(view, faded);
3206         }
3207     }
3208 
3209     /**
3210      * Only declare overlapping rendering if independent children of the view require it.
3211      */
3212     @Override
3213     public boolean hasOverlappingRendering() {
3214         return super.hasOverlappingRendering() && childrenRequireOverlappingRendering();
3215     }
3216 
3217     /**
3218      * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the
3219      * row should require overlapping rendering to ensure that the overlapped view doesn't bleed
3220      * through when alpha fading.
3221      * <p>
3222      * Note that this currently works for top-level notifications which squish their height down
3223      * while collapsing the shade, but does not work for children inside groups, because the
3224      * accordion affect does not apply to those views, so super.hasOverlappingRendering() will
3225      * always return false to avoid the clipping caused when the view's measured height is less than
3226      * the 'actual height'.
3227      */
3228     private boolean childrenRequireOverlappingRendering() {
3229         if (!FADE_LAYER_OPTIMIZATION_ENABLED) {
3230             return true;
3231         }
3232         // The colorized background is another layer with which all other elements overlap
3233         if (getEntry().getSbn().getNotification().isColorized()) {
3234             return true;
3235         }
3236         // Check if the showing layout has a need for overlapping rendering.
3237         // NOTE: We could check both public and private layouts here, but becuause these states
3238         //  don't animate well, there are bigger visual artifacts if we start changing the shown
3239         //  layout during shade expansion.
3240         NotificationContentView showingLayout = getShowingLayout();
3241         return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
3242     }
3243 
3244     @Override
3245     public void setActualHeight(int height, boolean notifyListeners) {
3246         boolean changed = height != getActualHeight();
3247         super.setActualHeight(height, notifyListeners);
3248         if (changed && isRemoved()) {
3249             // TODO: remove this once we found the gfx bug for this.
3250             // This is a hack since a removed view sometimes would just stay blank. it occured
3251             // when sending yourself a message and then clicking on it.
3252             ViewGroup parent = (ViewGroup) getParent();
3253             if (parent != null) {
3254                 parent.invalidate();
3255             }
3256         }
3257         if (mGuts != null && mGuts.isExposed()) {
3258             mGuts.setActualHeight(height);
3259             return;
3260         }
3261         for (NotificationContentView l : mLayouts) {
3262             l.setContentHeight(height);
3263         }
3264         if (mIsSummaryWithChildren) {
3265             mChildrenContainer.setActualHeight(height);
3266         }
3267         if (mGuts != null) {
3268             mGuts.setActualHeight(height);
3269         }
3270         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
3271             mMenuRow.onParentHeightUpdate();
3272         }
3273         handleIntrinsicHeightReached();
3274     }
3275 
3276     @Override
3277     public int getMaxContentHeight() {
3278         if (mIsSummaryWithChildren && !shouldShowPublic()) {
3279             return mChildrenContainer.getMaxContentHeight();
3280         }
3281         NotificationContentView showingLayout = getShowingLayout();
3282         return showingLayout.getMaxHeight();
3283     }
3284 
3285     @Override
3286     public int getMinHeight(boolean ignoreTemporaryStates) {
3287         if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) {
3288             return mGuts.getIntrinsicHeight();
3289         } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp
3290                 && mHeadsUpManager.isTrackingHeadsUp()) {
3291             return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
3292         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) {
3293             return mChildrenContainer.getMinHeight();
3294         } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) {
3295             return getHeadsUpHeight();
3296         }
3297         NotificationContentView showingLayout = getShowingLayout();
3298         return showingLayout.getMinHeight();
3299     }
3300 
3301     @Override
3302     public int getCollapsedHeight() {
3303         if (mIsSummaryWithChildren && !shouldShowPublic()) {
3304             return mChildrenContainer.getCollapsedHeight();
3305         }
3306         return getMinHeight();
3307     }
3308 
3309     @Override
3310     public int getHeadsUpHeightWithoutHeader() {
3311         if (!canShowHeadsUp() || !mIsHeadsUp) {
3312             return getCollapsedHeight();
3313         }
3314         if (mIsSummaryWithChildren && !shouldShowPublic()) {
3315             return mChildrenContainer.getCollapsedHeightWithoutHeader();
3316         }
3317         return getShowingLayout().getHeadsUpHeight(true /* forceNoHeader */);
3318     }
3319 
3320     @Override
3321     public void setClipTopAmount(int clipTopAmount) {
3322         super.setClipTopAmount(clipTopAmount);
3323         for (NotificationContentView l : mLayouts) {
3324             l.setClipTopAmount(clipTopAmount);
3325         }
3326         if (mGuts != null) {
3327             mGuts.setClipTopAmount(clipTopAmount);
3328         }
3329     }
3330 
3331     @Override
3332     public void setClipBottomAmount(int clipBottomAmount) {
3333         if (mExpandAnimationRunning) {
3334             return;
3335         }
3336         if (clipBottomAmount != mClipBottomAmount) {
3337             super.setClipBottomAmount(clipBottomAmount);
3338             for (NotificationContentView l : mLayouts) {
3339                 l.setClipBottomAmount(clipBottomAmount);
3340             }
3341             if (mGuts != null) {
3342                 mGuts.setClipBottomAmount(clipBottomAmount);
3343             }
3344         }
3345         if (mChildrenContainer != null && !mChildIsExpanding) {
3346             // We have to update this even if it hasn't changed, since the children locations can
3347             // have changed
3348             mChildrenContainer.setClipBottomAmount(clipBottomAmount);
3349         }
3350     }
3351 
3352     public NotificationContentView getShowingLayout() {
3353         return shouldShowPublic() ? mPublicLayout : mPrivateLayout;
3354     }
3355 
3356     public void setLegacy(boolean legacy) {
3357         for (NotificationContentView l : mLayouts) {
3358             l.setLegacy(legacy);
3359         }
3360     }
3361 
3362     @Override
3363     protected void updateBackgroundTint() {
3364         super.updateBackgroundTint();
3365         updateBackgroundForGroupState();
3366         if (mIsSummaryWithChildren) {
3367             List<ExpandableNotificationRow> notificationChildren =
3368                     mChildrenContainer.getAttachedChildren();
3369             for (int i = 0; i < notificationChildren.size(); i++) {
3370                 ExpandableNotificationRow child = notificationChildren.get(i);
3371                 child.updateBackgroundForGroupState();
3372             }
3373         }
3374     }
3375 
3376     /**
3377      * Called when a group has finished animating from collapsed or expanded state.
3378      */
3379     public void onFinishedExpansionChange() {
3380         mGroupExpansionChanging = false;
3381         updateBackgroundForGroupState();
3382     }
3383 
3384     /**
3385      * Updates the parent and children backgrounds in a group based on the expansion state.
3386      */
3387     public void updateBackgroundForGroupState() {
3388         if (mIsSummaryWithChildren) {
3389             // Only when the group has finished expanding do we hide its background.
3390             mShowNoBackground = !mShowGroupBackgroundWhenExpanded && isGroupExpanded()
3391                     && !isGroupExpansionChanging() && !isUserLocked();
3392             mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);
3393             List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren();
3394             for (int i = 0; i < children.size(); i++) {
3395                 children.get(i).updateBackgroundForGroupState();
3396             }
3397         } else if (isChildInGroup()) {
3398             final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
3399             // Only show a background if the group is expanded OR if it is expanding / collapsing
3400             // and has a custom background color.
3401             final boolean showBackground = isGroupExpanded()
3402                     || ((mNotificationParent.isGroupExpansionChanging()
3403                     || mNotificationParent.isUserLocked()) && childColor != 0);
3404             mShowNoBackground = !showBackground;
3405         } else {
3406             // Only children or parents ever need no background.
3407             mShowNoBackground = false;
3408         }
3409         updateOutline();
3410         updateBackground();
3411     }
3412 
3413     @Override
3414     protected boolean hideBackground() {
3415         return mShowNoBackground || super.hideBackground();
3416     }
3417 
3418     public int getPositionOfChild(ExpandableNotificationRow childRow) {
3419         if (mIsSummaryWithChildren) {
3420             return mChildrenContainer.getPositionInLinearLayout(childRow);
3421         }
3422         return 0;
3423     }
3424 
3425     public void onExpandedByGesture(boolean userExpanded) {
3426         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
3427         if (mGroupMembershipManager.isGroupSummary(mEntry)) {
3428             event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
3429         }
3430         mMetricsLogger.action(event, userExpanded);
3431     }
3432 
3433     @Override
3434     protected boolean disallowSingleClick(MotionEvent event) {
3435         if (areGutsExposed()) {
3436             return false;
3437         }
3438         float x = event.getX();
3439         float y = event.getY();
3440         NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper();
3441         NotificationHeaderView header = wrapper == null ? null : wrapper.getNotificationHeader();
3442         // the extra translation only needs to be added, if we're translating the notification
3443         // contents, otherwise the motionEvent is already at the right place due to the
3444         // touch event system.
3445         float translation = !mDismissUsingRowTranslationX ? getTranslation() : 0;
3446         if (header != null && header.isInTouchRect(x - translation, y)) {
3447             return true;
3448         }
3449         if ((!mIsSummaryWithChildren || shouldShowPublic())
3450                 && getShowingLayout().disallowSingleClick(x, y)) {
3451             return true;
3452         }
3453         return super.disallowSingleClick(event);
3454     }
3455 
3456     private void onExpansionChanged(boolean userAction, boolean wasExpanded) {
3457         boolean nowExpanded = isExpanded();
3458         if (mIsSummaryWithChildren && (!mIsMinimized || wasExpanded)) {
3459             nowExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
3460         }
3461         if (nowExpanded != wasExpanded) {
3462             updateShelfIconColor();
3463             if (mLogger != null) {
3464                 mLogger.logNotificationExpansion(mLoggingKey, getViewState().location, userAction,
3465                         nowExpanded);
3466             }
3467             if (mIsSummaryWithChildren) {
3468                 mChildrenContainer.onExpansionChanged();
3469             }
3470             if (mExpansionChangedListener != null) {
3471                 mExpansionChangedListener.onExpansionChanged(nowExpanded);
3472             }
3473         }
3474     }
3475 
3476     public void setOnExpansionChangedListener(@Nullable OnExpansionChangedListener listener) {
3477         mExpansionChangedListener = listener;
3478     }
3479 
3480     /**
3481      * Perform an action when the notification height has reached its intrinsic height.
3482      *
3483      * @param runnable the runnable to run
3484      */
3485     public void performOnIntrinsicHeightReached(@Nullable Runnable runnable) {
3486         mOnIntrinsicHeightReachedRunnable = runnable;
3487         handleIntrinsicHeightReached();
3488     }
3489 
3490     private void handleIntrinsicHeightReached() {
3491         if (mOnIntrinsicHeightReachedRunnable != null
3492                 && getActualHeight() == getIntrinsicHeight()) {
3493             mOnIntrinsicHeightReachedRunnable.run();
3494             mOnIntrinsicHeightReachedRunnable = null;
3495         }
3496     }
3497 
3498     @Override
3499     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
3500         super.onInitializeAccessibilityNodeInfoInternal(info);
3501         final boolean isLongClickable = isNotificationRowLongClickable();
3502         if (isLongClickable) {
3503             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
3504         }
3505         info.setLongClickable(isLongClickable);
3506 
3507         if (canViewBeDismissed() && !mIsSnoozed) {
3508             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
3509         }
3510         boolean expandable = shouldShowPublic();
3511         boolean isExpanded = false;
3512         if (!expandable) {
3513             if (mIsSummaryWithChildren) {
3514                 expandable = true;
3515                 if (!mIsMinimized || isExpanded()) {
3516                     isExpanded = isGroupExpanded();
3517                 }
3518             } else {
3519                 expandable = mPrivateLayout.isContentExpandable();
3520                 isExpanded = isExpanded();
3521             }
3522         }
3523         if (expandable && !mIsSnoozed) {
3524             if (isExpanded) {
3525                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
3526             } else {
3527                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
3528             }
3529         }
3530         NotificationMenuRowPlugin provider = getProvider();
3531         if (provider != null) {
3532             MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());
3533             if (snoozeMenu != null) {
3534                 AccessibilityAction action = new AccessibilityAction(R.id.action_snooze,
3535                         getContext().getResources()
3536                                 .getString(R.string.notification_menu_snooze_action));
3537                 info.addAction(action);
3538             }
3539         }
3540     }
3541 
3542     @Override
3543     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
3544         if (super.performAccessibilityActionInternal(action, arguments)) {
3545             return true;
3546         }
3547         switch (action) {
3548             case AccessibilityNodeInfo.ACTION_DISMISS:
3549                 performDismiss(true /* fromAccessibility */);
3550                 return true;
3551             case AccessibilityNodeInfo.ACTION_COLLAPSE:
3552             case AccessibilityNodeInfo.ACTION_EXPAND:
3553                 mExpandClickListener.onClick(this);
3554                 return true;
3555             case AccessibilityNodeInfo.ACTION_LONG_CLICK:
3556                 doLongClickCallback();
3557                 return true;
3558             default:
3559                 if (action == R.id.action_snooze) {
3560                     NotificationMenuRowPlugin provider = getProvider();
3561                     if (provider == null) {
3562                         return false;
3563                     }
3564                     MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());
3565                     if (snoozeMenu != null) {
3566                         doLongClickCallback(getWidth() / 2, getHeight() / 2, snoozeMenu);
3567                     }
3568                     return true;
3569                 }
3570         }
3571         return false;
3572     }
3573 
3574     public interface OnExpandClickListener {
3575         void onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded);
3576     }
3577 
3578     @Override
3579     @NonNull
3580     public ExpandableViewState createExpandableViewState() {
3581         return new NotificationViewState();
3582     }
3583 
3584     @Override
3585     public boolean isAboveShelf() {
3586         return (canShowHeadsUp()
3587                 && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
3588                 || mExpandAnimationRunning || mChildIsExpanding));
3589     }
3590 
3591     @Override
3592     protected boolean childNeedsClipping(View child) {
3593         if (child instanceof NotificationContentView contentView) {
3594             if (isClippingNeeded()) {
3595                 return true;
3596             } else if (hasRoundedCorner()
3597                     && contentView.shouldClipToRounding(getTopRoundness() != 0.0f,
3598                     getBottomRoundness() != 0.0f)) {
3599                 return true;
3600             }
3601         } else if (child == mChildrenContainer) {
3602             if (isClippingNeeded() || hasRoundedCorner()) {
3603                 return true;
3604             }
3605         } else if (child instanceof NotificationGuts) {
3606             return hasRoundedCorner();
3607         }
3608         return super.childNeedsClipping(child);
3609     }
3610 
3611     /**
3612      * Set a clip path to be set while expanding the notification. This is needed to nicely
3613      * clip ourselves during the launch if we were clipped rounded in the beginning
3614      */
3615     public void setExpandingClipPath(Path path) {
3616         mExpandingClipPath = path;
3617         invalidate();
3618     }
3619 
3620     @Override
3621     protected void dispatchDraw(Canvas canvas) {
3622         canvas.save();
3623         if (mExpandingClipPath != null && (mExpandAnimationRunning || mChildIsExpanding)) {
3624             // If we're launching a notification, let's clip if a clip rounded to the clipPath
3625             canvas.clipPath(mExpandingClipPath);
3626         }
3627         super.dispatchDraw(canvas);
3628         canvas.restore();
3629     }
3630 
3631     @Override
3632     public void applyRoundnessAndInvalidate() {
3633         applyChildrenRoundness();
3634         super.applyRoundnessAndInvalidate();
3635     }
3636 
3637     private void applyChildrenRoundness() {
3638         if (mIsSummaryWithChildren) {
3639             mChildrenContainer.requestRoundness(
3640                     /* top = */ getTopRoundness(),
3641                     /* bottom = */ getBottomRoundness(),
3642                     /* sourceType = */ FROM_PARENT,
3643                     /* animate = */ false);
3644         }
3645     }
3646 
3647     @Override
3648     public Path getCustomClipPath(View child) {
3649         if (child instanceof NotificationGuts) {
3650             return getClipPath(true /* ignoreTranslation */);
3651         }
3652         return super.getCustomClipPath(child);
3653     }
3654 
3655     public boolean isMediaRow() {
3656         return mEntry.getSbn().getNotification().isMediaNotification();
3657     }
3658 
3659     public boolean isGroupNotFullyVisible() {
3660         return getClipTopAmount() > 0 || getTranslationY() < 0;
3661     }
3662 
3663     public void setAboveShelf(boolean aboveShelf) {
3664         boolean wasAboveShelf = isAboveShelf();
3665         mAboveShelf = aboveShelf;
3666         if (isAboveShelf() != wasAboveShelf) {
3667             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
3668         }
3669     }
3670 
3671     private static class NotificationViewState extends ExpandableViewState {
3672 
3673         @Override
3674         public void applyToView(View view) {
3675             if (view instanceof ExpandableNotificationRow row) {
3676                 if (row.isExpandAnimationRunning()) {
3677                     return;
3678                 }
3679                 handleFixedTranslationZ(row);
3680                 super.applyToView(view);
3681                 row.applyChildrenState();
3682             }
3683         }
3684 
3685         private void handleFixedTranslationZ(ExpandableNotificationRow row) {
3686             if (row.hasExpandingChild()) {
3687                 setZTranslation(row.getTranslationZ());
3688                 clipTopAmount = row.getClipTopAmount();
3689             }
3690         }
3691 
3692         @Override
3693         protected void onYTranslationAnimationFinished(View view) {
3694             super.onYTranslationAnimationFinished(view);
3695             if (view instanceof ExpandableNotificationRow row) {
3696                 if (row.isHeadsUpAnimatingAway()) {
3697                     row.setHeadsUpAnimatingAway(false);
3698                 }
3699             }
3700         }
3701 
3702         @Override
3703         public void animateTo(View child, AnimationProperties properties) {
3704             if (child instanceof ExpandableNotificationRow row) {
3705                 if (row.isExpandAnimationRunning()) {
3706                     return;
3707                 }
3708                 handleFixedTranslationZ(row);
3709                 super.animateTo(child, properties);
3710                 row.startChildAnimation(properties);
3711             }
3712         }
3713     }
3714 
3715     /**
3716      * Returns the Smart Suggestions backing the smart suggestion buttons in the notification.
3717      */
3718     public InflatedSmartReplyState getExistingSmartReplyState() {
3719         return mPrivateLayout.getCurrentSmartReplyState();
3720     }
3721 
3722     @VisibleForTesting
3723     protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
3724         mChildrenContainer = childrenContainer;
3725     }
3726 
3727     @VisibleForTesting
3728     protected void setPrivateLayout(NotificationContentView privateLayout) {
3729         mPrivateLayout = privateLayout;
3730         mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
3731     }
3732 
3733     @VisibleForTesting
3734     protected void setPublicLayout(NotificationContentView publicLayout) {
3735         mPublicLayout = publicLayout;
3736         mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
3737     }
3738 
3739     /**
3740      * Equivalent to View.OnLongClickListener with coordinates
3741      */
3742     public interface LongPressListener {
3743         /**
3744          * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
3745          *
3746          * @return whether the longpress was handled
3747          */
3748         boolean onLongPress(View v, int x, int y, MenuItem item);
3749     }
3750 
3751     /**
3752      * Called when notification drag and drop is finished successfully.
3753      */
3754     public interface OnDragSuccessListener {
3755         /**
3756          * @param entry NotificationEntry that succeed to drop on proper target window.
3757          */
3758         void onDragSuccess(NotificationEntry entry);
3759     }
3760 
3761     /**
3762      * Equivalent to View.OnClickListener with coordinates
3763      */
3764     public interface CoordinateOnClickListener {
3765         /**
3766          * Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates
3767          *
3768          * @return whether the click was handled
3769          */
3770         boolean onClick(View v, int x, int y, MenuItem item);
3771     }
3772 
3773     @Override
3774     public void dump(PrintWriter pwOriginal, String[] args) {
3775         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
3776         // Skip super call; dump viewState ourselves
3777         pw.println("Notification: " + mEntry.getKey());
3778         DumpUtilsKt.withIncreasedIndent(pw, () -> {
3779             pw.println(this);
3780             pw.print("visibility: " + getVisibility());
3781             pw.print(", alpha: " + getAlpha());
3782             pw.print(", translation: " + getTranslation());
3783             pw.print(", entry dismissable: " + canEntryBeDismissed());
3784             pw.print(", mOnUserInteractionCallback null: " + (mOnUserInteractionCallback == null));
3785             pw.print(", removed: " + isRemoved());
3786             pw.print(", expandAnimationRunning: " + mExpandAnimationRunning);
3787             pw.print(", mShowingPublic: " + mShowingPublic);
3788             pw.print(", mShowingPublicInitialized: " + mShowingPublicInitialized);
3789             NotificationContentView showingLayout = getShowingLayout();
3790             pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
3791             pw.print(", mShowNoBackground: " + mShowNoBackground);
3792             pw.println();
3793             if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) {
3794                 dumpHeights(pw);
3795             }
3796             showingLayout.dump(pw, args);
3797 
3798             if (getViewState() != null) {
3799                 getViewState().dump(pw, args);
3800                 pw.println();
3801             } else {
3802                 pw.println("no viewState!!!");
3803             }
3804             pw.println(getRoundableState().debugString());
3805             if (mBigPictureIconManager != null) {
3806                 mBigPictureIconManager.dump(pw, args);
3807             }
3808             dumpBackgroundView(pw, args);
3809 
3810             int transientViewCount = mChildrenContainer == null
3811                     ? 0 : mChildrenContainer.getTransientViewCount();
3812             if (mIsSummaryWithChildren || transientViewCount > 0) {
3813                 pw.println(mChildrenContainer.debugString());
3814                 pw.println();
3815                 List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
3816                 pw.print("Children: " + notificationChildren.size() + " {");
3817                 pw.increaseIndent();
3818                 for (ExpandableNotificationRow child : notificationChildren) {
3819                     pw.println();
3820                     child.dump(pw, args);
3821                 }
3822                 pw.decreaseIndent();
3823                 pw.println("}");
3824                 pw.print("Transient Views: " + transientViewCount + " {");
3825                 pw.increaseIndent();
3826                 for (int i = 0; i < transientViewCount; i++) {
3827                     pw.println();
3828                     ExpandableView child = (ExpandableView) mChildrenContainer.getTransientView(i);
3829                     child.dump(pw, args);
3830                 }
3831                 pw.decreaseIndent();
3832                 pw.println("}");
3833             } else if (mPrivateLayout != null) {
3834                 mPrivateLayout.dumpSmartReplies(pw);
3835             }
3836         });
3837     }
3838 
3839     private void dumpHeights(IndentingPrintWriter pw) {
3840         pw.print("Heights: ");
3841         pw.print("intrinsic", getIntrinsicHeight());
3842         pw.print("actual", getActualHeight());
3843         pw.print("maxContent", getMaxContentHeight());
3844         pw.print("maxExpanded", getMaxExpandHeight());
3845         pw.print("collapsed", getCollapsedHeight());
3846         pw.print("headsup", getHeadsUpHeight());
3847         pw.print("headsup  without header", getHeadsUpHeightWithoutHeader());
3848         pw.print("minHeight", getMinHeight());
3849         pw.print("pinned headsup", getPinnedHeadsUpHeight(
3850                 true /* atLeastMinHeight */));
3851         pw.println();
3852         pw.print("Intrinsic Height Factors: ");
3853         pw.print("isUserLocked()", isUserLocked());
3854         pw.print("isChildInGroup()", isChildInGroup());
3855         pw.print("isGroupExpanded()", isGroupExpanded());
3856         pw.print("sensitive", mSensitive);
3857         pw.print("hideSensitiveForIntrinsicHeight", mHideSensitiveForIntrinsicHeight);
3858         pw.print("isSummaryWithChildren", mIsSummaryWithChildren);
3859         pw.print("canShowHeadsUp()", canShowHeadsUp());
3860         pw.print("isHeadsUpState()", isHeadsUpState());
3861         pw.print("isPinned()", isPinned());
3862         pw.print("headsupDisappearRunning", mHeadsupDisappearRunning);
3863         pw.print("isExpanded()", isExpanded());
3864         pw.println();
3865     }
3866 
3867     private void logKeepInParentChildDetached(ExpandableNotificationRow child) {
3868         if (mLogger != null) {
3869             mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry());
3870         }
3871     }
3872 
3873     private void logSkipAttachingKeepInParentChild(ExpandableNotificationRow child) {
3874         if (mLogger != null) {
3875             mLogger.logSkipAttachingKeepInParentChild(child.getEntry(), getEntry());
3876         }
3877     }
3878 
3879     private void setTargetPoint(Point p) {
3880         mTargetPoint = p;
3881     }
3882 
3883     public Point getTargetPoint() {
3884         return mTargetPoint;
3885     }
3886 
3887     /** Update the minimum roundness based on current state */
3888     private void updateBaseRoundness() {
3889         if (isChildInGroup()) {
3890             requestRoundnessReset(BASE_VALUE);
3891         } else {
3892             requestRoundness(mSmallRoundness, mSmallRoundness, BASE_VALUE);
3893         }
3894     }
3895 
3896     @Override
3897     protected void onAttachedToWindow() {
3898         super.onAttachedToWindow();
3899         updateBaseRoundness();
3900     }
3901 
3902     /** Set whether this notification may show a snooze action. */
3903     public void setShowSnooze(boolean showSnooze) {
3904         mShowSnooze = showSnooze;
3905     }
3906 
3907     /** Whether this notification may show a snooze action. */
3908     public boolean getShowSnooze() {
3909         return mShowSnooze;
3910     }
3911 
3912     @Override
3913     public void removeFromTransientContainer() {
3914         final ViewGroup transientContainer = getTransientContainer();
3915         final ViewParent parent = getParent();
3916         // Only log when there is real removal of transient views
3917         if (transientContainer == null || transientContainer != parent) {
3918             super.removeFromTransientContainer();
3919             return;
3920         }
3921         logRemoveFromTransientContainer(transientContainer);
3922         super.removeFromTransientContainer();
3923     }
3924 
3925     /**
3926      * Log calls to removeFromTransientContainer when the container is NotificationChildrenContainer
3927      * or NotificationStackScrollLayout.
3928      */
3929     public void logRemoveFromTransientContainer(ViewGroup transientContainer) {
3930         if (mLogger == null) {
3931             return;
3932         }
3933         if (transientContainer instanceof NotificationChildrenContainer) {
3934             mLogger.logRemoveTransientFromContainer(
3935                     /* childEntry = */ getEntry(),
3936                     /* containerEntry = */ ((NotificationChildrenContainer) transientContainer)
3937                             .getContainingNotification().getEntry()
3938             );
3939         } else if (transientContainer instanceof NotificationStackScrollLayout) {
3940             mLogger.logRemoveTransientFromNssl(
3941                     /* childEntry = */ getEntry()
3942             );
3943         } else {
3944             mLogger.logRemoveTransientFromViewGroup(
3945                     /* childEntry = */ getEntry(),
3946                     /* containerView = */ transientContainer
3947             );
3948         }
3949     }
3950 
3951     @Override
3952     public void addTransientView(View view, int index) {
3953         if (view instanceof ExpandableNotificationRow) {
3954             logAddTransientRow((ExpandableNotificationRow) view, index);
3955         }
3956         super.addTransientView(view, index);
3957     }
3958 
3959     private void logAddTransientRow(ExpandableNotificationRow row, int index) {
3960         if (mLogger == null) {
3961             return;
3962         }
3963         mLogger.logAddTransientRow(row.getEntry(), getEntry(), index);
3964     }
3965 
3966     @Override
3967     public void removeTransientView(View view) {
3968         if (view instanceof ExpandableNotificationRow) {
3969             logRemoveTransientRow((ExpandableNotificationRow) view);
3970         }
3971         super.removeTransientView(view);
3972     }
3973 
3974     private void logRemoveTransientRow(ExpandableNotificationRow row) {
3975         if (mLogger == null) {
3976             return;
3977         }
3978         mLogger.logRemoveTransientRow(row.getEntry(), getEntry());
3979     }
3980 }
3981