Home | History | Annotate | Download | only in statusbar
      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 {
    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      */
    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
    328     public boolean isGroupExpansionChanging() {
    329         if (isChildInGroup()) {
    330             return mNotificationParent.isGroupExpansionChanging();
    331         }
    332         return mGroupExpansionChanging;
    333     }
    334 
    335     public void setGroupExpansionChanging(boolean changing) {
    336         mGroupExpansionChanging = changing;
    337     }
    338 
    339     @Override
    340     public void setActualHeightAnimating(boolean animating) {
    341         if (mPrivateLayout != null) {
    342             mPrivateLayout.setContentHeightAnimating(animating);
    343         }
    344     }
    345 
    346     public NotificationContentView getPrivateLayout() {
    347         return mPrivateLayout;
    348     }
    349 
    350     public NotificationContentView getPublicLayout() {
    351         return mPublicLayout;
    352     }
    353 
    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 
    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 
    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 
    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 
    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      */
    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      */
    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 
    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
    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 
    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      */
    521     public void setSecureStateProvider(BooleanSupplier secureStateProvider) {
    522         mSecureStateProvider = secureStateProvider;
    523     }
    524 
    525     @Override
    526     public boolean isDimmable() {
    527         if (!getShowingLayout().isDimmable()) {
    528             return false;
    529         }
    530         return super.isDimmable();
    531     }
    532 
    533     private void updateLimits() {
    534         for (NotificationContentView l : mLayouts) {
    535             updateLimitsForView(l);
    536         }
    537     }
    538 
    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