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