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.NotificationInflater.InflationCallback;
     20 
     21 import android.animation.Animator;
     22 import android.animation.AnimatorListenerAdapter;
     23 import android.animation.ObjectAnimator;
     24 import android.animation.ValueAnimator.AnimatorUpdateListener;
     25 import android.annotation.Nullable;
     26 import android.content.Context;
     27 import android.content.res.Resources;
     28 import android.content.res.Configuration;
     29 import android.graphics.drawable.AnimatedVectorDrawable;
     30 import android.graphics.drawable.AnimationDrawable;
     31 import android.graphics.drawable.ColorDrawable;
     32 import android.graphics.drawable.Drawable;
     33 import android.os.Build;
     34 import android.os.Bundle;
     35 import android.service.notification.StatusBarNotification;
     36 import android.util.AttributeSet;
     37 import android.util.FloatProperty;
     38 import android.util.Property;
     39 import android.view.LayoutInflater;
     40 import android.view.MotionEvent;
     41 import android.view.NotificationHeaderView;
     42 import android.view.View;
     43 import android.view.ViewGroup;
     44 import android.view.ViewStub;
     45 import android.view.accessibility.AccessibilityEvent;
     46 import android.view.accessibility.AccessibilityNodeInfo;
     47 import android.widget.Chronometer;
     48 import android.widget.FrameLayout;
     49 import android.widget.ImageView;
     50 import android.widget.RemoteViews;
     51 
     52 import com.android.internal.annotations.VisibleForTesting;
     53 import com.android.internal.logging.MetricsLogger;
     54 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     55 import com.android.internal.util.NotificationColorUtil;
     56 import com.android.internal.widget.CachingIconView;
     57 import com.android.systemui.Dependency;
     58 import com.android.systemui.Interpolators;
     59 import com.android.systemui.R;
     60 import com.android.systemui.classifier.FalsingManager;
     61 import com.android.systemui.plugins.PluginListener;
     62 import com.android.systemui.plugins.PluginManager;
     63 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
     64 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
     65 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
     66 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
     67 import com.android.systemui.statusbar.notification.AboveShelfObserver;
     68 import com.android.systemui.statusbar.notification.HybridNotificationView;
     69 import com.android.systemui.statusbar.notification.NotificationInflater;
     70 import com.android.systemui.statusbar.notification.NotificationUtils;
     71 import com.android.systemui.statusbar.notification.VisualStabilityManager;
     72 import com.android.systemui.statusbar.phone.NotificationGroupManager;
     73 import com.android.systemui.statusbar.phone.StatusBar;
     74 import com.android.systemui.statusbar.policy.HeadsUpManager;
     75 import com.android.systemui.statusbar.stack.AnimationProperties;
     76 import com.android.systemui.statusbar.stack.ExpandableViewState;
     77 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
     78 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
     79 import com.android.systemui.statusbar.stack.StackScrollState;
     80 
     81 import java.util.ArrayList;
     82 import java.util.List;
     83 import java.util.function.BooleanSupplier;
     84 
     85 public class ExpandableNotificationRow extends ActivatableNotificationView
     86         implements PluginListener<NotificationMenuRowPlugin> {
     87 
     88     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
     89     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
     90     private static final int MENU_VIEW_INDEX = 0;
     91 
     92     public interface LayoutListener {
     93         public void onLayout();
     94     }
     95 
     96     private LayoutListener mLayoutListener;
     97     private boolean mDark;
     98     private boolean mLowPriorityStateUpdated;
     99     private final NotificationInflater mNotificationInflater;
    100     private int mIconTransformContentShift;
    101     private int mIconTransformContentShiftNoIcon;
    102     private int mNotificationMinHeightLegacy;
    103     private int mMaxHeadsUpHeightLegacy;
    104     private int mMaxHeadsUpHeight;
    105     private int mMaxHeadsUpHeightIncreased;
    106     private int mNotificationMinHeight;
    107     private int mNotificationMinHeightLarge;
    108     private int mNotificationMaxHeight;
    109     private int mNotificationAmbientHeight;
    110     private int mIncreasedPaddingBetweenElements;
    111 
    112     /** Does this row contain layouts that can adapt to row expansion */
    113     private boolean mExpandable;
    114     /** Has the user actively changed the expansion state of this row */
    115     private boolean mHasUserChangedExpansion;
    116     /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
    117     private boolean mUserExpanded;
    118 
    119     /**
    120      * Has this notification been expanded while it was pinned
    121      */
    122     private boolean mExpandedWhenPinned;
    123     /** Is the user touching this row */
    124     private boolean mUserLocked;
    125     /** Are we showing the "public" version */
    126     private boolean mShowingPublic;
    127     private boolean mSensitive;
    128     private boolean mSensitiveHiddenInGeneral;
    129     private boolean mShowingPublicInitialized;
    130     private boolean mHideSensitiveForIntrinsicHeight;
    131 
    132     /**
    133      * Is this notification expanded by the system. The expansion state can be overridden by the
    134      * user expansion.
    135      */
    136     private boolean mIsSystemExpanded;
    137 
    138     /**
    139      * Whether the notification is on the keyguard and the expansion is disabled.
    140      */
    141     private boolean mOnKeyguard;
    142 
    143     private Animator mTranslateAnim;
    144     private ArrayList<View> mTranslateableViews;
    145     private NotificationContentView mPublicLayout;
    146     private NotificationContentView mPrivateLayout;
    147     private NotificationContentView[] mLayouts;
    148     private int mMaxExpandHeight;
    149     private int mHeadsUpHeight;
    150     private int mNotificationColor;
    151     private ExpansionLogger mLogger;
    152     private String mLoggingKey;
    153     private NotificationGuts mGuts;
    154     private NotificationData.Entry mEntry;
    155     private StatusBarNotification mStatusBarNotification;
    156     private String mAppName;
    157     private boolean mIsHeadsUp;
    158     private boolean mLastChronometerRunning = true;
    159     private ViewStub mChildrenContainerStub;
    160     private NotificationGroupManager mGroupManager;
    161     private boolean mChildrenExpanded;
    162     private boolean mIsSummaryWithChildren;
    163     private NotificationChildrenContainer mChildrenContainer;
    164     private NotificationMenuRowPlugin mMenuRow;
    165     private ViewStub mGutsStub;
    166     private boolean mIsSystemChildExpanded;
    167     private boolean mIsPinned;
    168     private FalsingManager mFalsingManager;
    169     private AboveShelfChangedListener mAboveShelfChangedListener;
    170     private HeadsUpManager mHeadsUpManager;
    171 
    172     private boolean mJustClicked;
    173     private boolean mIconAnimationRunning;
    174     private boolean mShowNoBackground;
    175     private ExpandableNotificationRow mNotificationParent;
    176     private OnExpandClickListener mOnExpandClickListener;
    177     private boolean mGroupExpansionChanging;
    178 
    179     /**
    180      * A supplier that returns true if keyguard is secure.
    181      */
    182     private BooleanSupplier mSecureStateProvider;
    183 
    184     /**
    185      * Whether or not a notification that is not part of a group of notifications can be manually
    186      * expanded by the user.
    187      */
    188     private boolean mEnableNonGroupedNotificationExpand;
    189 
    190     /**
    191      * Whether or not to update the background of the header of the notification when its expanded.
    192      * If {@code true}, the header background will disappear when expanded.
    193      */
    194     private boolean mShowGroupBackgroundWhenExpanded;
    195 
    196     private OnClickListener mExpandClickListener = new OnClickListener() {
    197         @Override
    198         public void onClick(View v) {
    199             if (!mShowingPublic && (!mIsLowPriority || isExpanded())
    200                     && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
    201                 mGroupExpansionChanging = true;
    202                 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
    203                 boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification);
    204                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
    205                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER,
    206                         nowExpanded);
    207                 onExpansionChanged(true /* userAction */, wasExpanded);
    208             } else if (mEnableNonGroupedNotificationExpand) {
    209                 if (v.isAccessibilityFocused()) {
    210                     mPrivateLayout.setFocusOnVisibilityChange();
    211                 }
    212                 boolean nowExpanded;
    213                 if (isPinned()) {
    214                     nowExpanded = !mExpandedWhenPinned;
    215                     mExpandedWhenPinned = nowExpanded;
    216                 } else {
    217                     nowExpanded = !isExpanded();
    218                     setUserExpanded(nowExpanded);
    219                 }
    220                 notifyHeightChanged(true);
    221                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
    222                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_EXPANDER,
    223                         nowExpanded);
    224             }
    225         }
    226     };
    227     private boolean mForceUnlocked;
    228     private boolean mDismissed;
    229     private boolean mKeepInParent;
    230     private boolean mRemoved;
    231     private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
    232             new FloatProperty<ExpandableNotificationRow>("translate") {
    233                 @Override
    234                 public void setValue(ExpandableNotificationRow object, float value) {
    235                     object.setTranslation(value);
    236                 }
    237 
    238                 @Override
    239                 public Float get(ExpandableNotificationRow object) {
    240                     return object.getTranslation();
    241                 }
    242     };
    243     private OnClickListener mOnClickListener;
    244     private boolean mHeadsupDisappearRunning;
    245     private View mChildAfterViewWhenDismissed;
    246     private View mGroupParentWhenDismissed;
    247     private boolean mRefocusOnDismiss;
    248     private float mContentTransformationAmount;
    249     private boolean mIconsVisible = true;
    250     private boolean mAboveShelf;
    251     private boolean mShowAmbient;
    252     private boolean mIsLastChild;
    253     private Runnable mOnDismissRunnable;
    254     private boolean mIsLowPriority;
    255     private boolean mIsColorized;
    256     private boolean mUseIncreasedCollapsedHeight;
    257     private boolean mUseIncreasedHeadsUpHeight;
    258     private float mTranslationWhenRemoved;
    259     private boolean mWasChildInGroupWhenRemoved;
    260     private int mNotificationColorAmbient;
    261 
    262     @Override
    263     public boolean isGroupExpansionChanging() {
    264         if (isChildInGroup()) {
    265             return mNotificationParent.isGroupExpansionChanging();
    266         }
    267         return mGroupExpansionChanging;
    268     }
    269 
    270     public void setGroupExpansionChanging(boolean changing) {
    271         mGroupExpansionChanging = changing;
    272     }
    273 
    274     @Override
    275     public void setActualHeightAnimating(boolean animating) {
    276         if (mPrivateLayout != null) {
    277             mPrivateLayout.setContentHeightAnimating(animating);
    278         }
    279     }
    280 
    281     public NotificationContentView getPrivateLayout() {
    282         return mPrivateLayout;
    283     }
    284 
    285     public NotificationContentView getPublicLayout() {
    286         return mPublicLayout;
    287     }
    288 
    289     public void setIconAnimationRunning(boolean running) {
    290         for (NotificationContentView l : mLayouts) {
    291             setIconAnimationRunning(running, l);
    292         }
    293         if (mIsSummaryWithChildren) {
    294             setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView());
    295             setIconAnimationRunningForChild(running, mChildrenContainer.getLowPriorityHeaderView());
    296             List<ExpandableNotificationRow> notificationChildren =
    297                     mChildrenContainer.getNotificationChildren();
    298             for (int i = 0; i < notificationChildren.size(); i++) {
    299                 ExpandableNotificationRow child = notificationChildren.get(i);
    300                 child.setIconAnimationRunning(running);
    301             }
    302         }
    303         mIconAnimationRunning = running;
    304     }
    305 
    306     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
    307         if (layout != null) {
    308             View contractedChild = layout.getContractedChild();
    309             View expandedChild = layout.getExpandedChild();
    310             View headsUpChild = layout.getHeadsUpChild();
    311             setIconAnimationRunningForChild(running, contractedChild);
    312             setIconAnimationRunningForChild(running, expandedChild);
    313             setIconAnimationRunningForChild(running, headsUpChild);
    314         }
    315     }
    316 
    317     private void setIconAnimationRunningForChild(boolean running, View child) {
    318         if (child != null) {
    319             ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
    320             setIconRunning(icon, running);
    321             ImageView rightIcon = (ImageView) child.findViewById(
    322                     com.android.internal.R.id.right_icon);
    323             setIconRunning(rightIcon, running);
    324         }
    325     }
    326 
    327     private void setIconRunning(ImageView imageView, boolean running) {
    328         if (imageView != null) {
    329             Drawable drawable = imageView.getDrawable();
    330             if (drawable instanceof AnimationDrawable) {
    331                 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
    332                 if (running) {
    333                     animationDrawable.start();
    334                 } else {
    335                     animationDrawable.stop();
    336                 }
    337             } else if (drawable instanceof AnimatedVectorDrawable) {
    338                 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
    339                 if (running) {
    340                     animationDrawable.start();
    341                 } else {
    342                     animationDrawable.stop();
    343                 }
    344             }
    345         }
    346     }
    347 
    348     public void updateNotification(NotificationData.Entry entry) {
    349         mEntry = entry;
    350         mStatusBarNotification = entry.notification;
    351         mNotificationInflater.inflateNotificationViews();
    352     }
    353 
    354     public void onNotificationUpdated() {
    355         for (NotificationContentView l : mLayouts) {
    356             l.onNotificationUpdated(mEntry);
    357         }
    358         mIsColorized = mStatusBarNotification.getNotification().isColorized();
    359         mShowingPublicInitialized = false;
    360         updateNotificationColor();
    361         if (mMenuRow != null) {
    362             mMenuRow.onNotificationUpdated(mStatusBarNotification);
    363         }
    364         if (mIsSummaryWithChildren) {
    365             mChildrenContainer.recreateNotificationHeader(mExpandClickListener);
    366             mChildrenContainer.onNotificationUpdated();
    367         }
    368         if (mIconAnimationRunning) {
    369             setIconAnimationRunning(true);
    370         }
    371         if (mNotificationParent != null) {
    372             mNotificationParent.updateChildrenHeaderAppearance();
    373         }
    374         onChildrenCountChanged();
    375         // The public layouts expand button is always visible
    376         mPublicLayout.updateExpandButtons(true);
    377         updateLimits();
    378         updateIconVisibilities();
    379         updateShelfIconColor();
    380     }
    381 
    382     @VisibleForTesting
    383     void updateShelfIconColor() {
    384         StatusBarIconView expandedIcon = mEntry.expandedIcon;
    385         boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
    386         boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
    387                 NotificationColorUtil.getInstance(mContext));
    388         int color = StatusBarIconView.NO_COLOR;
    389         if (colorize) {
    390             NotificationHeaderView header = getVisibleNotificationHeader();
    391             if (header != null) {
    392                 color = header.getOriginalIconColor();
    393             } else {
    394                 color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(),
    395                         getBackgroundColorWithoutTint());
    396             }
    397         }
    398         expandedIcon.setStaticDrawableColor(color);
    399     }
    400 
    401     public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) {
    402         mAboveShelfChangedListener = aboveShelfChangedListener;
    403     }
    404 
    405     /**
    406      * Sets a supplier that can determine whether the keyguard is secure or not.
    407      * @param secureStateProvider A function that returns true if keyguard is secure.
    408      */
    409     public void setSecureStateProvider(BooleanSupplier secureStateProvider) {
    410         mSecureStateProvider = secureStateProvider;
    411     }
    412 
    413     @Override
    414     public boolean isDimmable() {
    415         if (!getShowingLayout().isDimmable()) {
    416             return false;
    417         }
    418         return super.isDimmable();
    419     }
    420 
    421     private void updateLimits() {
    422         for (NotificationContentView l : mLayouts) {
    423             updateLimitsForView(l);
    424         }
    425     }
    426 
    427     private void updateLimitsForView(NotificationContentView layout) {
    428         boolean customView = layout.getContractedChild().getId()
    429                 != com.android.internal.R.id.status_bar_latest_event_content;
    430         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
    431         int minHeight;
    432         if (customView && beforeN && !mIsSummaryWithChildren) {
    433             minHeight = mNotificationMinHeightLegacy;
    434         } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
    435             minHeight = mNotificationMinHeightLarge;
    436         } else {
    437             minHeight = mNotificationMinHeight;
    438         }
    439         boolean headsUpCustom = layout.getHeadsUpChild() != null &&
    440                 layout.getHeadsUpChild().getId()
    441                         != com.android.internal.R.id.status_bar_latest_event_content;
    442         int headsUpheight;
    443         if (headsUpCustom && beforeN) {
    444             headsUpheight = mMaxHeadsUpHeightLegacy;
    445         } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
    446             headsUpheight = mMaxHeadsUpHeightIncreased;
    447         } else {
    448             headsUpheight = mMaxHeadsUpHeight;
    449         }
    450         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
    451                 mNotificationAmbientHeight);
    452     }
    453 
    454     public StatusBarNotification getStatusBarNotification() {
    455         return mStatusBarNotification;
    456     }
    457 
    458     public NotificationData.Entry getEntry() {
    459         return mEntry;
    460     }
    461 
    462     public boolean isHeadsUp() {
    463         return mIsHeadsUp;
    464     }
    465 
    466     public void setHeadsUp(boolean isHeadsUp) {
    467         boolean wasAboveShelf = isAboveShelf();
    468         int intrinsicBefore = getIntrinsicHeight();
    469         mIsHeadsUp = isHeadsUp;
    470         mPrivateLayout.setHeadsUp(isHeadsUp);
    471         if (mIsSummaryWithChildren) {
    472             // The overflow might change since we allow more lines as HUN.
    473             mChildrenContainer.updateGroupOverflow();
    474         }
    475         if (intrinsicBefore != getIntrinsicHeight()) {
    476             notifyHeightChanged(false  /* needsAnimation */);
    477         }
    478         if (isHeadsUp) {
    479             setAboveShelf(true);
    480         } else if (isAboveShelf() != wasAboveShelf) {
    481             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
    482         }
    483     }
    484 
    485     public void setGroupManager(NotificationGroupManager groupManager) {
    486         mGroupManager = groupManager;
    487         mPrivateLayout.setGroupManager(groupManager);
    488     }
    489 
    490     public void setRemoteInputController(RemoteInputController r) {
    491         mPrivateLayout.setRemoteInputController(r);
    492     }
    493 
    494     public void setAppName(String appName) {
    495         mAppName = appName;
    496         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
    497             mMenuRow.setAppName(mAppName);
    498         }
    499     }
    500 
    501     public void addChildNotification(ExpandableNotificationRow row) {
    502         addChildNotification(row, -1);
    503     }
    504 
    505     /**
    506      * Add a child notification to this view.
    507      *
    508      * @param row the row to add
    509      * @param childIndex the index to add it at, if -1 it will be added at the end
    510      */
    511     public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
    512         if (mChildrenContainer == null) {
    513             mChildrenContainerStub.inflate();
    514         }
    515         mChildrenContainer.addNotification(row, childIndex);
    516         onChildrenCountChanged();
    517         row.setIsChildInGroup(true, this);
    518     }
    519 
    520     public void removeChildNotification(ExpandableNotificationRow row) {
    521         if (mChildrenContainer != null) {
    522             mChildrenContainer.removeNotification(row);
    523         }
    524         onChildrenCountChanged();
    525         row.setIsChildInGroup(false, null);
    526     }
    527 
    528     @Override
    529     public boolean isChildInGroup() {
    530         return mNotificationParent != null;
    531     }
    532 
    533     public ExpandableNotificationRow getNotificationParent() {
    534         return mNotificationParent;
    535     }
    536 
    537     /**
    538      * @param isChildInGroup Is this notification now in a group
    539      * @param parent the new parent notification
    540      */
    541     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {;
    542         boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
    543         mNotificationParent = childInGroup ? parent : null;
    544         mPrivateLayout.setIsChildInGroup(childInGroup);
    545         mNotificationInflater.setIsChildInGroup(childInGroup);
    546         resetBackgroundAlpha();
    547         updateBackgroundForGroupState();
    548         updateClickAndFocus();
    549         if (mNotificationParent != null) {
    550             setOverrideTintColor(NO_COLOR, 0.0f);
    551             mNotificationParent.updateBackgroundForGroupState();
    552         }
    553         updateIconVisibilities();
    554     }
    555 
    556     @Override
    557     public boolean onTouchEvent(MotionEvent event) {
    558         if (event.getActionMasked() != MotionEvent.ACTION_DOWN
    559                 || !isChildInGroup() || isGroupExpanded()) {
    560             return super.onTouchEvent(event);
    561         } else {
    562             return false;
    563         }
    564     }
    565 
    566     @Override
    567     protected boolean handleSlideBack() {
    568         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
    569             animateTranslateNotification(0 /* targetLeft */);
    570             return true;
    571         }
    572         return false;
    573     }
    574 
    575     @Override
    576     protected boolean shouldHideBackground() {
    577         return super.shouldHideBackground() || mShowNoBackground;
    578     }
    579 
    580     @Override
    581     public boolean isSummaryWithChildren() {
    582         return mIsSummaryWithChildren;
    583     }
    584 
    585     @Override
    586     public boolean areChildrenExpanded() {
    587         return mChildrenExpanded;
    588     }
    589 
    590     public List<ExpandableNotificationRow> getNotificationChildren() {
    591         return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
    592     }
    593 
    594     public int getNumberOfNotificationChildren() {
    595         if (mChildrenContainer == null) {
    596             return 0;
    597         }
    598         return mChildrenContainer.getNotificationChildren().size();
    599     }
    600 
    601     /**
    602      * Apply the order given in the list to the children.
    603      *
    604      * @param childOrder the new list order
    605      * @param visualStabilityManager
    606      * @param callback the callback to invoked in case it is not allowed
    607      * @return whether the list order has changed
    608      */
    609     public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
    610             VisualStabilityManager visualStabilityManager,
    611             VisualStabilityManager.Callback callback) {
    612         return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder,
    613                 visualStabilityManager, callback);
    614     }
    615 
    616     public void getChildrenStates(StackScrollState resultState) {
    617         if (mIsSummaryWithChildren) {
    618             ExpandableViewState parentState = resultState.getViewStateForView(this);
    619             mChildrenContainer.getState(resultState, parentState);
    620         }
    621     }
    622 
    623     public void applyChildrenState(StackScrollState state) {
    624         if (mIsSummaryWithChildren) {
    625             mChildrenContainer.applyState(state);
    626         }
    627     }
    628 
    629     public void prepareExpansionChanged(StackScrollState state) {
    630         if (mIsSummaryWithChildren) {
    631             mChildrenContainer.prepareExpansionChanged(state);
    632         }
    633     }
    634 
    635     public void startChildAnimation(StackScrollState finalState, AnimationProperties properties) {
    636         if (mIsSummaryWithChildren) {
    637             mChildrenContainer.startAnimationToState(finalState, properties);
    638         }
    639     }
    640 
    641     public ExpandableNotificationRow getViewAtPosition(float y) {
    642         if (!mIsSummaryWithChildren || !mChildrenExpanded) {
    643             return this;
    644         } else {
    645             ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
    646             return view == null ? this : view;
    647         }
    648     }
    649 
    650     public NotificationGuts getGuts() {
    651         return mGuts;
    652     }
    653 
    654     /**
    655      * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
    656      * the notification will be rendered on top of the screen.
    657      *
    658      * @param pinned whether it is pinned
    659      */
    660     public void setPinned(boolean pinned) {
    661         int intrinsicHeight = getIntrinsicHeight();
    662         boolean wasAboveShelf = isAboveShelf();
    663         mIsPinned = pinned;
    664         if (intrinsicHeight != getIntrinsicHeight()) {
    665             notifyHeightChanged(false /* needsAnimation */);
    666         }
    667         if (pinned) {
    668             setIconAnimationRunning(true);
    669             mExpandedWhenPinned = false;
    670         } else if (mExpandedWhenPinned) {
    671             setUserExpanded(true);
    672         }
    673         setChronometerRunning(mLastChronometerRunning);
    674         if (isAboveShelf() != wasAboveShelf) {
    675             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
    676         }
    677     }
    678 
    679     public boolean isPinned() {
    680         return mIsPinned;
    681     }
    682 
    683     @Override
    684     public int getPinnedHeadsUpHeight() {
    685         return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
    686     }
    687 
    688     /**
    689      * @param atLeastMinHeight should the value returned be at least the minimum height.
    690      *                         Used to avoid cyclic calls
    691      * @return the height of the heads up notification when pinned
    692      */
    693     private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
    694         if (mIsSummaryWithChildren) {
    695             return mChildrenContainer.getIntrinsicHeight();
    696         }
    697         if(mExpandedWhenPinned) {
    698             return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
    699         } else if (atLeastMinHeight) {
    700             return Math.max(getCollapsedHeight(), mHeadsUpHeight);
    701         } else {
    702             return mHeadsUpHeight;
    703         }
    704     }
    705 
    706     /**
    707      * Mark whether this notification was just clicked, i.e. the user has just clicked this
    708      * notification in this frame.
    709      */
    710     public void setJustClicked(boolean justClicked) {
    711         mJustClicked = justClicked;
    712     }
    713 
    714     /**
    715      * @return true if this notification has been clicked in this frame, false otherwise
    716      */
    717     public boolean wasJustClicked() {
    718         return mJustClicked;
    719     }
    720 
    721     public void setChronometerRunning(boolean running) {
    722         mLastChronometerRunning = running;
    723         setChronometerRunning(running, mPrivateLayout);
    724         setChronometerRunning(running, mPublicLayout);
    725         if (mChildrenContainer != null) {
    726             List<ExpandableNotificationRow> notificationChildren =
    727                     mChildrenContainer.getNotificationChildren();
    728             for (int i = 0; i < notificationChildren.size(); i++) {
    729                 ExpandableNotificationRow child = notificationChildren.get(i);
    730                 child.setChronometerRunning(running);
    731             }
    732         }
    733     }
    734 
    735     private void setChronometerRunning(boolean running, NotificationContentView layout) {
    736         if (layout != null) {
    737             running = running || isPinned();
    738             View contractedChild = layout.getContractedChild();
    739             View expandedChild = layout.getExpandedChild();
    740             View headsUpChild = layout.getHeadsUpChild();
    741             setChronometerRunningForChild(running, contractedChild);
    742             setChronometerRunningForChild(running, expandedChild);
    743             setChronometerRunningForChild(running, headsUpChild);
    744         }
    745     }
    746 
    747     private void setChronometerRunningForChild(boolean running, View child) {
    748         if (child != null) {
    749             View chronometer = child.findViewById(com.android.internal.R.id.chronometer);
    750             if (chronometer instanceof Chronometer) {
    751                 ((Chronometer) chronometer).setStarted(running);
    752             }
    753         }
    754     }
    755 
    756     public NotificationHeaderView getNotificationHeader() {
    757         if (mIsSummaryWithChildren) {
    758             return mChildrenContainer.getHeaderView();
    759         }
    760         return mPrivateLayout.getNotificationHeader();
    761     }
    762 
    763     /**
    764      * @return the currently visible notification header. This can be different from
    765      * {@link #getNotificationHeader()} in case it is a low-priority group.
    766      */
    767     public NotificationHeaderView getVisibleNotificationHeader() {
    768         if (mIsSummaryWithChildren && !mShowingPublic) {
    769             return mChildrenContainer.getVisibleHeader();
    770         }
    771         return getShowingLayout().getVisibleNotificationHeader();
    772     }
    773 
    774 
    775     /**
    776      * @return the contracted notification header. This can be different from
    777      * {@link #getNotificationHeader()} and also {@link #getVisibleNotificationHeader()} and only
    778      * returns the contracted version.
    779      */
    780     public NotificationHeaderView getContractedNotificationHeader() {
    781         if (mIsSummaryWithChildren) {
    782             return mChildrenContainer.getHeaderView();
    783         }
    784         return mPrivateLayout.getContractedNotificationHeader();
    785     }
    786 
    787     public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
    788         mOnExpandClickListener = onExpandClickListener;
    789     }
    790 
    791     @Override
    792     public void setOnClickListener(@Nullable OnClickListener l) {
    793         super.setOnClickListener(l);
    794         mOnClickListener = l;
    795         updateClickAndFocus();
    796     }
    797 
    798     private void updateClickAndFocus() {
    799         boolean normalChild = !isChildInGroup() || isGroupExpanded();
    800         boolean clickable = mOnClickListener != null && normalChild;
    801         if (isFocusable() != normalChild) {
    802             setFocusable(normalChild);
    803         }
    804         if (isClickable() != clickable) {
    805             setClickable(clickable);
    806         }
    807     }
    808 
    809     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
    810         mHeadsUpManager = headsUpManager;
    811     }
    812 
    813     public void setGutsView(MenuItem item) {
    814         if (mGuts != null && item.getGutsView() instanceof GutsContent) {
    815             ((GutsContent) item.getGutsView()).setGutsParent(mGuts);
    816             mGuts.setGutsContent((GutsContent) item.getGutsView());
    817         }
    818     }
    819 
    820     @Override
    821     protected void onAttachedToWindow() {
    822         super.onAttachedToWindow();
    823         Dependency.get(PluginManager.class).addPluginListener(this,
    824                 NotificationMenuRowPlugin.class, false /* Allow multiple */);
    825     }
    826 
    827     @Override
    828     protected void onDetachedFromWindow() {
    829         super.onDetachedFromWindow();
    830         Dependency.get(PluginManager.class).removePluginListener(this);
    831     }
    832 
    833     @Override
    834     public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) {
    835         boolean existed = mMenuRow.getMenuView() != null;
    836         if (existed) {
    837             removeView(mMenuRow.getMenuView());
    838         }
    839         mMenuRow = plugin;
    840         if (mMenuRow.useDefaultMenuItems()) {
    841             ArrayList<MenuItem> items = new ArrayList<>();
    842             items.add(NotificationMenuRow.createInfoItem(mContext));
    843             items.add(NotificationMenuRow.createSnoozeItem(mContext));
    844             mMenuRow.setMenuItems(items);
    845         }
    846         if (existed) {
    847             createMenu();
    848         }
    849     }
    850 
    851     @Override
    852     public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
    853         boolean existed = mMenuRow.getMenuView() != null;
    854         mMenuRow = new NotificationMenuRow(mContext); // Back to default
    855         if (existed) {
    856             createMenu();
    857         }
    858     }
    859 
    860     public NotificationMenuRowPlugin createMenu() {
    861         if (mMenuRow.getMenuView() == null) {
    862             mMenuRow.createMenu(this, mStatusBarNotification);
    863             mMenuRow.setAppName(mAppName);
    864             FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
    865                     LayoutParams.MATCH_PARENT);
    866             addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp);
    867         }
    868         return mMenuRow;
    869     }
    870 
    871     public NotificationMenuRowPlugin getProvider() {
    872         return mMenuRow;
    873     }
    874 
    875     @Override
    876     public void onDensityOrFontScaleChanged() {
    877         super.onDensityOrFontScaleChanged();
    878         initDimens();
    879         initBackground();
    880         // Let's update our childrencontainer. This is intentionally not guarded with
    881         // mIsSummaryWithChildren since we might have had children but not anymore.
    882         if (mChildrenContainer != null) {
    883             mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.notification);
    884         }
    885         if (mGuts != null) {
    886             View oldGuts = mGuts;
    887             int index = indexOfChild(oldGuts);
    888             removeView(oldGuts);
    889             mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate(
    890                     R.layout.notification_guts, this, false);
    891             mGuts.setVisibility(oldGuts.getVisibility());
    892             addView(mGuts, index);
    893         }
    894         View oldMenu = mMenuRow.getMenuView();
    895         if (oldMenu != null) {
    896             int menuIndex = indexOfChild(oldMenu);
    897             removeView(oldMenu);
    898             mMenuRow.createMenu(ExpandableNotificationRow.this, mStatusBarNotification);
    899             mMenuRow.setAppName(mAppName);
    900             addView(mMenuRow.getMenuView(), menuIndex);
    901         }
    902         for (NotificationContentView l : mLayouts) {
    903             l.reInflateViews();
    904         }
    905         mNotificationInflater.onDensityOrFontScaleChanged();
    906         onNotificationUpdated();
    907     }
    908 
    909     @Override
    910     public void onConfigurationChanged(Configuration newConfig) {
    911         if (mMenuRow.getMenuView() != null) {
    912             mMenuRow.onConfigurationChanged();
    913         }
    914     }
    915 
    916     public void setContentBackground(int customBackgroundColor, boolean animate,
    917             NotificationContentView notificationContentView) {
    918         if (getShowingLayout() == notificationContentView) {
    919             setTintColor(customBackgroundColor, animate);
    920         }
    921     }
    922 
    923     public void closeRemoteInput() {
    924         for (NotificationContentView l : mLayouts) {
    925             l.closeRemoteInput();
    926         }
    927     }
    928 
    929     /**
    930      * Set by how much the single line view should be indented.
    931      */
    932     public void setSingleLineWidthIndention(int indention) {
    933         mPrivateLayout.setSingleLineWidthIndention(indention);
    934     }
    935 
    936     public int getNotificationColor() {
    937         return mNotificationColor;
    938     }
    939 
    940     private void updateNotificationColor() {
    941         mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext,
    942                 getStatusBarNotification().getNotification().color,
    943                 getBackgroundColorWithoutTint());
    944         mNotificationColorAmbient = NotificationColorUtil.resolveAmbientColor(mContext,
    945                 getStatusBarNotification().getNotification().color);
    946     }
    947 
    948     public HybridNotificationView getSingleLineView() {
    949         return mPrivateLayout.getSingleLineView();
    950     }
    951 
    952     public HybridNotificationView getAmbientSingleLineView() {
    953         return getShowingLayout().getAmbientSingleLineChild();
    954     }
    955 
    956     public boolean isOnKeyguard() {
    957         return mOnKeyguard;
    958     }
    959 
    960     public void removeAllChildren() {
    961         List<ExpandableNotificationRow> notificationChildren
    962                 = mChildrenContainer.getNotificationChildren();
    963         ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren);
    964         for (int i = 0; i < clonedList.size(); i++) {
    965             ExpandableNotificationRow row = clonedList.get(i);
    966             if (row.keepInParent()) {
    967                 continue;
    968             }
    969             mChildrenContainer.removeNotification(row);
    970             row.setIsChildInGroup(false, null);
    971         }
    972         onChildrenCountChanged();
    973     }
    974 
    975     public void setForceUnlocked(boolean forceUnlocked) {
    976         mForceUnlocked = forceUnlocked;
    977         if (mIsSummaryWithChildren) {
    978             List<ExpandableNotificationRow> notificationChildren = getNotificationChildren();
    979             for (ExpandableNotificationRow child : notificationChildren) {
    980                 child.setForceUnlocked(forceUnlocked);
    981             }
    982         }
    983     }
    984 
    985     public void setDismissed(boolean dismissed, boolean fromAccessibility) {
    986         mDismissed = dismissed;
    987         mGroupParentWhenDismissed = mNotificationParent;
    988         mRefocusOnDismiss = fromAccessibility;
    989         mChildAfterViewWhenDismissed = null;
    990         if (isChildInGroup()) {
    991             List<ExpandableNotificationRow> notificationChildren =
    992                     mNotificationParent.getNotificationChildren();
    993             int i = notificationChildren.indexOf(this);
    994             if (i != -1 && i < notificationChildren.size() - 1) {
    995                 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1);
    996             }
    997         }
    998     }
    999 
   1000     public boolean isDismissed() {
   1001         return mDismissed;
   1002     }
   1003 
   1004     public boolean keepInParent() {
   1005         return mKeepInParent;
   1006     }
   1007 
   1008     public void setKeepInParent(boolean keepInParent) {
   1009         mKeepInParent = keepInParent;
   1010     }
   1011 
   1012     public boolean isRemoved() {
   1013         return mRemoved;
   1014     }
   1015 
   1016     public void setRemoved() {
   1017         mRemoved = true;
   1018         mTranslationWhenRemoved = getTranslationY();
   1019         mWasChildInGroupWhenRemoved = isChildInGroup();
   1020         if (isChildInGroup()) {
   1021             mTranslationWhenRemoved += getNotificationParent().getTranslationY();
   1022         }
   1023         mPrivateLayout.setRemoved();
   1024     }
   1025 
   1026     public boolean wasChildInGroupWhenRemoved() {
   1027         return mWasChildInGroupWhenRemoved;
   1028     }
   1029 
   1030     public float getTranslationWhenRemoved() {
   1031         return mTranslationWhenRemoved;
   1032     }
   1033 
   1034     public NotificationChildrenContainer getChildrenContainer() {
   1035         return mChildrenContainer;
   1036     }
   1037 
   1038     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
   1039         boolean wasAboveShelf = isAboveShelf();
   1040         mHeadsupDisappearRunning = headsUpAnimatingAway;
   1041         mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway);
   1042         if (isAboveShelf() != wasAboveShelf) {
   1043             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
   1044         }
   1045     }
   1046 
   1047     /**
   1048      * @return if the view was just heads upped and is now animating away. During such a time the
   1049      * layout needs to be kept consistent
   1050      */
   1051     public boolean isHeadsUpAnimatingAway() {
   1052         return mHeadsupDisappearRunning;
   1053     }
   1054 
   1055     public View getChildAfterViewWhenDismissed() {
   1056         return mChildAfterViewWhenDismissed;
   1057     }
   1058 
   1059     public View getGroupParentWhenDismissed() {
   1060         return mGroupParentWhenDismissed;
   1061     }
   1062 
   1063     public void performDismiss() {
   1064         if (mOnDismissRunnable != null) {
   1065             mOnDismissRunnable.run();
   1066         }
   1067     }
   1068 
   1069     public void setOnDismissRunnable(Runnable onDismissRunnable) {
   1070         mOnDismissRunnable = onDismissRunnable;
   1071     }
   1072 
   1073     public View getNotificationIcon() {
   1074         NotificationHeaderView notificationHeader = getVisibleNotificationHeader();
   1075         if (notificationHeader != null) {
   1076             return notificationHeader.getIcon();
   1077         }
   1078         return null;
   1079     }
   1080 
   1081     /**
   1082      * @return whether the notification is currently showing a view with an icon.
   1083      */
   1084     public boolean isShowingIcon() {
   1085         if (areGutsExposed()) {
   1086             return false;
   1087         }
   1088         return getVisibleNotificationHeader() != null;
   1089     }
   1090 
   1091     /**
   1092      * Set how much this notification is transformed into an icon.
   1093      *
   1094      * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed
   1095      *                                 to the content away
   1096      * @param isLastChild is this the last child in the list. If true, then the transformation is
   1097      *                    different since it's content fades out.
   1098      */
   1099     public void setContentTransformationAmount(float contentTransformationAmount,
   1100             boolean isLastChild) {
   1101         boolean changeTransformation = isLastChild != mIsLastChild;
   1102         changeTransformation |= mContentTransformationAmount != contentTransformationAmount;
   1103         mIsLastChild = isLastChild;
   1104         mContentTransformationAmount = contentTransformationAmount;
   1105         if (changeTransformation) {
   1106             updateContentTransformation();
   1107         }
   1108     }
   1109 
   1110     /**
   1111      * Set the icons to be visible of this notification.
   1112      */
   1113     public void setIconsVisible(boolean iconsVisible) {
   1114         if (iconsVisible != mIconsVisible) {
   1115             mIconsVisible = iconsVisible;
   1116             updateIconVisibilities();
   1117         }
   1118     }
   1119 
   1120     @Override
   1121     protected void onBelowSpeedBumpChanged() {
   1122         updateIconVisibilities();
   1123     }
   1124 
   1125     private void updateContentTransformation() {
   1126         float contentAlpha;
   1127         float translationY = -mContentTransformationAmount * mIconTransformContentShift;
   1128         if (mIsLastChild) {
   1129             contentAlpha = 1.0f - mContentTransformationAmount;
   1130             contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
   1131             contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
   1132             translationY *= 0.4f;
   1133         } else {
   1134             contentAlpha = 1.0f;
   1135         }
   1136         for (NotificationContentView l : mLayouts) {
   1137             l.setAlpha(contentAlpha);
   1138             l.setTranslationY(translationY);
   1139         }
   1140         if (mChildrenContainer != null) {
   1141             mChildrenContainer.setAlpha(contentAlpha);
   1142             mChildrenContainer.setTranslationY(translationY);
   1143             // TODO: handle children fade out better
   1144         }
   1145     }
   1146 
   1147     private void updateIconVisibilities() {
   1148         boolean visible = isChildInGroup()
   1149                 || (isBelowSpeedBump() && !NotificationShelf.SHOW_AMBIENT_ICONS)
   1150                 || mIconsVisible;
   1151         for (NotificationContentView l : mLayouts) {
   1152             l.setIconsVisible(visible);
   1153         }
   1154         if (mChildrenContainer != null) {
   1155             mChildrenContainer.setIconsVisible(visible);
   1156         }
   1157     }
   1158 
   1159     /**
   1160      * Get the relative top padding of a view relative to this view. This recursively walks up the
   1161      * hierarchy and does the corresponding measuring.
   1162      *
   1163      * @param view the view to the the padding for. The requested view has to be a child of this
   1164      *             notification.
   1165      * @return the toppadding
   1166      */
   1167     public int getRelativeTopPadding(View view) {
   1168         int topPadding = 0;
   1169         while (view.getParent() instanceof ViewGroup) {
   1170             topPadding += view.getTop();
   1171             view = (View) view.getParent();
   1172             if (view instanceof ExpandableNotificationRow) {
   1173                 return topPadding;
   1174             }
   1175         }
   1176         return topPadding;
   1177     }
   1178 
   1179     public float getContentTranslation() {
   1180         return mPrivateLayout.getTranslationY();
   1181     }
   1182 
   1183     public void setIsLowPriority(boolean isLowPriority) {
   1184         mIsLowPriority = isLowPriority;
   1185         mPrivateLayout.setIsLowPriority(isLowPriority);
   1186         mNotificationInflater.setIsLowPriority(mIsLowPriority);
   1187         if (mChildrenContainer != null) {
   1188             mChildrenContainer.setIsLowPriority(isLowPriority);
   1189         }
   1190     }
   1191 
   1192 
   1193     public void setLowPriorityStateUpdated(boolean lowPriorityStateUpdated) {
   1194         mLowPriorityStateUpdated = lowPriorityStateUpdated;
   1195     }
   1196 
   1197     public boolean hasLowPriorityStateUpdated() {
   1198         return mLowPriorityStateUpdated;
   1199     }
   1200 
   1201     public boolean isLowPriority() {
   1202         return mIsLowPriority;
   1203     }
   1204 
   1205     public void setUseIncreasedCollapsedHeight(boolean use) {
   1206         mUseIncreasedCollapsedHeight = use;
   1207         mNotificationInflater.setUsesIncreasedHeight(use);
   1208     }
   1209 
   1210     public void setUseIncreasedHeadsUpHeight(boolean use) {
   1211         mUseIncreasedHeadsUpHeight = use;
   1212         mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
   1213     }
   1214 
   1215     public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
   1216         mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
   1217     }
   1218 
   1219     public void setInflationCallback(InflationCallback callback) {
   1220         mNotificationInflater.setInflationCallback(callback);
   1221     }
   1222 
   1223     public void setNeedsRedaction(boolean needsRedaction) {
   1224         mNotificationInflater.setRedactAmbient(needsRedaction);
   1225     }
   1226 
   1227     @VisibleForTesting
   1228     public NotificationInflater getNotificationInflater() {
   1229         return mNotificationInflater;
   1230     }
   1231 
   1232     public int getNotificationColorAmbient() {
   1233         return mNotificationColorAmbient;
   1234     }
   1235 
   1236     public interface ExpansionLogger {
   1237         void logNotificationExpansion(String key, boolean userAction, boolean expanded);
   1238     }
   1239 
   1240     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
   1241         super(context, attrs);
   1242         mFalsingManager = FalsingManager.getInstance(context);
   1243         mNotificationInflater = new NotificationInflater(this);
   1244         mMenuRow = new NotificationMenuRow(mContext);
   1245         initDimens();
   1246     }
   1247 
   1248     private void initDimens() {
   1249         mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
   1250         mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
   1251         mNotificationMinHeightLarge = getFontScaledHeight(
   1252                 R.dimen.notification_min_height_increased);
   1253         mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
   1254         mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
   1255         mMaxHeadsUpHeightLegacy = getFontScaledHeight(
   1256                 R.dimen.notification_max_heads_up_height_legacy);
   1257         mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
   1258         mMaxHeadsUpHeightIncreased = getFontScaledHeight(
   1259                 R.dimen.notification_max_heads_up_height_increased);
   1260 
   1261         Resources res = getResources();
   1262         mIncreasedPaddingBetweenElements = res.getDimensionPixelSize(
   1263                 R.dimen.notification_divider_height_increased);
   1264         mIconTransformContentShiftNoIcon = res.getDimensionPixelSize(
   1265                 R.dimen.notification_icon_transform_content_shift);
   1266         mEnableNonGroupedNotificationExpand =
   1267                 res.getBoolean(R.bool.config_enableNonGroupedNotificationExpand);
   1268         mShowGroupBackgroundWhenExpanded =
   1269                 res.getBoolean(R.bool.config_showGroupNotificationBgWhenExpanded);
   1270     }
   1271 
   1272     /**
   1273      * @param dimenId the dimen to look up
   1274      * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
   1275      */
   1276     private int getFontScaledHeight(int dimenId) {
   1277         int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
   1278         float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
   1279                 getResources().getDisplayMetrics().density);
   1280         return (int) (dimensionPixelSize * factor);
   1281     }
   1282 
   1283     /**
   1284      * Resets this view so it can be re-used for an updated notification.
   1285      */
   1286     public void reset() {
   1287         mShowingPublicInitialized = false;
   1288         onHeightReset();
   1289         requestLayout();
   1290     }
   1291 
   1292     @Override
   1293     protected void onFinishInflate() {
   1294         super.onFinishInflate();
   1295         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
   1296         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
   1297         mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
   1298 
   1299         for (NotificationContentView l : mLayouts) {
   1300             l.setExpandClickListener(mExpandClickListener);
   1301             l.setContainingNotification(this);
   1302         }
   1303         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
   1304         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
   1305             @Override
   1306             public void onInflate(ViewStub stub, View inflated) {
   1307                 mGuts = (NotificationGuts) inflated;
   1308                 mGuts.setClipTopAmount(getClipTopAmount());
   1309                 mGuts.setActualHeight(getActualHeight());
   1310                 mGutsStub = null;
   1311             }
   1312         });
   1313         mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
   1314         mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
   1315 
   1316             @Override
   1317             public void onInflate(ViewStub stub, View inflated) {
   1318                 mChildrenContainer = (NotificationChildrenContainer) inflated;
   1319                 mChildrenContainer.setIsLowPriority(mIsLowPriority);
   1320                 mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
   1321                 mChildrenContainer.onNotificationUpdated();
   1322 
   1323                 if (mShouldTranslateContents) {
   1324                     mTranslateableViews.add(mChildrenContainer);
   1325                 }
   1326             }
   1327         });
   1328 
   1329         if (mShouldTranslateContents) {
   1330             // Add the views that we translate to reveal the menu
   1331             mTranslateableViews = new ArrayList<>();
   1332             for (int i = 0; i < getChildCount(); i++) {
   1333                 mTranslateableViews.add(getChildAt(i));
   1334             }
   1335             // Remove views that don't translate
   1336             mTranslateableViews.remove(mChildrenContainerStub);
   1337             mTranslateableViews.remove(mGutsStub);
   1338         }
   1339     }
   1340 
   1341     public void resetTranslation() {
   1342         if (mTranslateAnim != null) {
   1343             mTranslateAnim.cancel();
   1344         }
   1345 
   1346         if (!mShouldTranslateContents) {
   1347             setTranslationX(0);
   1348         } else if (mTranslateableViews != null) {
   1349             for (int i = 0; i < mTranslateableViews.size(); i++) {
   1350                 mTranslateableViews.get(i).setTranslationX(0);
   1351             }
   1352             invalidateOutline();
   1353         }
   1354 
   1355         mMenuRow.resetMenu();
   1356     }
   1357 
   1358     public void animateTranslateNotification(final float leftTarget) {
   1359         if (mTranslateAnim != null) {
   1360             mTranslateAnim.cancel();
   1361         }
   1362         mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */);
   1363         if (mTranslateAnim != null) {
   1364             mTranslateAnim.start();
   1365         }
   1366     }
   1367 
   1368     @Override
   1369     public void setTranslation(float translationX) {
   1370         if (areGutsExposed()) {
   1371             // Don't translate if guts are showing.
   1372             return;
   1373         }
   1374         if (!mShouldTranslateContents) {
   1375             setTranslationX(translationX);
   1376         } else if (mTranslateableViews != null) {
   1377             // Translate the group of views
   1378             for (int i = 0; i < mTranslateableViews.size(); i++) {
   1379                 if (mTranslateableViews.get(i) != null) {
   1380                     mTranslateableViews.get(i).setTranslationX(translationX);
   1381                 }
   1382             }
   1383             invalidateOutline();
   1384         }
   1385         if (mMenuRow.getMenuView() != null) {
   1386             mMenuRow.onTranslationUpdate(translationX);
   1387         }
   1388     }
   1389 
   1390     @Override
   1391     public float getTranslation() {
   1392         if (!mShouldTranslateContents) {
   1393             return getTranslationX();
   1394         }
   1395 
   1396         if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
   1397             // All of the views in the list should have same translation, just use first one.
   1398             return mTranslateableViews.get(0).getTranslationX();
   1399         }
   1400 
   1401         return 0;
   1402     }
   1403 
   1404     public Animator getTranslateViewAnimator(final float leftTarget,
   1405             AnimatorUpdateListener listener) {
   1406         if (mTranslateAnim != null) {
   1407             mTranslateAnim.cancel();
   1408         }
   1409         if (areGutsExposed()) {
   1410             // No translation if guts are exposed.
   1411             return null;
   1412         }
   1413         final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT,
   1414                 leftTarget);
   1415         if (listener != null) {
   1416             translateAnim.addUpdateListener(listener);
   1417         }
   1418         translateAnim.addListener(new AnimatorListenerAdapter() {
   1419             boolean cancelled = false;
   1420 
   1421             @Override
   1422             public void onAnimationCancel(Animator anim) {
   1423                 cancelled = true;
   1424             }
   1425 
   1426             @Override
   1427             public void onAnimationEnd(Animator anim) {
   1428                 if (!cancelled && leftTarget == 0) {
   1429                     mMenuRow.resetMenu();
   1430                     mTranslateAnim = null;
   1431                 }
   1432             }
   1433         });
   1434         mTranslateAnim = translateAnim;
   1435         return translateAnim;
   1436     }
   1437 
   1438     public void inflateGuts() {
   1439         if (mGuts == null) {
   1440             mGutsStub.inflate();
   1441         }
   1442     }
   1443 
   1444     private void updateChildrenVisibility() {
   1445         mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
   1446                 : INVISIBLE);
   1447         if (mChildrenContainer != null) {
   1448             mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
   1449                     : INVISIBLE);
   1450         }
   1451         // The limits might have changed if the view suddenly became a group or vice versa
   1452         updateLimits();
   1453     }
   1454 
   1455     @Override
   1456     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
   1457         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
   1458             // Add a record for the entire layout since its content is somehow small.
   1459             // The event comes from a leaf view that is interacted with.
   1460             AccessibilityEvent record = AccessibilityEvent.obtain();
   1461             onInitializeAccessibilityEvent(record);
   1462             dispatchPopulateAccessibilityEvent(record);
   1463             event.appendRecord(record);
   1464             return true;
   1465         }
   1466         return false;
   1467     }
   1468 
   1469     @Override
   1470     public void setDark(boolean dark, boolean fade, long delay) {
   1471         super.setDark(dark, fade, delay);
   1472         mDark = dark;
   1473         if (!mIsHeadsUp) {
   1474             // Only fade the showing view of the pulsing notification.
   1475             fade = false;
   1476         }
   1477         final NotificationContentView showing = getShowingLayout();
   1478         if (showing != null) {
   1479             showing.setDark(dark, fade, delay);
   1480         }
   1481         if (mIsSummaryWithChildren) {
   1482             mChildrenContainer.setDark(dark, fade, delay);
   1483         }
   1484         updateShelfIconColor();
   1485     }
   1486 
   1487     /**
   1488      * Tap sounds should not be played when we're unlocking.
   1489      * Doing so would cause audio collision and the system would feel unpolished.
   1490      */
   1491     @Override
   1492     public boolean isSoundEffectsEnabled() {
   1493         final boolean mute = mDark && mSecureStateProvider != null &&
   1494                 !mSecureStateProvider.getAsBoolean();
   1495         return !mute && super.isSoundEffectsEnabled();
   1496     }
   1497 
   1498     public boolean isExpandable() {
   1499         if (mIsSummaryWithChildren && !mShowingPublic) {
   1500             return !mChildrenExpanded;
   1501         }
   1502         return mEnableNonGroupedNotificationExpand && mExpandable;
   1503     }
   1504 
   1505     public void setExpandable(boolean expandable) {
   1506         mExpandable = expandable;
   1507         mPrivateLayout.updateExpandButtons(isExpandable());
   1508     }
   1509 
   1510     @Override
   1511     public void setClipToActualHeight(boolean clipToActualHeight) {
   1512         super.setClipToActualHeight(clipToActualHeight || isUserLocked());
   1513         getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked());
   1514     }
   1515 
   1516     /**
   1517      * @return whether the user has changed the expansion state
   1518      */
   1519     public boolean hasUserChangedExpansion() {
   1520         return mHasUserChangedExpansion;
   1521     }
   1522 
   1523     public boolean isUserExpanded() {
   1524         return mUserExpanded;
   1525     }
   1526 
   1527     /**
   1528      * Set this notification to be expanded by the user
   1529      *
   1530      * @param userExpanded whether the user wants this notification to be expanded
   1531      */
   1532     public void setUserExpanded(boolean userExpanded) {
   1533         setUserExpanded(userExpanded, false /* allowChildExpansion */);
   1534     }
   1535 
   1536     /**
   1537      * Set this notification to be expanded by the user
   1538      *
   1539      * @param userExpanded whether the user wants this notification to be expanded
   1540      * @param allowChildExpansion whether a call to this method allows expanding children
   1541      */
   1542     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
   1543         mFalsingManager.setNotificationExpanded();
   1544         if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion
   1545                 && !mChildrenContainer.showingAsLowPriority()) {
   1546             final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
   1547             mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded);
   1548             onExpansionChanged(true /* userAction */, wasExpanded);
   1549             return;
   1550         }
   1551         if (userExpanded && !mExpandable) return;
   1552         final boolean wasExpanded = isExpanded();
   1553         mHasUserChangedExpansion = true;
   1554         mUserExpanded = userExpanded;
   1555         onExpansionChanged(true /* userAction */, wasExpanded);
   1556         if (!wasExpanded && isExpanded()
   1557                 && getActualHeight() != getIntrinsicHeight()) {
   1558             notifyHeightChanged(true /* needsAnimation */);
   1559         }
   1560     }
   1561 
   1562     public void resetUserExpansion() {
   1563         boolean changed = mUserExpanded;
   1564         mHasUserChangedExpansion = false;
   1565         mUserExpanded = false;
   1566         if (changed && mIsSummaryWithChildren) {
   1567             mChildrenContainer.onExpansionChanged();
   1568         }
   1569         updateShelfIconColor();
   1570     }
   1571 
   1572     public boolean isUserLocked() {
   1573         return mUserLocked && !mForceUnlocked;
   1574     }
   1575 
   1576     public void setUserLocked(boolean userLocked) {
   1577         mUserLocked = userLocked;
   1578         mPrivateLayout.setUserExpanding(userLocked);
   1579         // This is intentionally not guarded with mIsSummaryWithChildren since we might have had
   1580         // children but not anymore.
   1581         if (mChildrenContainer != null) {
   1582             mChildrenContainer.setUserLocked(userLocked);
   1583             if (mIsSummaryWithChildren && (userLocked || !isGroupExpanded())) {
   1584                 updateBackgroundForGroupState();
   1585             }
   1586         }
   1587     }
   1588 
   1589     /**
   1590      * @return has the system set this notification to be expanded
   1591      */
   1592     public boolean isSystemExpanded() {
   1593         return mIsSystemExpanded;
   1594     }
   1595 
   1596     /**
   1597      * Set this notification to be expanded by the system.
   1598      *
   1599      * @param expand whether the system wants this notification to be expanded.
   1600      */
   1601     public void setSystemExpanded(boolean expand) {
   1602         if (expand != mIsSystemExpanded) {
   1603             final boolean wasExpanded = isExpanded();
   1604             mIsSystemExpanded = expand;
   1605             notifyHeightChanged(false /* needsAnimation */);
   1606             onExpansionChanged(false /* userAction */, wasExpanded);
   1607             if (mIsSummaryWithChildren) {
   1608                 mChildrenContainer.updateGroupOverflow();
   1609             }
   1610         }
   1611     }
   1612 
   1613     /**
   1614      * @param onKeyguard whether to prevent notification expansion
   1615      */
   1616     public void setOnKeyguard(boolean onKeyguard) {
   1617         if (onKeyguard != mOnKeyguard) {
   1618             boolean wasAboveShelf = isAboveShelf();
   1619             final boolean wasExpanded = isExpanded();
   1620             mOnKeyguard = onKeyguard;
   1621             onExpansionChanged(false /* userAction */, wasExpanded);
   1622             if (wasExpanded != isExpanded()) {
   1623                 if (mIsSummaryWithChildren) {
   1624                     mChildrenContainer.updateGroupOverflow();
   1625                 }
   1626                 notifyHeightChanged(false /* needsAnimation */);
   1627             }
   1628             if (isAboveShelf() != wasAboveShelf) {
   1629                 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
   1630             }
   1631         }
   1632     }
   1633 
   1634     /**
   1635      * @return Can the underlying notification be cleared? This can be different from whether the
   1636      *         notification can be dismissed in case notifications are sensitive on the lockscreen.
   1637      * @see #canViewBeDismissed()
   1638      */
   1639     public boolean isClearable() {
   1640         if (mStatusBarNotification == null || !mStatusBarNotification.isClearable()) {
   1641             return false;
   1642         }
   1643         if (mIsSummaryWithChildren) {
   1644             List<ExpandableNotificationRow> notificationChildren =
   1645                     mChildrenContainer.getNotificationChildren();
   1646             for (int i = 0; i < notificationChildren.size(); i++) {
   1647                 ExpandableNotificationRow child = notificationChildren.get(i);
   1648                 if (!child.isClearable()) {
   1649                     return false;
   1650                 }
   1651             }
   1652         }
   1653         return true;
   1654     }
   1655 
   1656     @Override
   1657     public int getIntrinsicHeight() {
   1658         if (isUserLocked()) {
   1659             return getActualHeight();
   1660         }
   1661         if (mGuts != null && mGuts.isExposed()) {
   1662             return mGuts.getIntrinsicHeight();
   1663         } else if ((isChildInGroup() && !isGroupExpanded())) {
   1664             return mPrivateLayout.getMinHeight();
   1665         } else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
   1666             return getMinHeight();
   1667         } else if (mIsSummaryWithChildren && (!mOnKeyguard || mShowAmbient)) {
   1668             return mChildrenContainer.getIntrinsicHeight();
   1669         } else if (isHeadsUpAllowed() && (mIsHeadsUp || mHeadsupDisappearRunning)) {
   1670             if (isPinned() || mHeadsupDisappearRunning) {
   1671                 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
   1672             } else if (isExpanded()) {
   1673                 return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
   1674             } else {
   1675                 return Math.max(getCollapsedHeight(), mHeadsUpHeight);
   1676             }
   1677         } else if (isExpanded()) {
   1678             return getMaxExpandHeight();
   1679         } else {
   1680             return getCollapsedHeight();
   1681         }
   1682     }
   1683 
   1684     private boolean isHeadsUpAllowed() {
   1685         return !mOnKeyguard && !mShowAmbient;
   1686     }
   1687 
   1688     @Override
   1689     public boolean isGroupExpanded() {
   1690         return mGroupManager.isGroupExpanded(mStatusBarNotification);
   1691     }
   1692 
   1693     private void onChildrenCountChanged() {
   1694         mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS
   1695                 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
   1696         if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
   1697             mChildrenContainer.recreateNotificationHeader(mExpandClickListener
   1698             );
   1699         }
   1700         getShowingLayout().updateBackgroundColor(false /* animate */);
   1701         mPrivateLayout.updateExpandButtons(isExpandable());
   1702         updateChildrenHeaderAppearance();
   1703         updateChildrenVisibility();
   1704     }
   1705 
   1706     public void updateChildrenHeaderAppearance() {
   1707         if (mIsSummaryWithChildren) {
   1708             mChildrenContainer.updateChildrenHeaderAppearance();
   1709         }
   1710     }
   1711 
   1712     /**
   1713      * Check whether the view state is currently expanded. This is given by the system in {@link
   1714      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
   1715      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
   1716      * view can differ from this state, if layout params are modified from outside.
   1717      *
   1718      * @return whether the view state is currently expanded.
   1719      */
   1720     public boolean isExpanded() {
   1721         return isExpanded(false /* allowOnKeyguard */);
   1722     }
   1723 
   1724     public boolean isExpanded(boolean allowOnKeyguard) {
   1725         return (!mOnKeyguard || allowOnKeyguard)
   1726                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
   1727                 || isUserExpanded());
   1728     }
   1729 
   1730     private boolean isSystemChildExpanded() {
   1731         return mIsSystemChildExpanded;
   1732     }
   1733 
   1734     public void setSystemChildExpanded(boolean expanded) {
   1735         mIsSystemChildExpanded = expanded;
   1736     }
   1737 
   1738     public void setLayoutListener(LayoutListener listener) {
   1739         mLayoutListener = listener;
   1740     }
   1741 
   1742     public void removeListener() {
   1743         mLayoutListener = null;
   1744     }
   1745 
   1746     @Override
   1747     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   1748         super.onLayout(changed, left, top, right, bottom);
   1749         updateMaxHeights();
   1750         if (mMenuRow.getMenuView() != null) {
   1751             mMenuRow.onHeightUpdate();
   1752         }
   1753         updateContentShiftHeight();
   1754         if (mLayoutListener != null) {
   1755             mLayoutListener.onLayout();
   1756         }
   1757     }
   1758 
   1759     /**
   1760      * Updates the content shift height such that the header is completely hidden when coming from
   1761      * the top.
   1762      */
   1763     private void updateContentShiftHeight() {
   1764         NotificationHeaderView notificationHeader = getVisibleNotificationHeader();
   1765         if (notificationHeader != null) {
   1766             CachingIconView icon = notificationHeader.getIcon();
   1767             mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight();
   1768         } else {
   1769             mIconTransformContentShift = mIconTransformContentShiftNoIcon;
   1770         }
   1771     }
   1772 
   1773     private void updateMaxHeights() {
   1774         int intrinsicBefore = getIntrinsicHeight();
   1775         View expandedChild = mPrivateLayout.getExpandedChild();
   1776         if (expandedChild == null) {
   1777             expandedChild = mPrivateLayout.getContractedChild();
   1778         }
   1779         mMaxExpandHeight = expandedChild.getHeight();
   1780         View headsUpChild = mPrivateLayout.getHeadsUpChild();
   1781         if (headsUpChild == null) {
   1782             headsUpChild = mPrivateLayout.getContractedChild();
   1783         }
   1784         mHeadsUpHeight = headsUpChild.getHeight();
   1785         if (intrinsicBefore != getIntrinsicHeight()) {
   1786             notifyHeightChanged(true  /* needsAnimation */);
   1787         }
   1788     }
   1789 
   1790     @Override
   1791     public void notifyHeightChanged(boolean needsAnimation) {
   1792         super.notifyHeightChanged(needsAnimation);
   1793         getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
   1794     }
   1795 
   1796     public void setSensitive(boolean sensitive, boolean hideSensitive) {
   1797         mSensitive = sensitive;
   1798         mSensitiveHiddenInGeneral = hideSensitive;
   1799     }
   1800 
   1801     @Override
   1802     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
   1803         mHideSensitiveForIntrinsicHeight = hideSensitive;
   1804         if (mIsSummaryWithChildren) {
   1805             List<ExpandableNotificationRow> notificationChildren =
   1806                     mChildrenContainer.getNotificationChildren();
   1807             for (int i = 0; i < notificationChildren.size(); i++) {
   1808                 ExpandableNotificationRow child = notificationChildren.get(i);
   1809                 child.setHideSensitiveForIntrinsicHeight(hideSensitive);
   1810             }
   1811         }
   1812     }
   1813 
   1814     @Override
   1815     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
   1816             long duration) {
   1817         boolean oldShowingPublic = mShowingPublic;
   1818         mShowingPublic = mSensitive && hideSensitive;
   1819         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
   1820             return;
   1821         }
   1822 
   1823         // bail out if no public version
   1824         if (mPublicLayout.getChildCount() == 0) return;
   1825 
   1826         if (!animated) {
   1827             mPublicLayout.animate().cancel();
   1828             mPrivateLayout.animate().cancel();
   1829             if (mChildrenContainer != null) {
   1830                 mChildrenContainer.animate().cancel();
   1831                 mChildrenContainer.setAlpha(1f);
   1832             }
   1833             mPublicLayout.setAlpha(1f);
   1834             mPrivateLayout.setAlpha(1f);
   1835             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
   1836             updateChildrenVisibility();
   1837         } else {
   1838             animateShowingPublic(delay, duration);
   1839         }
   1840         NotificationContentView showingLayout = getShowingLayout();
   1841         showingLayout.updateBackgroundColor(animated);
   1842         mPrivateLayout.updateExpandButtons(isExpandable());
   1843         updateShelfIconColor();
   1844         showingLayout.setDark(isDark(), false /* animate */, 0 /* delay */);
   1845         mShowingPublicInitialized = true;
   1846     }
   1847 
   1848     private void animateShowingPublic(long delay, long duration) {
   1849         View[] privateViews = mIsSummaryWithChildren
   1850                 ? new View[] {mChildrenContainer}
   1851                 : new View[] {mPrivateLayout};
   1852         View[] publicViews = new View[] {mPublicLayout};
   1853         View[] hiddenChildren = mShowingPublic ? privateViews : publicViews;
   1854         View[] shownChildren = mShowingPublic ? publicViews : privateViews;
   1855         for (final View hiddenView : hiddenChildren) {
   1856             hiddenView.setVisibility(View.VISIBLE);
   1857             hiddenView.animate().cancel();
   1858             hiddenView.animate()
   1859                     .alpha(0f)
   1860                     .setStartDelay(delay)
   1861                     .setDuration(duration)
   1862                     .withEndAction(new Runnable() {
   1863                         @Override
   1864                         public void run() {
   1865                             hiddenView.setVisibility(View.INVISIBLE);
   1866                         }
   1867                     });
   1868         }
   1869         for (View showView : shownChildren) {
   1870             showView.setVisibility(View.VISIBLE);
   1871             showView.setAlpha(0f);
   1872             showView.animate().cancel();
   1873             showView.animate()
   1874                     .alpha(1f)
   1875                     .setStartDelay(delay)
   1876                     .setDuration(duration);
   1877         }
   1878     }
   1879 
   1880     @Override
   1881     public boolean mustStayOnScreen() {
   1882         return mIsHeadsUp;
   1883     }
   1884 
   1885     /**
   1886      * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
   1887      *         otherwise some state might not be updated. To request about the general clearability
   1888      *         see {@link #isClearable()}.
   1889      */
   1890     public boolean canViewBeDismissed() {
   1891         return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral);
   1892     }
   1893 
   1894     public void makeActionsVisibile() {
   1895         setUserExpanded(true, true);
   1896         if (isChildInGroup()) {
   1897             mGroupManager.setGroupExpanded(mStatusBarNotification, true);
   1898         }
   1899         notifyHeightChanged(false /* needsAnimation */);
   1900     }
   1901 
   1902     public void setChildrenExpanded(boolean expanded, boolean animate) {
   1903         mChildrenExpanded = expanded;
   1904         if (mChildrenContainer != null) {
   1905             mChildrenContainer.setChildrenExpanded(expanded);
   1906         }
   1907         updateBackgroundForGroupState();
   1908         updateClickAndFocus();
   1909     }
   1910 
   1911     public static void applyTint(View v, int color) {
   1912         int alpha;
   1913         if (color != 0) {
   1914             alpha = COLORED_DIVIDER_ALPHA;
   1915         } else {
   1916             color = 0xff000000;
   1917             alpha = DEFAULT_DIVIDER_ALPHA;
   1918         }
   1919         if (v.getBackground() instanceof ColorDrawable) {
   1920             ColorDrawable background = (ColorDrawable) v.getBackground();
   1921             background.mutate();
   1922             background.setColor(color);
   1923             background.setAlpha(alpha);
   1924         }
   1925     }
   1926 
   1927     public int getMaxExpandHeight() {
   1928         return mMaxExpandHeight;
   1929     }
   1930 
   1931     public boolean areGutsExposed() {
   1932         return (mGuts != null && mGuts.isExposed());
   1933     }
   1934 
   1935     @Override
   1936     public boolean isContentExpandable() {
   1937         if (mIsSummaryWithChildren && !mShowingPublic) {
   1938             return true;
   1939         }
   1940         NotificationContentView showingLayout = getShowingLayout();
   1941         return showingLayout.isContentExpandable();
   1942     }
   1943 
   1944     @Override
   1945     protected View getContentView() {
   1946         if (mIsSummaryWithChildren && !mShowingPublic) {
   1947             return mChildrenContainer;
   1948         }
   1949         return getShowingLayout();
   1950     }
   1951 
   1952     @Override
   1953     protected void onAppearAnimationFinished(boolean wasAppearing) {
   1954         super.onAppearAnimationFinished(wasAppearing);
   1955         if (wasAppearing) {
   1956             // During the animation the visible view might have changed, so let's make sure all
   1957             // alphas are reset
   1958             if (mChildrenContainer != null) {
   1959                 mChildrenContainer.setAlpha(1.0f);
   1960                 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
   1961             }
   1962             for (NotificationContentView l : mLayouts) {
   1963                 l.setAlpha(1.0f);
   1964                 l.setLayerType(LAYER_TYPE_NONE, null);
   1965             }
   1966         }
   1967     }
   1968 
   1969     @Override
   1970     public int getExtraBottomPadding() {
   1971         if (mIsSummaryWithChildren && isGroupExpanded()) {
   1972             return mIncreasedPaddingBetweenElements;
   1973         }
   1974         return 0;
   1975     }
   1976 
   1977     @Override
   1978     public void setActualHeight(int height, boolean notifyListeners) {
   1979         boolean changed = height != getActualHeight();
   1980         super.setActualHeight(height, notifyListeners);
   1981         if (changed && isRemoved()) {
   1982             // TODO: remove this once we found the gfx bug for this.
   1983             // This is a hack since a removed view sometimes would just stay blank. it occured
   1984             // when sending yourself a message and then clicking on it.
   1985             ViewGroup parent = (ViewGroup) getParent();
   1986             if (parent != null) {
   1987                 parent.invalidate();
   1988             }
   1989         }
   1990         if (mGuts != null && mGuts.isExposed()) {
   1991             mGuts.setActualHeight(height);
   1992             return;
   1993         }
   1994         int contentHeight = Math.max(getMinHeight(), height);
   1995         for (NotificationContentView l : mLayouts) {
   1996             l.setContentHeight(contentHeight);
   1997         }
   1998         if (mIsSummaryWithChildren) {
   1999             mChildrenContainer.setActualHeight(height);
   2000         }
   2001         if (mGuts != null) {
   2002             mGuts.setActualHeight(height);
   2003         }
   2004         if (mMenuRow.getMenuView() != null) {
   2005             mMenuRow.onHeightUpdate();
   2006         }
   2007     }
   2008 
   2009     @Override
   2010     public int getMaxContentHeight() {
   2011         if (mIsSummaryWithChildren && !mShowingPublic) {
   2012             return mChildrenContainer.getMaxContentHeight();
   2013         }
   2014         NotificationContentView showingLayout = getShowingLayout();
   2015         return showingLayout.getMaxHeight();
   2016     }
   2017 
   2018     @Override
   2019     public int getMinHeight(boolean ignoreTemporaryStates) {
   2020         if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) {
   2021             return mGuts.getIntrinsicHeight();
   2022         } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp
   2023                 && mHeadsUpManager.isTrackingHeadsUp()) {
   2024                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
   2025         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
   2026             return mChildrenContainer.getMinHeight();
   2027         } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) {
   2028             return mHeadsUpHeight;
   2029         }
   2030         NotificationContentView showingLayout = getShowingLayout();
   2031         return showingLayout.getMinHeight();
   2032     }
   2033 
   2034     @Override
   2035     public int getCollapsedHeight() {
   2036         if (mIsSummaryWithChildren && !mShowingPublic) {
   2037             return mChildrenContainer.getCollapsedHeight();
   2038         }
   2039         return getMinHeight();
   2040     }
   2041 
   2042     @Override
   2043     public void setClipTopAmount(int clipTopAmount) {
   2044         super.setClipTopAmount(clipTopAmount);
   2045         for (NotificationContentView l : mLayouts) {
   2046             l.setClipTopAmount(clipTopAmount);
   2047         }
   2048         if (mGuts != null) {
   2049             mGuts.setClipTopAmount(clipTopAmount);
   2050         }
   2051     }
   2052 
   2053     @Override
   2054     public void setClipBottomAmount(int clipBottomAmount) {
   2055         if (clipBottomAmount != mClipBottomAmount) {
   2056             super.setClipBottomAmount(clipBottomAmount);
   2057             for (NotificationContentView l : mLayouts) {
   2058                 l.setClipBottomAmount(clipBottomAmount);
   2059             }
   2060             if (mGuts != null) {
   2061                 mGuts.setClipBottomAmount(clipBottomAmount);
   2062             }
   2063         }
   2064         if (mChildrenContainer != null) {
   2065             // We have to update this even if it hasn't changed, since the children locations can
   2066             // have changed
   2067             mChildrenContainer.setClipBottomAmount(clipBottomAmount);
   2068         }
   2069     }
   2070 
   2071     public boolean isMaxExpandHeightInitialized() {
   2072         return mMaxExpandHeight != 0;
   2073     }
   2074 
   2075     public NotificationContentView getShowingLayout() {
   2076         return mShowingPublic ? mPublicLayout : mPrivateLayout;
   2077     }
   2078 
   2079     public void setLegacy(boolean legacy) {
   2080         for (NotificationContentView l : mLayouts) {
   2081             l.setLegacy(legacy);
   2082         }
   2083     }
   2084 
   2085     @Override
   2086     protected void updateBackgroundTint() {
   2087         super.updateBackgroundTint();
   2088         updateBackgroundForGroupState();
   2089         if (mIsSummaryWithChildren) {
   2090             List<ExpandableNotificationRow> notificationChildren =
   2091                     mChildrenContainer.getNotificationChildren();
   2092             for (int i = 0; i < notificationChildren.size(); i++) {
   2093                 ExpandableNotificationRow child = notificationChildren.get(i);
   2094                 child.updateBackgroundForGroupState();
   2095             }
   2096         }
   2097     }
   2098 
   2099     /**
   2100      * Called when a group has finished animating from collapsed or expanded state.
   2101      */
   2102     public void onFinishedExpansionChange() {
   2103         mGroupExpansionChanging = false;
   2104         updateBackgroundForGroupState();
   2105     }
   2106 
   2107     /**
   2108      * Updates the parent and children backgrounds in a group based on the expansion state.
   2109      */
   2110     public void updateBackgroundForGroupState() {
   2111         if (mIsSummaryWithChildren) {
   2112             // Only when the group has finished expanding do we hide its background.
   2113             mShowNoBackground = !mShowGroupBackgroundWhenExpanded && isGroupExpanded()
   2114                     && !isGroupExpansionChanging() && !isUserLocked();
   2115             mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);
   2116             List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren();
   2117             for (int i = 0; i < children.size(); i++) {
   2118                 children.get(i).updateBackgroundForGroupState();
   2119             }
   2120         } else if (isChildInGroup()) {
   2121             final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
   2122             // Only show a background if the group is expanded OR if it is expanding / collapsing
   2123             // and has a custom background color.
   2124             final boolean showBackground = isGroupExpanded()
   2125                     || ((mNotificationParent.isGroupExpansionChanging()
   2126                     || mNotificationParent.isUserLocked()) && childColor != 0);
   2127             mShowNoBackground = !showBackground;
   2128         } else {
   2129             // Only children or parents ever need no background.
   2130             mShowNoBackground = false;
   2131         }
   2132         updateOutline();
   2133         updateBackground();
   2134     }
   2135 
   2136     public int getPositionOfChild(ExpandableNotificationRow childRow) {
   2137         if (mIsSummaryWithChildren) {
   2138             return mChildrenContainer.getPositionInLinearLayout(childRow);
   2139         }
   2140         return 0;
   2141     }
   2142 
   2143     public void setExpansionLogger(ExpansionLogger logger, String key) {
   2144         mLogger = logger;
   2145         mLoggingKey = key;
   2146     }
   2147 
   2148     public void onExpandedByGesture(boolean userExpanded) {
   2149         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
   2150         if (mGroupManager.isSummaryOfGroup(getStatusBarNotification())) {
   2151             event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
   2152         }
   2153         MetricsLogger.action(mContext, event, userExpanded);
   2154     }
   2155 
   2156     @Override
   2157     public float getIncreasedPaddingAmount() {
   2158         if (mIsSummaryWithChildren) {
   2159             if (isGroupExpanded()) {
   2160                 return 1.0f;
   2161             } else if (isUserLocked()) {
   2162                 return mChildrenContainer.getIncreasedPaddingAmount();
   2163             }
   2164         } else if (isColorized() && (!mIsLowPriority || isExpanded())) {
   2165             return -1.0f;
   2166         }
   2167         return 0.0f;
   2168     }
   2169 
   2170     private boolean isColorized() {
   2171         return mIsColorized && mBgTint != NO_COLOR;
   2172     }
   2173 
   2174     @Override
   2175     protected boolean disallowSingleClick(MotionEvent event) {
   2176         float x = event.getX();
   2177         float y = event.getY();
   2178         NotificationHeaderView header = getVisibleNotificationHeader();
   2179         if (header != null && header.isInTouchRect(x - getTranslation(), y)) {
   2180             return true;
   2181         }
   2182         if ((!mIsSummaryWithChildren || mShowingPublic)
   2183                 && getShowingLayout().disallowSingleClick(x, y)) {
   2184             return true;
   2185         }
   2186         return super.disallowSingleClick(event);
   2187     }
   2188 
   2189     private void onExpansionChanged(boolean userAction, boolean wasExpanded) {
   2190         boolean nowExpanded = isExpanded();
   2191         if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) {
   2192             nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
   2193         }
   2194         if (nowExpanded != wasExpanded) {
   2195             updateShelfIconColor();
   2196             if (mLogger != null) {
   2197                 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded);
   2198             }
   2199             if (mIsSummaryWithChildren) {
   2200                 mChildrenContainer.onExpansionChanged();
   2201             }
   2202         }
   2203     }
   2204 
   2205     @Override
   2206     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
   2207         super.onInitializeAccessibilityNodeInfoInternal(info);
   2208         if (canViewBeDismissed()) {
   2209             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
   2210         }
   2211         boolean expandable = mShowingPublic;
   2212         boolean isExpanded = false;
   2213         if (!expandable) {
   2214             if (mIsSummaryWithChildren) {
   2215                 expandable = true;
   2216                 if (!mIsLowPriority || isExpanded()) {
   2217                     isExpanded = isGroupExpanded();
   2218                 }
   2219             } else {
   2220                 expandable = mPrivateLayout.isContentExpandable();
   2221                 isExpanded = isExpanded();
   2222             }
   2223         }
   2224         if (expandable) {
   2225             if (isExpanded) {
   2226                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
   2227             } else {
   2228                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
   2229             }
   2230         }
   2231     }
   2232 
   2233     @Override
   2234     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
   2235         if (super.performAccessibilityActionInternal(action, arguments)) {
   2236             return true;
   2237         }
   2238         switch (action) {
   2239             case AccessibilityNodeInfo.ACTION_DISMISS:
   2240                 NotificationStackScrollLayout.performDismiss(this, mGroupManager,
   2241                         true /* fromAccessibility */);
   2242                 return true;
   2243             case AccessibilityNodeInfo.ACTION_COLLAPSE:
   2244             case AccessibilityNodeInfo.ACTION_EXPAND:
   2245                 mExpandClickListener.onClick(this);
   2246                 return true;
   2247         }
   2248         return false;
   2249     }
   2250 
   2251     public boolean shouldRefocusOnDismiss() {
   2252         return mRefocusOnDismiss || isAccessibilityFocused();
   2253     }
   2254 
   2255     public interface OnExpandClickListener {
   2256         void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
   2257     }
   2258 
   2259     @Override
   2260     public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
   2261         return new NotificationViewState(stackScrollState);
   2262     }
   2263 
   2264     @Override
   2265     public boolean isAboveShelf() {
   2266         return !isOnKeyguard()
   2267                 && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf));
   2268     }
   2269 
   2270     public void setShowAmbient(boolean showAmbient) {
   2271         if (showAmbient != mShowAmbient) {
   2272             mShowAmbient = showAmbient;
   2273             if (mChildrenContainer != null) {
   2274                 mChildrenContainer.notifyShowAmbientChanged();
   2275             }
   2276             notifyHeightChanged(false /* needsAnimation */);
   2277         }
   2278     }
   2279 
   2280     public boolean isShowingAmbient() {
   2281         return mShowAmbient;
   2282     }
   2283 
   2284     public void setAboveShelf(boolean aboveShelf) {
   2285         boolean wasAboveShelf = isAboveShelf();
   2286         mAboveShelf = aboveShelf;
   2287         if (isAboveShelf() != wasAboveShelf) {
   2288             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
   2289         }
   2290     }
   2291 
   2292     public static class NotificationViewState extends ExpandableViewState {
   2293 
   2294         private final StackScrollState mOverallState;
   2295 
   2296 
   2297         private NotificationViewState(StackScrollState stackScrollState) {
   2298             mOverallState = stackScrollState;
   2299         }
   2300 
   2301         @Override
   2302         public void applyToView(View view) {
   2303             super.applyToView(view);
   2304             if (view instanceof ExpandableNotificationRow) {
   2305                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
   2306                 row.applyChildrenState(mOverallState);
   2307             }
   2308         }
   2309 
   2310         @Override
   2311         protected void onYTranslationAnimationFinished(View view) {
   2312             super.onYTranslationAnimationFinished(view);
   2313             if (view instanceof ExpandableNotificationRow) {
   2314                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
   2315                 if (row.isHeadsUpAnimatingAway()) {
   2316                     row.setHeadsUpAnimatingAway(false);
   2317                 }
   2318             }
   2319         }
   2320 
   2321         @Override
   2322         public void animateTo(View child, AnimationProperties properties) {
   2323             super.animateTo(child, properties);
   2324             if (child instanceof ExpandableNotificationRow) {
   2325                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
   2326                 row.startChildAnimation(mOverallState, properties);
   2327             }
   2328         }
   2329     }
   2330 
   2331     @VisibleForTesting
   2332     protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
   2333         mChildrenContainer = childrenContainer;
   2334     }
   2335 }
   2336