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