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