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