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