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