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