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