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