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 android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.ValueAnimator.AnimatorUpdateListener;
     23 import android.annotation.Nullable;
     24 import android.content.Context;
     25 import android.graphics.drawable.AnimatedVectorDrawable;
     26 import android.graphics.drawable.AnimationDrawable;
     27 import android.graphics.drawable.ColorDrawable;
     28 import android.graphics.drawable.Drawable;
     29 import android.os.Build;
     30 import android.os.Bundle;
     31 import android.service.notification.StatusBarNotification;
     32 import android.util.AttributeSet;
     33 import android.util.FloatProperty;
     34 import android.util.Property;
     35 import android.view.LayoutInflater;
     36 import android.view.MotionEvent;
     37 import android.view.NotificationHeaderView;
     38 import android.view.View;
     39 import android.view.ViewStub;
     40 import android.view.accessibility.AccessibilityEvent;
     41 import android.view.accessibility.AccessibilityNodeInfo;
     42 import android.widget.Chronometer;
     43 import android.widget.ImageView;
     44 
     45 import com.android.internal.logging.MetricsLogger;
     46 import com.android.internal.logging.MetricsProto.MetricsEvent;
     47 import com.android.internal.util.NotificationColorUtil;
     48 import com.android.systemui.R;
     49 import com.android.systemui.classifier.FalsingManager;
     50 import com.android.systemui.statusbar.notification.HybridNotificationView;
     51 import com.android.systemui.statusbar.phone.NotificationGroupManager;
     52 import com.android.systemui.statusbar.policy.HeadsUpManager;
     53 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
     54 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
     55 import com.android.systemui.statusbar.stack.StackScrollState;
     56 import com.android.systemui.statusbar.stack.StackStateAnimator;
     57 import com.android.systemui.statusbar.stack.StackViewState;
     58 
     59 import java.util.ArrayList;
     60 import java.util.List;
     61 
     62 public class ExpandableNotificationRow extends ActivatableNotificationView {
     63 
     64     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
     65     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
     66     private int mNotificationMinHeightLegacy;
     67     private int mMaxHeadsUpHeightLegacy;
     68     private int mMaxHeadsUpHeight;
     69     private int mNotificationMinHeight;
     70     private int mNotificationMaxHeight;
     71     private int mIncreasedPaddingBetweenElements;
     72 
     73     /** Does this row contain layouts that can adapt to row expansion */
     74     private boolean mExpandable;
     75     /** Has the user actively changed the expansion state of this row */
     76     private boolean mHasUserChangedExpansion;
     77     /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
     78     private boolean mUserExpanded;
     79 
     80     /**
     81      * Has this notification been expanded while it was pinned
     82      */
     83     private boolean mExpandedWhenPinned;
     84     /** Is the user touching this row */
     85     private boolean mUserLocked;
     86     /** Are we showing the "public" version */
     87     private boolean mShowingPublic;
     88     private boolean mSensitive;
     89     private boolean mSensitiveHiddenInGeneral;
     90     private boolean mShowingPublicInitialized;
     91     private boolean mHideSensitiveForIntrinsicHeight;
     92 
     93     /**
     94      * Is this notification expanded by the system. The expansion state can be overridden by the
     95      * user expansion.
     96      */
     97     private boolean mIsSystemExpanded;
     98 
     99     /**
    100      * Whether the notification is on the keyguard and the expansion is disabled.
    101      */
    102     private boolean mOnKeyguard;
    103 
    104     private Animator mTranslateAnim;
    105     private ArrayList<View> mTranslateableViews;
    106     private NotificationContentView mPublicLayout;
    107     private NotificationContentView mPrivateLayout;
    108     private int mMaxExpandHeight;
    109     private int mHeadsUpHeight;
    110     private View mVetoButton;
    111     private int mNotificationColor;
    112     private ExpansionLogger mLogger;
    113     private String mLoggingKey;
    114     private NotificationSettingsIconRow mSettingsIconRow;
    115     private NotificationGuts mGuts;
    116     private NotificationData.Entry mEntry;
    117     private StatusBarNotification mStatusBarNotification;
    118     private String mAppName;
    119     private boolean mIsHeadsUp;
    120     private boolean mLastChronometerRunning = true;
    121     private ViewStub mChildrenContainerStub;
    122     private NotificationGroupManager mGroupManager;
    123     private boolean mChildrenExpanded;
    124     private boolean mIsSummaryWithChildren;
    125     private NotificationChildrenContainer mChildrenContainer;
    126     private ViewStub mSettingsIconRowStub;
    127     private ViewStub mGutsStub;
    128     private boolean mIsSystemChildExpanded;
    129     private boolean mIsPinned;
    130     private FalsingManager mFalsingManager;
    131     private HeadsUpManager mHeadsUpManager;
    132 
    133     private boolean mJustClicked;
    134     private boolean mIconAnimationRunning;
    135     private boolean mShowNoBackground;
    136     private ExpandableNotificationRow mNotificationParent;
    137     private OnExpandClickListener mOnExpandClickListener;
    138     private boolean mGroupExpansionChanging;
    139 
    140     private OnClickListener mExpandClickListener = new OnClickListener() {
    141         @Override
    142         public void onClick(View v) {
    143             if (!mShowingPublic && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
    144                 mGroupExpansionChanging = true;
    145                 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
    146                 boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification);
    147                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
    148                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER,
    149                         nowExpanded);
    150                 logExpansionEvent(true /* userAction */, wasExpanded);
    151             } else {
    152                 if (v.isAccessibilityFocused()) {
    153                     mPrivateLayout.setFocusOnVisibilityChange();
    154                 }
    155                 boolean nowExpanded;
    156                 if (isPinned()) {
    157                     nowExpanded = !mExpandedWhenPinned;
    158                     mExpandedWhenPinned = nowExpanded;
    159                 } else {
    160                     nowExpanded = !isExpanded();
    161                     setUserExpanded(nowExpanded);
    162                 }
    163                 notifyHeightChanged(true);
    164                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
    165                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_EXPANDER,
    166                         nowExpanded);
    167             }
    168         }
    169     };
    170     private boolean mForceUnlocked;
    171     private boolean mDismissed;
    172     private boolean mKeepInParent;
    173     private boolean mRemoved;
    174     private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
    175             new FloatProperty<ExpandableNotificationRow>("translate") {
    176                 @Override
    177                 public void setValue(ExpandableNotificationRow object, float value) {
    178                     object.setTranslation(value);
    179                 }
    180 
    181                 @Override
    182                 public Float get(ExpandableNotificationRow object) {
    183                     return object.getTranslation();
    184                 }
    185     };
    186     private OnClickListener mOnClickListener;
    187     private boolean mHeadsupDisappearRunning;
    188     private View mChildAfterViewWhenDismissed;
    189     private View mGroupParentWhenDismissed;
    190     private boolean mRefocusOnDismiss;
    191 
    192     public boolean isGroupExpansionChanging() {
    193         if (isChildInGroup()) {
    194             return mNotificationParent.isGroupExpansionChanging();
    195         }
    196         return mGroupExpansionChanging;
    197     }
    198 
    199     public void setGroupExpansionChanging(boolean changing) {
    200         mGroupExpansionChanging = changing;
    201     }
    202 
    203     @Override
    204     public void setActualHeightAnimating(boolean animating) {
    205         if (mPrivateLayout != null) {
    206             mPrivateLayout.setContentHeightAnimating(animating);
    207         }
    208     }
    209 
    210     public NotificationContentView getPrivateLayout() {
    211         return mPrivateLayout;
    212     }
    213 
    214     public NotificationContentView getPublicLayout() {
    215         return mPublicLayout;
    216     }
    217 
    218     public void setIconAnimationRunning(boolean running) {
    219         setIconAnimationRunning(running, mPublicLayout);
    220         setIconAnimationRunning(running, mPrivateLayout);
    221         if (mIsSummaryWithChildren) {
    222             setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView());
    223             List<ExpandableNotificationRow> notificationChildren =
    224                     mChildrenContainer.getNotificationChildren();
    225             for (int i = 0; i < notificationChildren.size(); i++) {
    226                 ExpandableNotificationRow child = notificationChildren.get(i);
    227                 child.setIconAnimationRunning(running);
    228             }
    229         }
    230         mIconAnimationRunning = running;
    231     }
    232 
    233     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
    234         if (layout != null) {
    235             View contractedChild = layout.getContractedChild();
    236             View expandedChild = layout.getExpandedChild();
    237             View headsUpChild = layout.getHeadsUpChild();
    238             setIconAnimationRunningForChild(running, contractedChild);
    239             setIconAnimationRunningForChild(running, expandedChild);
    240             setIconAnimationRunningForChild(running, headsUpChild);
    241         }
    242     }
    243 
    244     private void setIconAnimationRunningForChild(boolean running, View child) {
    245         if (child != null) {
    246             ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
    247             setIconRunning(icon, running);
    248             ImageView rightIcon = (ImageView) child.findViewById(
    249                     com.android.internal.R.id.right_icon);
    250             setIconRunning(rightIcon, running);
    251         }
    252     }
    253 
    254     private void setIconRunning(ImageView imageView, boolean running) {
    255         if (imageView != null) {
    256             Drawable drawable = imageView.getDrawable();
    257             if (drawable instanceof AnimationDrawable) {
    258                 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
    259                 if (running) {
    260                     animationDrawable.start();
    261                 } else {
    262                     animationDrawable.stop();
    263                 }
    264             } else if (drawable instanceof AnimatedVectorDrawable) {
    265                 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
    266                 if (running) {
    267                     animationDrawable.start();
    268                 } else {
    269                     animationDrawable.stop();
    270                 }
    271             }
    272         }
    273     }
    274 
    275     public void onNotificationUpdated(NotificationData.Entry entry) {
    276         mEntry = entry;
    277         mStatusBarNotification = entry.notification;
    278         mPrivateLayout.onNotificationUpdated(entry);
    279         mPublicLayout.onNotificationUpdated(entry);
    280         mShowingPublicInitialized = false;
    281         updateNotificationColor();
    282         if (mIsSummaryWithChildren) {
    283             mChildrenContainer.recreateNotificationHeader(mExpandClickListener, mEntry.notification);
    284             mChildrenContainer.onNotificationUpdated();
    285         }
    286         if (mIconAnimationRunning) {
    287             setIconAnimationRunning(true);
    288         }
    289         if (mNotificationParent != null) {
    290             mNotificationParent.updateChildrenHeaderAppearance();
    291         }
    292         onChildrenCountChanged();
    293         // The public layouts expand button is always visible
    294         mPublicLayout.updateExpandButtons(true);
    295         updateLimits();
    296     }
    297 
    298     private void updateLimits() {
    299         updateLimitsForView(mPrivateLayout);
    300         updateLimitsForView(mPublicLayout);
    301     }
    302 
    303     private void updateLimitsForView(NotificationContentView layout) {
    304         boolean customView = layout.getContractedChild().getId()
    305                 != com.android.internal.R.id.status_bar_latest_event_content;
    306         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
    307         int minHeight = customView && beforeN && !mIsSummaryWithChildren ?
    308                 mNotificationMinHeightLegacy : mNotificationMinHeight;
    309         boolean headsUpCustom = layout.getHeadsUpChild() != null &&
    310                 layout.getHeadsUpChild().getId()
    311                         != com.android.internal.R.id.status_bar_latest_event_content;
    312         int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy
    313                 : mMaxHeadsUpHeight;
    314         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
    315     }
    316 
    317     public StatusBarNotification getStatusBarNotification() {
    318         return mStatusBarNotification;
    319     }
    320 
    321     public boolean isHeadsUp() {
    322         return mIsHeadsUp;
    323     }
    324 
    325     public void setHeadsUp(boolean isHeadsUp) {
    326         int intrinsicBefore = getIntrinsicHeight();
    327         mIsHeadsUp = isHeadsUp;
    328         mPrivateLayout.setHeadsUp(isHeadsUp);
    329         if (mIsSummaryWithChildren) {
    330             // The overflow might change since we allow more lines as HUN.
    331             mChildrenContainer.updateGroupOverflow();
    332         }
    333         if (intrinsicBefore != getIntrinsicHeight()) {
    334             notifyHeightChanged(false  /* needsAnimation */);
    335         }
    336     }
    337 
    338     public void setGroupManager(NotificationGroupManager groupManager) {
    339         mGroupManager = groupManager;
    340         mPrivateLayout.setGroupManager(groupManager);
    341     }
    342 
    343     public void setRemoteInputController(RemoteInputController r) {
    344         mPrivateLayout.setRemoteInputController(r);
    345     }
    346 
    347     public void setAppName(String appName) {
    348         mAppName = appName;
    349         if (mSettingsIconRow != null) {
    350             mSettingsIconRow.setAppName(mAppName);
    351         }
    352     }
    353 
    354     public void addChildNotification(ExpandableNotificationRow row) {
    355         addChildNotification(row, -1);
    356     }
    357 
    358     /**
    359      * Add a child notification to this view.
    360      *
    361      * @param row the row to add
    362      * @param childIndex the index to add it at, if -1 it will be added at the end
    363      */
    364     public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
    365         if (mChildrenContainer == null) {
    366             mChildrenContainerStub.inflate();
    367         }
    368         mChildrenContainer.addNotification(row, childIndex);
    369         onChildrenCountChanged();
    370         row.setIsChildInGroup(true, this);
    371     }
    372 
    373     public void removeChildNotification(ExpandableNotificationRow row) {
    374         if (mChildrenContainer != null) {
    375             mChildrenContainer.removeNotification(row);
    376         }
    377         onChildrenCountChanged();
    378         row.setIsChildInGroup(false, null);
    379     }
    380 
    381     public boolean isChildInGroup() {
    382         return mNotificationParent != null;
    383     }
    384 
    385     public ExpandableNotificationRow getNotificationParent() {
    386         return mNotificationParent;
    387     }
    388 
    389     /**
    390      * @param isChildInGroup Is this notification now in a group
    391      * @param parent the new parent notification
    392      */
    393     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {;
    394         boolean childInGroup = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
    395         mNotificationParent = childInGroup ? parent : null;
    396         mPrivateLayout.setIsChildInGroup(childInGroup);
    397         resetBackgroundAlpha();
    398         updateBackgroundForGroupState();
    399         updateClickAndFocus();
    400         if (mNotificationParent != null) {
    401             mNotificationParent.updateBackgroundForGroupState();
    402         }
    403     }
    404 
    405     @Override
    406     public boolean onTouchEvent(MotionEvent event) {
    407         if (event.getActionMasked() != MotionEvent.ACTION_DOWN
    408                 || !isChildInGroup() || isGroupExpanded()) {
    409             return super.onTouchEvent(event);
    410         } else {
    411             return false;
    412         }
    413     }
    414 
    415     @Override
    416     protected boolean handleSlideBack() {
    417         if (mSettingsIconRow != null && mSettingsIconRow.isVisible()) {
    418             animateTranslateNotification(0 /* targetLeft */);
    419             return true;
    420         }
    421         return false;
    422     }
    423 
    424     @Override
    425     protected boolean shouldHideBackground() {
    426         return super.shouldHideBackground() || mShowNoBackground;
    427     }
    428 
    429     @Override
    430     public boolean isSummaryWithChildren() {
    431         return mIsSummaryWithChildren;
    432     }
    433 
    434     @Override
    435     public boolean areChildrenExpanded() {
    436         return mChildrenExpanded;
    437     }
    438 
    439     public List<ExpandableNotificationRow> getNotificationChildren() {
    440         return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
    441     }
    442 
    443     public int getNumberOfNotificationChildren() {
    444         if (mChildrenContainer == null) {
    445             return 0;
    446         }
    447         return mChildrenContainer.getNotificationChildren().size();
    448     }
    449 
    450     /**
    451      * Apply the order given in the list to the children.
    452      *
    453      * @param childOrder the new list order
    454      * @return whether the list order has changed
    455      */
    456     public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
    457         return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder);
    458     }
    459 
    460     public void getChildrenStates(StackScrollState resultState) {
    461         if (mIsSummaryWithChildren) {
    462             StackViewState parentState = resultState.getViewStateForView(this);
    463             mChildrenContainer.getState(resultState, parentState);
    464         }
    465     }
    466 
    467     public void applyChildrenState(StackScrollState state) {
    468         if (mIsSummaryWithChildren) {
    469             mChildrenContainer.applyState(state);
    470         }
    471     }
    472 
    473     public void prepareExpansionChanged(StackScrollState state) {
    474         if (mIsSummaryWithChildren) {
    475             mChildrenContainer.prepareExpansionChanged(state);
    476         }
    477     }
    478 
    479     public void startChildAnimation(StackScrollState finalState,
    480             StackStateAnimator stateAnimator, long delay, long duration) {
    481         if (mIsSummaryWithChildren) {
    482             mChildrenContainer.startAnimationToState(finalState, stateAnimator, delay,
    483                     duration);
    484         }
    485     }
    486 
    487     public ExpandableNotificationRow getViewAtPosition(float y) {
    488         if (!mIsSummaryWithChildren || !mChildrenExpanded) {
    489             return this;
    490         } else {
    491             ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
    492             return view == null ? this : view;
    493         }
    494     }
    495 
    496     public NotificationGuts getGuts() {
    497         return mGuts;
    498     }
    499 
    500     /**
    501      * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
    502      * the notification will be rendered on top of the screen.
    503      *
    504      * @param pinned whether it is pinned
    505      */
    506     public void setPinned(boolean pinned) {
    507         int intrinsicHeight = getIntrinsicHeight();
    508         mIsPinned = pinned;
    509         if (intrinsicHeight != getIntrinsicHeight()) {
    510             notifyHeightChanged(false);
    511         }
    512         if (pinned) {
    513             setIconAnimationRunning(true);
    514             mExpandedWhenPinned = false;
    515         } else if (mExpandedWhenPinned) {
    516             setUserExpanded(true);
    517         }
    518         setChronometerRunning(mLastChronometerRunning);
    519     }
    520 
    521     public boolean isPinned() {
    522         return mIsPinned;
    523     }
    524 
    525     /**
    526      * @param atLeastMinHeight should the value returned be at least the minimum height.
    527      *                         Used to avoid cyclic calls
    528      * @return the height of the heads up notification when pinned
    529      */
    530     public int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
    531         if (mIsSummaryWithChildren) {
    532             return mChildrenContainer.getIntrinsicHeight();
    533         }
    534         if(mExpandedWhenPinned) {
    535             return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
    536         } else if (atLeastMinHeight) {
    537             return Math.max(getCollapsedHeight(), mHeadsUpHeight);
    538         } else {
    539             return mHeadsUpHeight;
    540         }
    541     }
    542 
    543     /**
    544      * Mark whether this notification was just clicked, i.e. the user has just clicked this
    545      * notification in this frame.
    546      */
    547     public void setJustClicked(boolean justClicked) {
    548         mJustClicked = justClicked;
    549     }
    550 
    551     /**
    552      * @return true if this notification has been clicked in this frame, false otherwise
    553      */
    554     public boolean wasJustClicked() {
    555         return mJustClicked;
    556     }
    557 
    558     public void setChronometerRunning(boolean running) {
    559         mLastChronometerRunning = running;
    560         setChronometerRunning(running, mPrivateLayout);
    561         setChronometerRunning(running, mPublicLayout);
    562         if (mChildrenContainer != null) {
    563             List<ExpandableNotificationRow> notificationChildren =
    564                     mChildrenContainer.getNotificationChildren();
    565             for (int i = 0; i < notificationChildren.size(); i++) {
    566                 ExpandableNotificationRow child = notificationChildren.get(i);
    567                 child.setChronometerRunning(running);
    568             }
    569         }
    570     }
    571 
    572     private void setChronometerRunning(boolean running, NotificationContentView layout) {
    573         if (layout != null) {
    574             running = running || isPinned();
    575             View contractedChild = layout.getContractedChild();
    576             View expandedChild = layout.getExpandedChild();
    577             View headsUpChild = layout.getHeadsUpChild();
    578             setChronometerRunningForChild(running, contractedChild);
    579             setChronometerRunningForChild(running, expandedChild);
    580             setChronometerRunningForChild(running, headsUpChild);
    581         }
    582     }
    583 
    584     private void setChronometerRunningForChild(boolean running, View child) {
    585         if (child != null) {
    586             View chronometer = child.findViewById(com.android.internal.R.id.chronometer);
    587             if (chronometer instanceof Chronometer) {
    588                 ((Chronometer) chronometer).setStarted(running);
    589             }
    590         }
    591     }
    592 
    593     public NotificationHeaderView getNotificationHeader() {
    594         if (mIsSummaryWithChildren) {
    595             return mChildrenContainer.getHeaderView();
    596         }
    597         return mPrivateLayout.getNotificationHeader();
    598     }
    599 
    600     private NotificationHeaderView getVisibleNotificationHeader() {
    601         if (mIsSummaryWithChildren && !mShowingPublic) {
    602             return mChildrenContainer.getHeaderView();
    603         }
    604         return getShowingLayout().getVisibleNotificationHeader();
    605     }
    606 
    607     public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
    608         mOnExpandClickListener = onExpandClickListener;
    609     }
    610 
    611     @Override
    612     public void setOnClickListener(@Nullable OnClickListener l) {
    613         super.setOnClickListener(l);
    614         mOnClickListener = l;
    615         updateClickAndFocus();
    616     }
    617 
    618     private void updateClickAndFocus() {
    619         boolean normalChild = !isChildInGroup() || isGroupExpanded();
    620         boolean clickable = mOnClickListener != null && normalChild;
    621         if (isFocusable() != normalChild) {
    622             setFocusable(normalChild);
    623         }
    624         if (isClickable() != clickable) {
    625             setClickable(clickable);
    626         }
    627     }
    628 
    629     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
    630         mHeadsUpManager = headsUpManager;
    631     }
    632 
    633     public void reInflateViews() {
    634         initDimens();
    635         if (mIsSummaryWithChildren) {
    636             if (mChildrenContainer != null) {
    637                 mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.notification);
    638             }
    639         }
    640         if (mGuts != null) {
    641             View oldGuts = mGuts;
    642             int index = indexOfChild(oldGuts);
    643             removeView(oldGuts);
    644             mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate(
    645                     R.layout.notification_guts, this, false);
    646             mGuts.setVisibility(oldGuts.getVisibility());
    647             addView(mGuts, index);
    648         }
    649         if (mSettingsIconRow != null) {
    650             View oldSettings = mSettingsIconRow;
    651             int settingsIndex = indexOfChild(oldSettings);
    652             removeView(oldSettings);
    653             mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate(
    654                     R.layout.notification_settings_icon_row, this, false);
    655             mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
    656             mSettingsIconRow.setAppName(mAppName);
    657             mSettingsIconRow.setVisibility(oldSettings.getVisibility());
    658             addView(mSettingsIconRow, settingsIndex);
    659 
    660         }
    661         mPrivateLayout.reInflateViews();
    662         mPublicLayout.reInflateViews();
    663     }
    664 
    665     public void setContentBackground(int customBackgroundColor, boolean animate,
    666             NotificationContentView notificationContentView) {
    667         if (getShowingLayout() == notificationContentView) {
    668             setTintColor(customBackgroundColor, animate);
    669         }
    670     }
    671 
    672     public void closeRemoteInput() {
    673         mPrivateLayout.closeRemoteInput();
    674         mPublicLayout.closeRemoteInput();
    675     }
    676 
    677     /**
    678      * Set by how much the single line view should be indented.
    679      */
    680     public void setSingleLineWidthIndention(int indention) {
    681         mPrivateLayout.setSingleLineWidthIndention(indention);
    682     }
    683 
    684     public int getNotificationColor() {
    685         return mNotificationColor;
    686     }
    687 
    688     private void updateNotificationColor() {
    689         mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext,
    690                 getStatusBarNotification().getNotification().color);
    691     }
    692 
    693     public HybridNotificationView getSingleLineView() {
    694         return mPrivateLayout.getSingleLineView();
    695     }
    696 
    697     public boolean isOnKeyguard() {
    698         return mOnKeyguard;
    699     }
    700 
    701     public void removeAllChildren() {
    702         List<ExpandableNotificationRow> notificationChildren
    703                 = mChildrenContainer.getNotificationChildren();
    704         ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren);
    705         for (int i = 0; i < clonedList.size(); i++) {
    706             ExpandableNotificationRow row = clonedList.get(i);
    707             if (row.keepInParent()) {
    708                 continue;
    709             }
    710             mChildrenContainer.removeNotification(row);
    711             row.setIsChildInGroup(false, null);
    712         }
    713         onChildrenCountChanged();
    714     }
    715 
    716     public void setForceUnlocked(boolean forceUnlocked) {
    717         mForceUnlocked = forceUnlocked;
    718         if (mIsSummaryWithChildren) {
    719             List<ExpandableNotificationRow> notificationChildren = getNotificationChildren();
    720             for (ExpandableNotificationRow child : notificationChildren) {
    721                 child.setForceUnlocked(forceUnlocked);
    722             }
    723         }
    724     }
    725 
    726     public void setDismissed(boolean dismissed, boolean fromAccessibility) {
    727         mDismissed = dismissed;
    728         mGroupParentWhenDismissed = mNotificationParent;
    729         mRefocusOnDismiss = fromAccessibility;
    730         mChildAfterViewWhenDismissed = null;
    731         if (isChildInGroup()) {
    732             List<ExpandableNotificationRow> notificationChildren =
    733                     mNotificationParent.getNotificationChildren();
    734             int i = notificationChildren.indexOf(this);
    735             if (i != -1 && i < notificationChildren.size() - 1) {
    736                 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1);
    737             }
    738         }
    739     }
    740 
    741     public boolean isDismissed() {
    742         return mDismissed;
    743     }
    744 
    745     public boolean keepInParent() {
    746         return mKeepInParent;
    747     }
    748 
    749     public void setKeepInParent(boolean keepInParent) {
    750         mKeepInParent = keepInParent;
    751     }
    752 
    753     public boolean isRemoved() {
    754         return mRemoved;
    755     }
    756 
    757     public void setRemoved() {
    758         mRemoved = true;
    759 
    760         mPrivateLayout.setRemoved();
    761     }
    762 
    763     public NotificationChildrenContainer getChildrenContainer() {
    764         return mChildrenContainer;
    765     }
    766 
    767     public void setHeadsupDisappearRunning(boolean running) {
    768         mHeadsupDisappearRunning = running;
    769         mPrivateLayout.setHeadsupDisappearRunning(running);
    770     }
    771 
    772     public View getChildAfterViewWhenDismissed() {
    773         return mChildAfterViewWhenDismissed;
    774     }
    775 
    776     public View getGroupParentWhenDismissed() {
    777         return mGroupParentWhenDismissed;
    778     }
    779 
    780     public void performDismiss() {
    781         mVetoButton.performClick();
    782     }
    783 
    784     public void setOnDismissListener(OnClickListener listener) {
    785         mVetoButton.setOnClickListener(listener);
    786     }
    787 
    788     public interface ExpansionLogger {
    789         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
    790     }
    791 
    792     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
    793         super(context, attrs);
    794         mFalsingManager = FalsingManager.getInstance(context);
    795         initDimens();
    796     }
    797 
    798     private void initDimens() {
    799         mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
    800         mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
    801         mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
    802         mMaxHeadsUpHeightLegacy = getFontScaledHeight(
    803                 R.dimen.notification_max_heads_up_height_legacy);
    804         mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
    805         mIncreasedPaddingBetweenElements = getResources()
    806                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
    807     }
    808 
    809     /**
    810      * @param dimenId the dimen to look up
    811      * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
    812      */
    813     private int getFontScaledHeight(int dimenId) {
    814         int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
    815         float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
    816                 getResources().getDisplayMetrics().density);
    817         return (int) (dimensionPixelSize * factor);
    818     }
    819 
    820     /**
    821      * Resets this view so it can be re-used for an updated notification.
    822      */
    823     @Override
    824     public void reset() {
    825         super.reset();
    826         final boolean wasExpanded = isExpanded();
    827         mExpandable = false;
    828         mHasUserChangedExpansion = false;
    829         mUserLocked = false;
    830         mShowingPublic = false;
    831         mSensitive = false;
    832         mShowingPublicInitialized = false;
    833         mIsSystemExpanded = false;
    834         mOnKeyguard = false;
    835         mPublicLayout.reset();
    836         mPrivateLayout.reset();
    837         resetHeight();
    838         resetTranslation();
    839         logExpansionEvent(false, wasExpanded);
    840     }
    841 
    842     public void resetHeight() {
    843         mMaxExpandHeight = 0;
    844         mHeadsUpHeight = 0;
    845         onHeightReset();
    846         requestLayout();
    847     }
    848 
    849     @Override
    850     protected void onFinishInflate() {
    851         super.onFinishInflate();
    852         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
    853         mPublicLayout.setContainingNotification(this);
    854         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
    855         mPrivateLayout.setExpandClickListener(mExpandClickListener);
    856         mPrivateLayout.setContainingNotification(this);
    857         mPublicLayout.setExpandClickListener(mExpandClickListener);
    858         mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub);
    859         mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    860             @Override
    861             public void onInflate(ViewStub stub, View inflated) {
    862                 mSettingsIconRow = (NotificationSettingsIconRow) inflated;
    863                 mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
    864                 mSettingsIconRow.setAppName(mAppName);
    865             }
    866         });
    867         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
    868         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    869             @Override
    870             public void onInflate(ViewStub stub, View inflated) {
    871                 mGuts = (NotificationGuts) inflated;
    872                 mGuts.setClipTopAmount(getClipTopAmount());
    873                 mGuts.setActualHeight(getActualHeight());
    874                 mGutsStub = null;
    875             }
    876         });
    877         mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
    878         mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    879 
    880             @Override
    881             public void onInflate(ViewStub stub, View inflated) {
    882                 mChildrenContainer = (NotificationChildrenContainer) inflated;
    883                 mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this);
    884                 mChildrenContainer.onNotificationUpdated();
    885                 mTranslateableViews.add(mChildrenContainer);
    886             }
    887         });
    888         mVetoButton = findViewById(R.id.veto);
    889         mVetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
    890         mVetoButton.setContentDescription(mContext.getString(
    891                 R.string.accessibility_remove_notification));
    892 
    893         // Add the views that we translate to reveal the gear
    894         mTranslateableViews = new ArrayList<View>();
    895         for (int i = 0; i < getChildCount(); i++) {
    896             mTranslateableViews.add(getChildAt(i));
    897         }
    898         // Remove views that don't translate
    899         mTranslateableViews.remove(mVetoButton);
    900         mTranslateableViews.remove(mSettingsIconRowStub);
    901         mTranslateableViews.remove(mChildrenContainerStub);
    902         mTranslateableViews.remove(mGutsStub);
    903     }
    904 
    905     public View getVetoButton() {
    906         return mVetoButton;
    907     }
    908 
    909     public void resetTranslation() {
    910         if (mTranslateableViews != null) {
    911             for (int i = 0; i < mTranslateableViews.size(); i++) {
    912                 mTranslateableViews.get(i).setTranslationX(0);
    913             }
    914         }
    915         invalidateOutline();
    916         if (mSettingsIconRow != null) {
    917             mSettingsIconRow.resetState();
    918         }
    919     }
    920 
    921     public void animateTranslateNotification(final float leftTarget) {
    922         if (mTranslateAnim != null) {
    923             mTranslateAnim.cancel();
    924         }
    925         mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */);
    926         if (mTranslateAnim != null) {
    927             mTranslateAnim.start();
    928         }
    929     }
    930 
    931     @Override
    932     public void setTranslation(float translationX) {
    933         if (areGutsExposed()) {
    934             // Don't translate if guts are showing.
    935             return;
    936         }
    937         // Translate the group of views
    938         for (int i = 0; i < mTranslateableViews.size(); i++) {
    939             if (mTranslateableViews.get(i) != null) {
    940                 mTranslateableViews.get(i).setTranslationX(translationX);
    941             }
    942         }
    943         invalidateOutline();
    944         if (mSettingsIconRow != null) {
    945             mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth());
    946         }
    947     }
    948 
    949     @Override
    950     public float getTranslation() {
    951         if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
    952             // All of the views in the list should have same translation, just use first one.
    953             return mTranslateableViews.get(0).getTranslationX();
    954         }
    955         return 0;
    956     }
    957 
    958     public Animator getTranslateViewAnimator(final float leftTarget,
    959             AnimatorUpdateListener listener) {
    960         if (mTranslateAnim != null) {
    961             mTranslateAnim.cancel();
    962         }
    963         if (areGutsExposed()) {
    964             // No translation if guts are exposed.
    965             return null;
    966         }
    967         final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT,
    968                 leftTarget);
    969         if (listener != null) {
    970             translateAnim.addUpdateListener(listener);
    971         }
    972         translateAnim.addListener(new AnimatorListenerAdapter() {
    973             boolean cancelled = false;
    974 
    975             @Override
    976             public void onAnimationCancel(Animator anim) {
    977                 cancelled = true;
    978             }
    979 
    980             @Override
    981             public void onAnimationEnd(Animator anim) {
    982                 if (!cancelled && mSettingsIconRow != null && leftTarget == 0) {
    983                     mSettingsIconRow.resetState();
    984                     mTranslateAnim = null;
    985                 }
    986             }
    987         });
    988         mTranslateAnim = translateAnim;
    989         return translateAnim;
    990     }
    991 
    992     public float getSpaceForGear() {
    993         if (mSettingsIconRow != null) {
    994             return mSettingsIconRow.getSpaceForGear();
    995         }
    996         return 0;
    997     }
    998 
    999     public NotificationSettingsIconRow getSettingsRow() {
   1000         if (mSettingsIconRow == null) {
   1001             mSettingsIconRowStub.inflate();
   1002         }
   1003         return mSettingsIconRow;
   1004     }
   1005 
   1006     public void inflateGuts() {
   1007         if (mGuts == null) {
   1008             mGutsStub.inflate();
   1009         }
   1010     }
   1011 
   1012     private void updateChildrenVisibility() {
   1013         mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
   1014                 : INVISIBLE);
   1015         if (mChildrenContainer != null) {
   1016             mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
   1017                     : INVISIBLE);
   1018             mChildrenContainer.updateHeaderVisibility(!mShowingPublic && mIsSummaryWithChildren
   1019                     ? VISIBLE
   1020                     : INVISIBLE);
   1021         }
   1022         // The limits might have changed if the view suddenly became a group or vice versa
   1023         updateLimits();
   1024     }
   1025 
   1026     @Override
   1027     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
   1028         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
   1029             // Add a record for the entire layout since its content is somehow small.
   1030             // The event comes from a leaf view that is interacted with.
   1031             AccessibilityEvent record = AccessibilityEvent.obtain();
   1032             onInitializeAccessibilityEvent(record);
   1033             dispatchPopulateAccessibilityEvent(record);
   1034             event.appendRecord(record);
   1035             return true;
   1036         }
   1037         return false;
   1038     }
   1039 
   1040     @Override
   1041     public void setDark(boolean dark, boolean fade, long delay) {
   1042         super.setDark(dark, fade, delay);
   1043         final NotificationContentView showing = getShowingLayout();
   1044         if (showing != null) {
   1045             showing.setDark(dark, fade, delay);
   1046         }
   1047         if (mIsSummaryWithChildren) {
   1048             mChildrenContainer.setDark(dark, fade, delay);
   1049         }
   1050     }
   1051 
   1052     public boolean isExpandable() {
   1053         if (mIsSummaryWithChildren && !mShowingPublic) {
   1054             return !mChildrenExpanded;
   1055         }
   1056         return mExpandable;
   1057     }
   1058 
   1059     public void setExpandable(boolean expandable) {
   1060         mExpandable = expandable;
   1061         mPrivateLayout.updateExpandButtons(isExpandable());
   1062     }
   1063 
   1064     @Override
   1065     public void setClipToActualHeight(boolean clipToActualHeight) {
   1066         super.setClipToActualHeight(clipToActualHeight || isUserLocked());
   1067         getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked());
   1068     }
   1069 
   1070     /**
   1071      * @return whether the user has changed the expansion state
   1072      */
   1073     public boolean hasUserChangedExpansion() {
   1074         return mHasUserChangedExpansion;
   1075     }
   1076 
   1077     public boolean isUserExpanded() {
   1078         return mUserExpanded;
   1079     }
   1080 
   1081     /**
   1082      * Set this notification to be expanded by the user
   1083      *
   1084      * @param userExpanded whether the user wants this notification to be expanded
   1085      */
   1086     public void setUserExpanded(boolean userExpanded) {
   1087         setUserExpanded(userExpanded, false /* allowChildExpansion */);
   1088     }
   1089 
   1090     /**
   1091      * Set this notification to be expanded by the user
   1092      *
   1093      * @param userExpanded whether the user wants this notification to be expanded
   1094      * @param allowChildExpansion whether a call to this method allows expanding children
   1095      */
   1096     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
   1097         mFalsingManager.setNotificationExpanded();
   1098         if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion) {
   1099             final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
   1100             mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded);
   1101             logExpansionEvent(true /* userAction */, wasExpanded);
   1102             return;
   1103         }
   1104         if (userExpanded && !mExpandable) return;
   1105         final boolean wasExpanded = isExpanded();
   1106         mHasUserChangedExpansion = true;
   1107         mUserExpanded = userExpanded;
   1108         logExpansionEvent(true, wasExpanded);
   1109     }
   1110 
   1111     public void resetUserExpansion() {
   1112         mHasUserChangedExpansion = false;
   1113         mUserExpanded = false;
   1114     }
   1115 
   1116     public boolean isUserLocked() {
   1117         return mUserLocked && !mForceUnlocked;
   1118     }
   1119 
   1120     public void setUserLocked(boolean userLocked) {
   1121         mUserLocked = userLocked;
   1122         mPrivateLayout.setUserExpanding(userLocked);
   1123         if (mIsSummaryWithChildren) {
   1124             mChildrenContainer.setUserLocked(userLocked);
   1125             if (userLocked || !isGroupExpanded()) {
   1126                 updateBackgroundForGroupState();
   1127             }
   1128         }
   1129     }
   1130 
   1131     /**
   1132      * @return has the system set this notification to be expanded
   1133      */
   1134     public boolean isSystemExpanded() {
   1135         return mIsSystemExpanded;
   1136     }
   1137 
   1138     /**
   1139      * Set this notification to be expanded by the system.
   1140      *
   1141      * @param expand whether the system wants this notification to be expanded.
   1142      */
   1143     public void setSystemExpanded(boolean expand) {
   1144         if (expand != mIsSystemExpanded) {
   1145             final boolean wasExpanded = isExpanded();
   1146             mIsSystemExpanded = expand;
   1147             notifyHeightChanged(false /* needsAnimation */);
   1148             logExpansionEvent(false, wasExpanded);
   1149             if (mIsSummaryWithChildren) {
   1150                 mChildrenContainer.updateGroupOverflow();
   1151             }
   1152         }
   1153     }
   1154 
   1155     /**
   1156      * @param onKeyguard whether to prevent notification expansion
   1157      */
   1158     public void setOnKeyguard(boolean onKeyguard) {
   1159         if (onKeyguard != mOnKeyguard) {
   1160             final boolean wasExpanded = isExpanded();
   1161             mOnKeyguard = onKeyguard;
   1162             logExpansionEvent(false, wasExpanded);
   1163             if (wasExpanded != isExpanded()) {
   1164                 if (mIsSummaryWithChildren) {
   1165                     mChildrenContainer.updateGroupOverflow();
   1166                 }
   1167                 notifyHeightChanged(false /* needsAnimation */);
   1168             }
   1169         }
   1170     }
   1171 
   1172     /**
   1173      * @return Can the underlying notification be cleared? This can be different from whether the
   1174      *         notification can be dismissed in case notifications are sensitive on the lockscreen.
   1175      * @see #canViewBeDismissed()
   1176      */
   1177     public boolean isClearable() {
   1178         if (mStatusBarNotification == null || !mStatusBarNotification.isClearable()) {
   1179             return false;
   1180         }
   1181         if (mIsSummaryWithChildren) {
   1182             List<ExpandableNotificationRow> notificationChildren =
   1183                     mChildrenContainer.getNotificationChildren();
   1184             for (int i = 0; i < notificationChildren.size(); i++) {
   1185                 ExpandableNotificationRow child = notificationChildren.get(i);
   1186                 if (!child.isClearable()) {
   1187                     return false;
   1188                 }
   1189             }
   1190         }
   1191         return true;
   1192     }
   1193 
   1194     @Override
   1195     public int getIntrinsicHeight() {
   1196         if (isUserLocked()) {
   1197             return getActualHeight();
   1198         }
   1199         if (mGuts != null && mGuts.areGutsExposed()) {
   1200             return mGuts.getHeight();
   1201         } else if ((isChildInGroup() && !isGroupExpanded())) {
   1202             return mPrivateLayout.getMinHeight();
   1203         } else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
   1204             return getMinHeight();
   1205         } else if (mIsSummaryWithChildren && !mOnKeyguard) {
   1206             return mChildrenContainer.getIntrinsicHeight();
   1207         } else if (mIsHeadsUp || mHeadsupDisappearRunning) {
   1208             if (isPinned() || mHeadsupDisappearRunning) {
   1209                 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
   1210             } else if (isExpanded()) {
   1211                 return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
   1212             } else {
   1213                 return Math.max(getCollapsedHeight(), mHeadsUpHeight);
   1214             }
   1215         } else if (isExpanded()) {
   1216             return getMaxExpandHeight();
   1217         } else {
   1218             return getCollapsedHeight();
   1219         }
   1220     }
   1221 
   1222     public boolean isGroupExpanded() {
   1223         return mGroupManager.isGroupExpanded(mStatusBarNotification);
   1224     }
   1225 
   1226     private void onChildrenCountChanged() {
   1227         mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
   1228                 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
   1229         if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
   1230             mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
   1231                     mEntry.notification);
   1232         }
   1233         getShowingLayout().updateBackgroundColor(false /* animate */);
   1234         mPrivateLayout.updateExpandButtons(isExpandable());
   1235         updateChildrenHeaderAppearance();
   1236         updateChildrenVisibility();
   1237     }
   1238 
   1239     public void updateChildrenHeaderAppearance() {
   1240         if (mIsSummaryWithChildren) {
   1241             mChildrenContainer.updateChildrenHeaderAppearance();
   1242         }
   1243     }
   1244 
   1245     /**
   1246      * Check whether the view state is currently expanded. This is given by the system in {@link
   1247      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
   1248      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
   1249      * view can differ from this state, if layout params are modified from outside.
   1250      *
   1251      * @return whether the view state is currently expanded.
   1252      */
   1253     public boolean isExpanded() {
   1254         return isExpanded(false /* allowOnKeyguard */);
   1255     }
   1256 
   1257     public boolean isExpanded(boolean allowOnKeyguard) {
   1258         return (!mOnKeyguard || allowOnKeyguard)
   1259                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
   1260                 || isUserExpanded());
   1261     }
   1262 
   1263     private boolean isSystemChildExpanded() {
   1264         return mIsSystemChildExpanded;
   1265     }
   1266 
   1267     public void setSystemChildExpanded(boolean expanded) {
   1268         mIsSystemChildExpanded = expanded;
   1269     }
   1270 
   1271     @Override
   1272     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   1273         super.onLayout(changed, left, top, right, bottom);
   1274         updateMaxHeights();
   1275         if (mSettingsIconRow != null) {
   1276             mSettingsIconRow.updateVerticalLocation();
   1277         }
   1278     }
   1279 
   1280     private void updateMaxHeights() {
   1281         int intrinsicBefore = getIntrinsicHeight();
   1282         View expandedChild = mPrivateLayout.getExpandedChild();
   1283         if (expandedChild == null) {
   1284             expandedChild = mPrivateLayout.getContractedChild();
   1285         }
   1286         mMaxExpandHeight = expandedChild.getHeight();
   1287         View headsUpChild = mPrivateLayout.getHeadsUpChild();
   1288         if (headsUpChild == null) {
   1289             headsUpChild = mPrivateLayout.getContractedChild();
   1290         }
   1291         mHeadsUpHeight = headsUpChild.getHeight();
   1292         if (intrinsicBefore != getIntrinsicHeight()) {
   1293             notifyHeightChanged(false  /* needsAnimation */);
   1294         }
   1295     }
   1296 
   1297     @Override
   1298     public void notifyHeightChanged(boolean needsAnimation) {
   1299         super.notifyHeightChanged(needsAnimation);
   1300         getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
   1301     }
   1302 
   1303     public void setSensitive(boolean sensitive, boolean hideSensitive) {
   1304         mSensitive = sensitive;
   1305         mSensitiveHiddenInGeneral = hideSensitive;
   1306     }
   1307 
   1308     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
   1309         mHideSensitiveForIntrinsicHeight = hideSensitive;
   1310         if (mIsSummaryWithChildren) {
   1311             List<ExpandableNotificationRow> notificationChildren =
   1312                     mChildrenContainer.getNotificationChildren();
   1313             for (int i = 0; i < notificationChildren.size(); i++) {
   1314                 ExpandableNotificationRow child = notificationChildren.get(i);
   1315                 child.setHideSensitiveForIntrinsicHeight(hideSensitive);
   1316             }
   1317         }
   1318     }
   1319 
   1320     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
   1321             long duration) {
   1322         boolean oldShowingPublic = mShowingPublic;
   1323         mShowingPublic = mSensitive && hideSensitive;
   1324         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
   1325             return;
   1326         }
   1327 
   1328         // bail out if no public version
   1329         if (mPublicLayout.getChildCount() == 0) return;
   1330 
   1331         if (!animated) {
   1332             mPublicLayout.animate().cancel();
   1333             mPrivateLayout.animate().cancel();
   1334             if (mChildrenContainer != null) {
   1335                 mChildrenContainer.animate().cancel();
   1336                 mChildrenContainer.setAlpha(1f);
   1337             }
   1338             mPublicLayout.setAlpha(1f);
   1339             mPrivateLayout.setAlpha(1f);
   1340             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
   1341             updateChildrenVisibility();
   1342         } else {
   1343             animateShowingPublic(delay, duration);
   1344         }
   1345         NotificationContentView showingLayout = getShowingLayout();
   1346         showingLayout.updateBackgroundColor(animated);
   1347         mPrivateLayout.updateExpandButtons(isExpandable());
   1348         mShowingPublicInitialized = true;
   1349     }
   1350 
   1351     private void animateShowingPublic(long delay, long duration) {
   1352         View[] privateViews = mIsSummaryWithChildren
   1353                 ? new View[] {mChildrenContainer}
   1354                 : new View[] {mPrivateLayout};
   1355         View[] publicViews = new View[] {mPublicLayout};
   1356         View[] hiddenChildren = mShowingPublic ? privateViews : publicViews;
   1357         View[] shownChildren = mShowingPublic ? publicViews : privateViews;
   1358         for (final View hiddenView : hiddenChildren) {
   1359             hiddenView.setVisibility(View.VISIBLE);
   1360             hiddenView.animate().cancel();
   1361             hiddenView.animate()
   1362                     .alpha(0f)
   1363                     .setStartDelay(delay)
   1364                     .setDuration(duration)
   1365                     .withEndAction(new Runnable() {
   1366                         @Override
   1367                         public void run() {
   1368                             hiddenView.setVisibility(View.INVISIBLE);
   1369                         }
   1370                     });
   1371         }
   1372         for (View showView : shownChildren) {
   1373             showView.setVisibility(View.VISIBLE);
   1374             showView.setAlpha(0f);
   1375             showView.animate().cancel();
   1376             showView.animate()
   1377                     .alpha(1f)
   1378                     .setStartDelay(delay)
   1379                     .setDuration(duration);
   1380         }
   1381     }
   1382 
   1383     public boolean mustStayOnScreen() {
   1384         return mIsHeadsUp;
   1385     }
   1386 
   1387     /**
   1388      * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
   1389      *         otherwise some state might not be updated. To request about the general clearability
   1390      *         see {@link #isClearable()}.
   1391      */
   1392     public boolean canViewBeDismissed() {
   1393         return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral);
   1394     }
   1395 
   1396     public void makeActionsVisibile() {
   1397         setUserExpanded(true, true);
   1398         if (isChildInGroup()) {
   1399             mGroupManager.setGroupExpanded(mStatusBarNotification, true);
   1400         }
   1401         notifyHeightChanged(false);
   1402     }
   1403 
   1404     public void setChildrenExpanded(boolean expanded, boolean animate) {
   1405         mChildrenExpanded = expanded;
   1406         if (mChildrenContainer != null) {
   1407             mChildrenContainer.setChildrenExpanded(expanded);
   1408         }
   1409         updateBackgroundForGroupState();
   1410         updateClickAndFocus();
   1411     }
   1412 
   1413     public static void applyTint(View v, int color) {
   1414         int alpha;
   1415         if (color != 0) {
   1416             alpha = COLORED_DIVIDER_ALPHA;
   1417         } else {
   1418             color = 0xff000000;
   1419             alpha = DEFAULT_DIVIDER_ALPHA;
   1420         }
   1421         if (v.getBackground() instanceof ColorDrawable) {
   1422             ColorDrawable background = (ColorDrawable) v.getBackground();
   1423             background.mutate();
   1424             background.setColor(color);
   1425             background.setAlpha(alpha);
   1426         }
   1427     }
   1428 
   1429     public int getMaxExpandHeight() {
   1430         return mMaxExpandHeight;
   1431     }
   1432 
   1433     public boolean areGutsExposed() {
   1434         return (mGuts != null && mGuts.areGutsExposed());
   1435     }
   1436 
   1437     @Override
   1438     public boolean isContentExpandable() {
   1439         NotificationContentView showingLayout = getShowingLayout();
   1440         return showingLayout.isContentExpandable();
   1441     }
   1442 
   1443     @Override
   1444     protected View getContentView() {
   1445         if (mIsSummaryWithChildren && !mShowingPublic) {
   1446             return mChildrenContainer;
   1447         }
   1448         return getShowingLayout();
   1449     }
   1450 
   1451     @Override
   1452     protected void onAppearAnimationFinished(boolean wasAppearing) {
   1453         super.onAppearAnimationFinished(wasAppearing);
   1454         if (wasAppearing) {
   1455             // During the animation the visible view might have changed, so let's make sure all
   1456             // alphas are reset
   1457             if (mChildrenContainer != null) {
   1458                 mChildrenContainer.setAlpha(1.0f);
   1459                 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
   1460             }
   1461             mPrivateLayout.setAlpha(1.0f);
   1462             mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null);
   1463             mPublicLayout.setAlpha(1.0f);
   1464             mPublicLayout.setLayerType(LAYER_TYPE_NONE, null);
   1465         }
   1466     }
   1467 
   1468     @Override
   1469     public int getExtraBottomPadding() {
   1470         if (mIsSummaryWithChildren && isGroupExpanded()) {
   1471             return mIncreasedPaddingBetweenElements;
   1472         }
   1473         return 0;
   1474     }
   1475 
   1476     @Override
   1477     public void setActualHeight(int height, boolean notifyListeners) {
   1478         super.setActualHeight(height, notifyListeners);
   1479         if (mGuts != null && mGuts.areGutsExposed()) {
   1480             mGuts.setActualHeight(height);
   1481             return;
   1482         }
   1483         int contentHeight = Math.max(getMinHeight(), height);
   1484         mPrivateLayout.setContentHeight(contentHeight);
   1485         mPublicLayout.setContentHeight(contentHeight);
   1486         if (mIsSummaryWithChildren) {
   1487             mChildrenContainer.setActualHeight(height);
   1488         }
   1489         if (mGuts != null) {
   1490             mGuts.setActualHeight(height);
   1491         }
   1492     }
   1493 
   1494     @Override
   1495     public int getMaxContentHeight() {
   1496         if (mIsSummaryWithChildren && !mShowingPublic) {
   1497             return mChildrenContainer.getMaxContentHeight();
   1498         }
   1499         NotificationContentView showingLayout = getShowingLayout();
   1500         return showingLayout.getMaxHeight();
   1501     }
   1502 
   1503     @Override
   1504     public int getMinHeight() {
   1505         if (mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
   1506                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
   1507         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
   1508             return mChildrenContainer.getMinHeight();
   1509         } else if (mIsHeadsUp) {
   1510             return mHeadsUpHeight;
   1511         }
   1512         NotificationContentView showingLayout = getShowingLayout();
   1513         return showingLayout.getMinHeight();
   1514     }
   1515 
   1516     @Override
   1517     public int getCollapsedHeight() {
   1518         if (mIsSummaryWithChildren && !mShowingPublic) {
   1519             return mChildrenContainer.getCollapsedHeight();
   1520         }
   1521         return getMinHeight();
   1522     }
   1523 
   1524     @Override
   1525     public void setClipTopAmount(int clipTopAmount) {
   1526         super.setClipTopAmount(clipTopAmount);
   1527         mPrivateLayout.setClipTopAmount(clipTopAmount);
   1528         mPublicLayout.setClipTopAmount(clipTopAmount);
   1529         if (mGuts != null) {
   1530             mGuts.setClipTopAmount(clipTopAmount);
   1531         }
   1532     }
   1533 
   1534     public boolean isMaxExpandHeightInitialized() {
   1535         return mMaxExpandHeight != 0;
   1536     }
   1537 
   1538     public NotificationContentView getShowingLayout() {
   1539         return mShowingPublic ? mPublicLayout : mPrivateLayout;
   1540     }
   1541 
   1542     @Override
   1543     public void setShowingLegacyBackground(boolean showing) {
   1544         super.setShowingLegacyBackground(showing);
   1545         mPrivateLayout.setShowingLegacyBackground(showing);
   1546         mPublicLayout.setShowingLegacyBackground(showing);
   1547     }
   1548 
   1549     @Override
   1550     protected void updateBackgroundTint() {
   1551         super.updateBackgroundTint();
   1552         updateBackgroundForGroupState();
   1553         if (mIsSummaryWithChildren) {
   1554             List<ExpandableNotificationRow> notificationChildren =
   1555                     mChildrenContainer.getNotificationChildren();
   1556             for (int i = 0; i < notificationChildren.size(); i++) {
   1557                 ExpandableNotificationRow child = notificationChildren.get(i);
   1558                 child.updateBackgroundForGroupState();
   1559             }
   1560         }
   1561     }
   1562 
   1563     /**
   1564      * Called when a group has finished animating from collapsed or expanded state.
   1565      */
   1566     public void onFinishedExpansionChange() {
   1567         mGroupExpansionChanging = false;
   1568         updateBackgroundForGroupState();
   1569     }
   1570 
   1571     /**
   1572      * Updates the parent and children backgrounds in a group based on the expansion state.
   1573      */
   1574     public void updateBackgroundForGroupState() {
   1575         if (mIsSummaryWithChildren) {
   1576             // Only when the group has finished expanding do we hide its background.
   1577             mShowNoBackground = isGroupExpanded() && !isGroupExpansionChanging() && !isUserLocked();
   1578             mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);
   1579             List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren();
   1580             for (int i = 0; i < children.size(); i++) {
   1581                 children.get(i).updateBackgroundForGroupState();
   1582             }
   1583         } else if (isChildInGroup()) {
   1584             final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
   1585             // Only show a background if the group is expanded OR if it is expanding / collapsing
   1586             // and has a custom background color
   1587             final boolean showBackground = isGroupExpanded()
   1588                     || ((mNotificationParent.isGroupExpansionChanging()
   1589                             || mNotificationParent.isUserLocked()) && childColor != 0);
   1590             mShowNoBackground = !showBackground;
   1591         } else {
   1592             // Only children or parents ever need no background.
   1593             mShowNoBackground = false;
   1594         }
   1595         updateOutline();
   1596         updateBackground();
   1597     }
   1598 
   1599     public int getPositionOfChild(ExpandableNotificationRow childRow) {
   1600         if (mIsSummaryWithChildren) {
   1601             return mChildrenContainer.getPositionInLinearLayout(childRow);
   1602         }
   1603         return 0;
   1604     }
   1605 
   1606     public void setExpansionLogger(ExpansionLogger logger, String key) {
   1607         mLogger = logger;
   1608         mLoggingKey = key;
   1609     }
   1610 
   1611     public void onExpandedByGesture(boolean userExpanded) {
   1612         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
   1613         if (mGroupManager.isSummaryOfGroup(getStatusBarNotification())) {
   1614             event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
   1615         }
   1616         MetricsLogger.action(mContext, event, userExpanded);
   1617     }
   1618 
   1619     @Override
   1620     public float getIncreasedPaddingAmount() {
   1621         if (mIsSummaryWithChildren) {
   1622             if (isGroupExpanded()) {
   1623                 return 1.0f;
   1624             } else if (isUserLocked()) {
   1625                 return mChildrenContainer.getGroupExpandFraction();
   1626             }
   1627         }
   1628         return 0.0f;
   1629     }
   1630 
   1631     @Override
   1632     protected boolean disallowSingleClick(MotionEvent event) {
   1633         float x = event.getX();
   1634         float y = event.getY();
   1635         NotificationHeaderView header = getVisibleNotificationHeader();
   1636         if (header != null) {
   1637             return header.isInTouchRect(x - getTranslation(), y);
   1638         }
   1639         return super.disallowSingleClick(event);
   1640     }
   1641 
   1642     private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
   1643         boolean nowExpanded = isExpanded();
   1644         if (mIsSummaryWithChildren) {
   1645             nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
   1646         }
   1647         if (wasExpanded != nowExpanded && mLogger != null) {
   1648             mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
   1649         }
   1650     }
   1651 
   1652     @Override
   1653     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
   1654         super.onInitializeAccessibilityNodeInfoInternal(info);
   1655         if (canViewBeDismissed()) {
   1656             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
   1657         }
   1658     }
   1659 
   1660     @Override
   1661     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
   1662         if (super.performAccessibilityActionInternal(action, arguments)) {
   1663             return true;
   1664         }
   1665         switch (action) {
   1666             case AccessibilityNodeInfo.ACTION_DISMISS:
   1667                 NotificationStackScrollLayout.performDismiss(this, mGroupManager,
   1668                         true /* fromAccessibility */);
   1669                 return true;
   1670         }
   1671         return false;
   1672     }
   1673 
   1674     public boolean shouldRefocusOnDismiss() {
   1675         return mRefocusOnDismiss || isAccessibilityFocused();
   1676     }
   1677 
   1678     public interface OnExpandClickListener {
   1679         void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
   1680     }
   1681 }
   1682