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 /* needsAnimation */);
    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         onHeightReset();
    844         requestLayout();
    845     }
    846 
    847     @Override
    848     protected void onFinishInflate() {
    849         super.onFinishInflate();
    850         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
    851         mPublicLayout.setContainingNotification(this);
    852         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
    853         mPrivateLayout.setExpandClickListener(mExpandClickListener);
    854         mPrivateLayout.setContainingNotification(this);
    855         mPublicLayout.setExpandClickListener(mExpandClickListener);
    856         mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub);
    857         mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    858             @Override
    859             public void onInflate(ViewStub stub, View inflated) {
    860                 mSettingsIconRow = (NotificationSettingsIconRow) inflated;
    861                 mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
    862                 mSettingsIconRow.setAppName(mAppName);
    863             }
    864         });
    865         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
    866         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    867             @Override
    868             public void onInflate(ViewStub stub, View inflated) {
    869                 mGuts = (NotificationGuts) inflated;
    870                 mGuts.setClipTopAmount(getClipTopAmount());
    871                 mGuts.setActualHeight(getActualHeight());
    872                 mGutsStub = null;
    873             }
    874         });
    875         mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
    876         mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    877 
    878             @Override
    879             public void onInflate(ViewStub stub, View inflated) {
    880                 mChildrenContainer = (NotificationChildrenContainer) inflated;
    881                 mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this);
    882                 mChildrenContainer.onNotificationUpdated();
    883                 mTranslateableViews.add(mChildrenContainer);
    884             }
    885         });
    886         mVetoButton = findViewById(R.id.veto);
    887         mVetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
    888         mVetoButton.setContentDescription(mContext.getString(
    889                 R.string.accessibility_remove_notification));
    890 
    891         // Add the views that we translate to reveal the gear
    892         mTranslateableViews = new ArrayList<View>();
    893         for (int i = 0; i < getChildCount(); i++) {
    894             mTranslateableViews.add(getChildAt(i));
    895         }
    896         // Remove views that don't translate
    897         mTranslateableViews.remove(mVetoButton);
    898         mTranslateableViews.remove(mSettingsIconRowStub);
    899         mTranslateableViews.remove(mChildrenContainerStub);
    900         mTranslateableViews.remove(mGutsStub);
    901     }
    902 
    903     public View getVetoButton() {
    904         return mVetoButton;
    905     }
    906 
    907     public void resetTranslation() {
    908         if (mTranslateAnim != null) {
    909             mTranslateAnim.cancel();
    910         }
    911         if (mTranslateableViews != null) {
    912             for (int i = 0; i < mTranslateableViews.size(); i++) {
    913                 mTranslateableViews.get(i).setTranslationX(0);
    914             }
    915         }
    916         invalidateOutline();
    917         if (mSettingsIconRow != null) {
    918             mSettingsIconRow.resetState();
    919         }
    920     }
    921 
    922     public void animateTranslateNotification(final float leftTarget) {
    923         if (mTranslateAnim != null) {
    924             mTranslateAnim.cancel();
    925         }
    926         mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */);
    927         if (mTranslateAnim != null) {
    928             mTranslateAnim.start();
    929         }
    930     }
    931 
    932     @Override
    933     public void setTranslation(float translationX) {
    934         if (areGutsExposed()) {
    935             // Don't translate if guts are showing.
    936             return;
    937         }
    938         // Translate the group of views
    939         for (int i = 0; i < mTranslateableViews.size(); i++) {
    940             if (mTranslateableViews.get(i) != null) {
    941                 mTranslateableViews.get(i).setTranslationX(translationX);
    942             }
    943         }
    944         invalidateOutline();
    945         if (mSettingsIconRow != null) {
    946             mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth());
    947         }
    948     }
    949 
    950     @Override
    951     public float getTranslation() {
    952         if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
    953             // All of the views in the list should have same translation, just use first one.
    954             return mTranslateableViews.get(0).getTranslationX();
    955         }
    956         return 0;
    957     }
    958 
    959     public Animator getTranslateViewAnimator(final float leftTarget,
    960             AnimatorUpdateListener listener) {
    961         if (mTranslateAnim != null) {
    962             mTranslateAnim.cancel();
    963         }
    964         if (areGutsExposed()) {
    965             // No translation if guts are exposed.
    966             return null;
    967         }
    968         final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT,
    969                 leftTarget);
    970         if (listener != null) {
    971             translateAnim.addUpdateListener(listener);
    972         }
    973         translateAnim.addListener(new AnimatorListenerAdapter() {
    974             boolean cancelled = false;
    975 
    976             @Override
    977             public void onAnimationCancel(Animator anim) {
    978                 cancelled = true;
    979             }
    980 
    981             @Override
    982             public void onAnimationEnd(Animator anim) {
    983                 if (!cancelled && mSettingsIconRow != null && leftTarget == 0) {
    984                     mSettingsIconRow.resetState();
    985                     mTranslateAnim = null;
    986                 }
    987             }
    988         });
    989         mTranslateAnim = translateAnim;
    990         return translateAnim;
    991     }
    992 
    993     public float getSpaceForGear() {
    994         if (mSettingsIconRow != null) {
    995             return mSettingsIconRow.getSpaceForGear();
    996         }
    997         return 0;
    998     }
    999 
   1000     public NotificationSettingsIconRow getSettingsRow() {
   1001         if (mSettingsIconRow == null) {
   1002             mSettingsIconRowStub.inflate();
   1003         }
   1004         return mSettingsIconRow;
   1005     }
   1006 
   1007     public void inflateGuts() {
   1008         if (mGuts == null) {
   1009             mGutsStub.inflate();
   1010         }
   1011     }
   1012 
   1013     private void updateChildrenVisibility() {
   1014         mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
   1015                 : INVISIBLE);
   1016         if (mChildrenContainer != null) {
   1017             mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
   1018                     : INVISIBLE);
   1019             mChildrenContainer.updateHeaderVisibility(!mShowingPublic && mIsSummaryWithChildren
   1020                     ? VISIBLE
   1021                     : INVISIBLE);
   1022         }
   1023         // The limits might have changed if the view suddenly became a group or vice versa
   1024         updateLimits();
   1025     }
   1026 
   1027     @Override
   1028     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
   1029         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
   1030             // Add a record for the entire layout since its content is somehow small.
   1031             // The event comes from a leaf view that is interacted with.
   1032             AccessibilityEvent record = AccessibilityEvent.obtain();
   1033             onInitializeAccessibilityEvent(record);
   1034             dispatchPopulateAccessibilityEvent(record);
   1035             event.appendRecord(record);
   1036             return true;
   1037         }
   1038         return false;
   1039     }
   1040 
   1041     @Override
   1042     public void setDark(boolean dark, boolean fade, long delay) {
   1043         super.setDark(dark, fade, delay);
   1044         final NotificationContentView showing = getShowingLayout();
   1045         if (showing != null) {
   1046             showing.setDark(dark, fade, delay);
   1047         }
   1048         if (mIsSummaryWithChildren) {
   1049             mChildrenContainer.setDark(dark, fade, delay);
   1050         }
   1051     }
   1052 
   1053     public boolean isExpandable() {
   1054         if (mIsSummaryWithChildren && !mShowingPublic) {
   1055             return !mChildrenExpanded;
   1056         }
   1057         return mExpandable;
   1058     }
   1059 
   1060     public void setExpandable(boolean expandable) {
   1061         mExpandable = expandable;
   1062         mPrivateLayout.updateExpandButtons(isExpandable());
   1063     }
   1064 
   1065     @Override
   1066     public void setClipToActualHeight(boolean clipToActualHeight) {
   1067         super.setClipToActualHeight(clipToActualHeight || isUserLocked());
   1068         getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked());
   1069     }
   1070 
   1071     /**
   1072      * @return whether the user has changed the expansion state
   1073      */
   1074     public boolean hasUserChangedExpansion() {
   1075         return mHasUserChangedExpansion;
   1076     }
   1077 
   1078     public boolean isUserExpanded() {
   1079         return mUserExpanded;
   1080     }
   1081 
   1082     /**
   1083      * Set this notification to be expanded by the user
   1084      *
   1085      * @param userExpanded whether the user wants this notification to be expanded
   1086      */
   1087     public void setUserExpanded(boolean userExpanded) {
   1088         setUserExpanded(userExpanded, false /* allowChildExpansion */);
   1089     }
   1090 
   1091     /**
   1092      * Set this notification to be expanded by the user
   1093      *
   1094      * @param userExpanded whether the user wants this notification to be expanded
   1095      * @param allowChildExpansion whether a call to this method allows expanding children
   1096      */
   1097     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
   1098         mFalsingManager.setNotificationExpanded();
   1099         if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion) {
   1100             final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
   1101             mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded);
   1102             logExpansionEvent(true /* userAction */, wasExpanded);
   1103             return;
   1104         }
   1105         if (userExpanded && !mExpandable) return;
   1106         final boolean wasExpanded = isExpanded();
   1107         mHasUserChangedExpansion = true;
   1108         mUserExpanded = userExpanded;
   1109         logExpansionEvent(true, wasExpanded);
   1110     }
   1111 
   1112     public void resetUserExpansion() {
   1113         mHasUserChangedExpansion = false;
   1114         mUserExpanded = false;
   1115     }
   1116 
   1117     public boolean isUserLocked() {
   1118         return mUserLocked && !mForceUnlocked;
   1119     }
   1120 
   1121     public void setUserLocked(boolean userLocked) {
   1122         mUserLocked = userLocked;
   1123         mPrivateLayout.setUserExpanding(userLocked);
   1124         if (mIsSummaryWithChildren) {
   1125             mChildrenContainer.setUserLocked(userLocked);
   1126             if (userLocked || !isGroupExpanded()) {
   1127                 updateBackgroundForGroupState();
   1128             }
   1129         }
   1130     }
   1131 
   1132     /**
   1133      * @return has the system set this notification to be expanded
   1134      */
   1135     public boolean isSystemExpanded() {
   1136         return mIsSystemExpanded;
   1137     }
   1138 
   1139     /**
   1140      * Set this notification to be expanded by the system.
   1141      *
   1142      * @param expand whether the system wants this notification to be expanded.
   1143      */
   1144     public void setSystemExpanded(boolean expand) {
   1145         if (expand != mIsSystemExpanded) {
   1146             final boolean wasExpanded = isExpanded();
   1147             mIsSystemExpanded = expand;
   1148             notifyHeightChanged(false /* needsAnimation */);
   1149             logExpansionEvent(false, wasExpanded);
   1150             if (mIsSummaryWithChildren) {
   1151                 mChildrenContainer.updateGroupOverflow();
   1152             }
   1153         }
   1154     }
   1155 
   1156     /**
   1157      * @param onKeyguard whether to prevent notification expansion
   1158      */
   1159     public void setOnKeyguard(boolean onKeyguard) {
   1160         if (onKeyguard != mOnKeyguard) {
   1161             final boolean wasExpanded = isExpanded();
   1162             mOnKeyguard = onKeyguard;
   1163             logExpansionEvent(false, wasExpanded);
   1164             if (wasExpanded != isExpanded()) {
   1165                 if (mIsSummaryWithChildren) {
   1166                     mChildrenContainer.updateGroupOverflow();
   1167                 }
   1168                 notifyHeightChanged(false /* needsAnimation */);
   1169             }
   1170         }
   1171     }
   1172 
   1173     /**
   1174      * @return Can the underlying notification be cleared? This can be different from whether the
   1175      *         notification can be dismissed in case notifications are sensitive on the lockscreen.
   1176      * @see #canViewBeDismissed()
   1177      */
   1178     public boolean isClearable() {
   1179         if (mStatusBarNotification == null || !mStatusBarNotification.isClearable()) {
   1180             return false;
   1181         }
   1182         if (mIsSummaryWithChildren) {
   1183             List<ExpandableNotificationRow> notificationChildren =
   1184                     mChildrenContainer.getNotificationChildren();
   1185             for (int i = 0; i < notificationChildren.size(); i++) {
   1186                 ExpandableNotificationRow child = notificationChildren.get(i);
   1187                 if (!child.isClearable()) {
   1188                     return false;
   1189                 }
   1190             }
   1191         }
   1192         return true;
   1193     }
   1194 
   1195     @Override
   1196     public int getIntrinsicHeight() {
   1197         if (isUserLocked()) {
   1198             return getActualHeight();
   1199         }
   1200         if (mGuts != null && mGuts.areGutsExposed()) {
   1201             return mGuts.getHeight();
   1202         } else if ((isChildInGroup() && !isGroupExpanded())) {
   1203             return mPrivateLayout.getMinHeight();
   1204         } else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
   1205             return getMinHeight();
   1206         } else if (mIsSummaryWithChildren && !mOnKeyguard) {
   1207             return mChildrenContainer.getIntrinsicHeight();
   1208         } else if (mIsHeadsUp || mHeadsupDisappearRunning) {
   1209             if (isPinned() || mHeadsupDisappearRunning) {
   1210                 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
   1211             } else if (isExpanded()) {
   1212                 return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
   1213             } else {
   1214                 return Math.max(getCollapsedHeight(), mHeadsUpHeight);
   1215             }
   1216         } else if (isExpanded()) {
   1217             return getMaxExpandHeight();
   1218         } else {
   1219             return getCollapsedHeight();
   1220         }
   1221     }
   1222 
   1223     public boolean isGroupExpanded() {
   1224         return mGroupManager.isGroupExpanded(mStatusBarNotification);
   1225     }
   1226 
   1227     private void onChildrenCountChanged() {
   1228         mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
   1229                 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
   1230         if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
   1231             mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
   1232                     mEntry.notification);
   1233         }
   1234         getShowingLayout().updateBackgroundColor(false /* animate */);
   1235         mPrivateLayout.updateExpandButtons(isExpandable());
   1236         updateChildrenHeaderAppearance();
   1237         updateChildrenVisibility();
   1238     }
   1239 
   1240     public void updateChildrenHeaderAppearance() {
   1241         if (mIsSummaryWithChildren) {
   1242             mChildrenContainer.updateChildrenHeaderAppearance();
   1243         }
   1244     }
   1245 
   1246     /**
   1247      * Check whether the view state is currently expanded. This is given by the system in {@link
   1248      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
   1249      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
   1250      * view can differ from this state, if layout params are modified from outside.
   1251      *
   1252      * @return whether the view state is currently expanded.
   1253      */
   1254     public boolean isExpanded() {
   1255         return isExpanded(false /* allowOnKeyguard */);
   1256     }
   1257 
   1258     public boolean isExpanded(boolean allowOnKeyguard) {
   1259         return (!mOnKeyguard || allowOnKeyguard)
   1260                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
   1261                 || isUserExpanded());
   1262     }
   1263 
   1264     private boolean isSystemChildExpanded() {
   1265         return mIsSystemChildExpanded;
   1266     }
   1267 
   1268     public void setSystemChildExpanded(boolean expanded) {
   1269         mIsSystemChildExpanded = expanded;
   1270     }
   1271 
   1272     @Override
   1273     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   1274         super.onLayout(changed, left, top, right, bottom);
   1275         updateMaxHeights();
   1276         if (mSettingsIconRow != null) {
   1277             mSettingsIconRow.updateVerticalLocation();
   1278         }
   1279     }
   1280 
   1281     private void updateMaxHeights() {
   1282         int intrinsicBefore = getIntrinsicHeight();
   1283         View expandedChild = mPrivateLayout.getExpandedChild();
   1284         if (expandedChild == null) {
   1285             expandedChild = mPrivateLayout.getContractedChild();
   1286         }
   1287         mMaxExpandHeight = expandedChild.getHeight();
   1288         View headsUpChild = mPrivateLayout.getHeadsUpChild();
   1289         if (headsUpChild == null) {
   1290             headsUpChild = mPrivateLayout.getContractedChild();
   1291         }
   1292         mHeadsUpHeight = headsUpChild.getHeight();
   1293         if (intrinsicBefore != getIntrinsicHeight()) {
   1294             notifyHeightChanged(true  /* needsAnimation */);
   1295         }
   1296     }
   1297 
   1298     @Override
   1299     public void notifyHeightChanged(boolean needsAnimation) {
   1300         super.notifyHeightChanged(needsAnimation);
   1301         getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
   1302     }
   1303 
   1304     public void setSensitive(boolean sensitive, boolean hideSensitive) {
   1305         mSensitive = sensitive;
   1306         mSensitiveHiddenInGeneral = hideSensitive;
   1307     }
   1308 
   1309     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
   1310         mHideSensitiveForIntrinsicHeight = hideSensitive;
   1311         if (mIsSummaryWithChildren) {
   1312             List<ExpandableNotificationRow> notificationChildren =
   1313                     mChildrenContainer.getNotificationChildren();
   1314             for (int i = 0; i < notificationChildren.size(); i++) {
   1315                 ExpandableNotificationRow child = notificationChildren.get(i);
   1316                 child.setHideSensitiveForIntrinsicHeight(hideSensitive);
   1317             }
   1318         }
   1319     }
   1320 
   1321     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
   1322             long duration) {
   1323         boolean oldShowingPublic = mShowingPublic;
   1324         mShowingPublic = mSensitive && hideSensitive;
   1325         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
   1326             return;
   1327         }
   1328 
   1329         // bail out if no public version
   1330         if (mPublicLayout.getChildCount() == 0) return;
   1331 
   1332         if (!animated) {
   1333             mPublicLayout.animate().cancel();
   1334             mPrivateLayout.animate().cancel();
   1335             if (mChildrenContainer != null) {
   1336                 mChildrenContainer.animate().cancel();
   1337                 mChildrenContainer.setAlpha(1f);
   1338             }
   1339             mPublicLayout.setAlpha(1f);
   1340             mPrivateLayout.setAlpha(1f);
   1341             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
   1342             updateChildrenVisibility();
   1343         } else {
   1344             animateShowingPublic(delay, duration);
   1345         }
   1346         NotificationContentView showingLayout = getShowingLayout();
   1347         showingLayout.updateBackgroundColor(animated);
   1348         mPrivateLayout.updateExpandButtons(isExpandable());
   1349         mShowingPublicInitialized = true;
   1350     }
   1351 
   1352     private void animateShowingPublic(long delay, long duration) {
   1353         View[] privateViews = mIsSummaryWithChildren
   1354                 ? new View[] {mChildrenContainer}
   1355                 : new View[] {mPrivateLayout};
   1356         View[] publicViews = new View[] {mPublicLayout};
   1357         View[] hiddenChildren = mShowingPublic ? privateViews : publicViews;
   1358         View[] shownChildren = mShowingPublic ? publicViews : privateViews;
   1359         for (final View hiddenView : hiddenChildren) {
   1360             hiddenView.setVisibility(View.VISIBLE);
   1361             hiddenView.animate().cancel();
   1362             hiddenView.animate()
   1363                     .alpha(0f)
   1364                     .setStartDelay(delay)
   1365                     .setDuration(duration)
   1366                     .withEndAction(new Runnable() {
   1367                         @Override
   1368                         public void run() {
   1369                             hiddenView.setVisibility(View.INVISIBLE);
   1370                         }
   1371                     });
   1372         }
   1373         for (View showView : shownChildren) {
   1374             showView.setVisibility(View.VISIBLE);
   1375             showView.setAlpha(0f);
   1376             showView.animate().cancel();
   1377             showView.animate()
   1378                     .alpha(1f)
   1379                     .setStartDelay(delay)
   1380                     .setDuration(duration);
   1381         }
   1382     }
   1383 
   1384     public boolean mustStayOnScreen() {
   1385         return mIsHeadsUp;
   1386     }
   1387 
   1388     /**
   1389      * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
   1390      *         otherwise some state might not be updated. To request about the general clearability
   1391      *         see {@link #isClearable()}.
   1392      */
   1393     public boolean canViewBeDismissed() {
   1394         return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral);
   1395     }
   1396 
   1397     public void makeActionsVisibile() {
   1398         setUserExpanded(true, true);
   1399         if (isChildInGroup()) {
   1400             mGroupManager.setGroupExpanded(mStatusBarNotification, true);
   1401         }
   1402         notifyHeightChanged(false /* needsAnimation */);
   1403     }
   1404 
   1405     public void setChildrenExpanded(boolean expanded, boolean animate) {
   1406         mChildrenExpanded = expanded;
   1407         if (mChildrenContainer != null) {
   1408             mChildrenContainer.setChildrenExpanded(expanded);
   1409         }
   1410         updateBackgroundForGroupState();
   1411         updateClickAndFocus();
   1412     }
   1413 
   1414     public static void applyTint(View v, int color) {
   1415         int alpha;
   1416         if (color != 0) {
   1417             alpha = COLORED_DIVIDER_ALPHA;
   1418         } else {
   1419             color = 0xff000000;
   1420             alpha = DEFAULT_DIVIDER_ALPHA;
   1421         }
   1422         if (v.getBackground() instanceof ColorDrawable) {
   1423             ColorDrawable background = (ColorDrawable) v.getBackground();
   1424             background.mutate();
   1425             background.setColor(color);
   1426             background.setAlpha(alpha);
   1427         }
   1428     }
   1429 
   1430     public int getMaxExpandHeight() {
   1431         return mMaxExpandHeight;
   1432     }
   1433 
   1434     public boolean areGutsExposed() {
   1435         return (mGuts != null && mGuts.areGutsExposed());
   1436     }
   1437 
   1438     @Override
   1439     public boolean isContentExpandable() {
   1440         NotificationContentView showingLayout = getShowingLayout();
   1441         return showingLayout.isContentExpandable();
   1442     }
   1443 
   1444     @Override
   1445     protected View getContentView() {
   1446         if (mIsSummaryWithChildren && !mShowingPublic) {
   1447             return mChildrenContainer;
   1448         }
   1449         return getShowingLayout();
   1450     }
   1451 
   1452     @Override
   1453     protected void onAppearAnimationFinished(boolean wasAppearing) {
   1454         super.onAppearAnimationFinished(wasAppearing);
   1455         if (wasAppearing) {
   1456             // During the animation the visible view might have changed, so let's make sure all
   1457             // alphas are reset
   1458             if (mChildrenContainer != null) {
   1459                 mChildrenContainer.setAlpha(1.0f);
   1460                 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
   1461             }
   1462             mPrivateLayout.setAlpha(1.0f);
   1463             mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null);
   1464             mPublicLayout.setAlpha(1.0f);
   1465             mPublicLayout.setLayerType(LAYER_TYPE_NONE, null);
   1466         }
   1467     }
   1468 
   1469     @Override
   1470     public int getExtraBottomPadding() {
   1471         if (mIsSummaryWithChildren && isGroupExpanded()) {
   1472             return mIncreasedPaddingBetweenElements;
   1473         }
   1474         return 0;
   1475     }
   1476 
   1477     @Override
   1478     public void setActualHeight(int height, boolean notifyListeners) {
   1479         super.setActualHeight(height, notifyListeners);
   1480         if (mGuts != null && mGuts.areGutsExposed()) {
   1481             mGuts.setActualHeight(height);
   1482             return;
   1483         }
   1484         int contentHeight = Math.max(getMinHeight(), height);
   1485         mPrivateLayout.setContentHeight(contentHeight);
   1486         mPublicLayout.setContentHeight(contentHeight);
   1487         if (mIsSummaryWithChildren) {
   1488             mChildrenContainer.setActualHeight(height);
   1489         }
   1490         if (mGuts != null) {
   1491             mGuts.setActualHeight(height);
   1492         }
   1493     }
   1494 
   1495     @Override
   1496     public int getMaxContentHeight() {
   1497         if (mIsSummaryWithChildren && !mShowingPublic) {
   1498             return mChildrenContainer.getMaxContentHeight();
   1499         }
   1500         NotificationContentView showingLayout = getShowingLayout();
   1501         return showingLayout.getMaxHeight();
   1502     }
   1503 
   1504     @Override
   1505     public int getMinHeight() {
   1506         if (mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
   1507                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
   1508         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
   1509             return mChildrenContainer.getMinHeight();
   1510         } else if (mIsHeadsUp) {
   1511             return mHeadsUpHeight;
   1512         }
   1513         NotificationContentView showingLayout = getShowingLayout();
   1514         return showingLayout.getMinHeight();
   1515     }
   1516 
   1517     @Override
   1518     public int getCollapsedHeight() {
   1519         if (mIsSummaryWithChildren && !mShowingPublic) {
   1520             return mChildrenContainer.getCollapsedHeight();
   1521         }
   1522         return getMinHeight();
   1523     }
   1524 
   1525     @Override
   1526     public void setClipTopAmount(int clipTopAmount) {
   1527         super.setClipTopAmount(clipTopAmount);
   1528         mPrivateLayout.setClipTopAmount(clipTopAmount);
   1529         mPublicLayout.setClipTopAmount(clipTopAmount);
   1530         if (mGuts != null) {
   1531             mGuts.setClipTopAmount(clipTopAmount);
   1532         }
   1533     }
   1534 
   1535     public boolean isMaxExpandHeightInitialized() {
   1536         return mMaxExpandHeight != 0;
   1537     }
   1538 
   1539     public NotificationContentView getShowingLayout() {
   1540         return mShowingPublic ? mPublicLayout : mPrivateLayout;
   1541     }
   1542 
   1543     @Override
   1544     public void setShowingLegacyBackground(boolean showing) {
   1545         super.setShowingLegacyBackground(showing);
   1546         mPrivateLayout.setShowingLegacyBackground(showing);
   1547         mPublicLayout.setShowingLegacyBackground(showing);
   1548     }
   1549 
   1550     @Override
   1551     protected void updateBackgroundTint() {
   1552         super.updateBackgroundTint();
   1553         updateBackgroundForGroupState();
   1554         if (mIsSummaryWithChildren) {
   1555             List<ExpandableNotificationRow> notificationChildren =
   1556                     mChildrenContainer.getNotificationChildren();
   1557             for (int i = 0; i < notificationChildren.size(); i++) {
   1558                 ExpandableNotificationRow child = notificationChildren.get(i);
   1559                 child.updateBackgroundForGroupState();
   1560             }
   1561         }
   1562     }
   1563 
   1564     /**
   1565      * Called when a group has finished animating from collapsed or expanded state.
   1566      */
   1567     public void onFinishedExpansionChange() {
   1568         mGroupExpansionChanging = false;
   1569         updateBackgroundForGroupState();
   1570     }
   1571 
   1572     /**
   1573      * Updates the parent and children backgrounds in a group based on the expansion state.
   1574      */
   1575     public void updateBackgroundForGroupState() {
   1576         if (mIsSummaryWithChildren) {
   1577             // Only when the group has finished expanding do we hide its background.
   1578             mShowNoBackground = isGroupExpanded() && !isGroupExpansionChanging() && !isUserLocked();
   1579             mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);
   1580             List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren();
   1581             for (int i = 0; i < children.size(); i++) {
   1582                 children.get(i).updateBackgroundForGroupState();
   1583             }
   1584         } else if (isChildInGroup()) {
   1585             final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
   1586             // Only show a background if the group is expanded OR if it is expanding / collapsing
   1587             // and has a custom background color
   1588             final boolean showBackground = isGroupExpanded()
   1589                     || ((mNotificationParent.isGroupExpansionChanging()
   1590                             || mNotificationParent.isUserLocked()) && childColor != 0);
   1591             mShowNoBackground = !showBackground;
   1592         } else {
   1593             // Only children or parents ever need no background.
   1594             mShowNoBackground = false;
   1595         }
   1596         updateOutline();
   1597         updateBackground();
   1598     }
   1599 
   1600     public int getPositionOfChild(ExpandableNotificationRow childRow) {
   1601         if (mIsSummaryWithChildren) {
   1602             return mChildrenContainer.getPositionInLinearLayout(childRow);
   1603         }
   1604         return 0;
   1605     }
   1606 
   1607     public void setExpansionLogger(ExpansionLogger logger, String key) {
   1608         mLogger = logger;
   1609         mLoggingKey = key;
   1610     }
   1611 
   1612     public void onExpandedByGesture(boolean userExpanded) {
   1613         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
   1614         if (mGroupManager.isSummaryOfGroup(getStatusBarNotification())) {
   1615             event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
   1616         }
   1617         MetricsLogger.action(mContext, event, userExpanded);
   1618     }
   1619 
   1620     @Override
   1621     public float getIncreasedPaddingAmount() {
   1622         if (mIsSummaryWithChildren) {
   1623             if (isGroupExpanded()) {
   1624                 return 1.0f;
   1625             } else if (isUserLocked()) {
   1626                 return mChildrenContainer.getGroupExpandFraction();
   1627             }
   1628         }
   1629         return 0.0f;
   1630     }
   1631 
   1632     @Override
   1633     protected boolean disallowSingleClick(MotionEvent event) {
   1634         float x = event.getX();
   1635         float y = event.getY();
   1636         NotificationHeaderView header = getVisibleNotificationHeader();
   1637         if (header != null) {
   1638             return header.isInTouchRect(x - getTranslation(), y);
   1639         }
   1640         return super.disallowSingleClick(event);
   1641     }
   1642 
   1643     private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
   1644         boolean nowExpanded = isExpanded();
   1645         if (mIsSummaryWithChildren) {
   1646             nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
   1647         }
   1648         if (wasExpanded != nowExpanded && mLogger != null) {
   1649             mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
   1650         }
   1651     }
   1652 
   1653     @Override
   1654     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
   1655         super.onInitializeAccessibilityNodeInfoInternal(info);
   1656         if (canViewBeDismissed()) {
   1657             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
   1658         }
   1659     }
   1660 
   1661     @Override
   1662     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
   1663         if (super.performAccessibilityActionInternal(action, arguments)) {
   1664             return true;
   1665         }
   1666         switch (action) {
   1667             case AccessibilityNodeInfo.ACTION_DISMISS:
   1668                 NotificationStackScrollLayout.performDismiss(this, mGroupManager,
   1669                         true /* fromAccessibility */);
   1670                 return true;
   1671         }
   1672         return false;
   1673     }
   1674 
   1675     public boolean shouldRefocusOnDismiss() {
   1676         return mRefocusOnDismiss || isAccessibilityFocused();
   1677     }
   1678 
   1679     public interface OnExpandClickListener {
   1680         void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
   1681     }
   1682 }
   1683