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