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.ValueAnimator;
     22 import android.content.Context;
     23 import android.graphics.drawable.AnimatedVectorDrawable;
     24 import android.graphics.drawable.AnimationDrawable;
     25 import android.graphics.drawable.ColorDrawable;
     26 import android.graphics.drawable.Drawable;
     27 import android.service.notification.StatusBarNotification;
     28 import android.util.AttributeSet;
     29 import android.view.MotionEvent;
     30 import android.view.View;
     31 import android.view.ViewStub;
     32 import android.view.accessibility.AccessibilityEvent;
     33 import android.view.animation.LinearInterpolator;
     34 import android.widget.ImageView;
     35 
     36 import com.android.systemui.R;
     37 import com.android.systemui.statusbar.phone.NotificationGroupManager;
     38 import com.android.systemui.statusbar.phone.PhoneStatusBar;
     39 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
     40 import com.android.systemui.statusbar.stack.StackScrollState;
     41 import com.android.systemui.statusbar.stack.StackStateAnimator;
     42 import com.android.systemui.statusbar.stack.StackViewState;
     43 
     44 import java.util.List;
     45 
     46 public class ExpandableNotificationRow extends ActivatableNotificationView {
     47 
     48     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
     49     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
     50     private final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
     51     private int mRowMinHeight;
     52 
     53     /** Does this row contain layouts that can adapt to row expansion */
     54     private boolean mExpandable;
     55     /** Has the user actively changed the expansion state of this row */
     56     private boolean mHasUserChangedExpansion;
     57     /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
     58     private boolean mUserExpanded;
     59     /** Is the user touching this row */
     60     private boolean mUserLocked;
     61     /** Are we showing the "public" version */
     62     private boolean mShowingPublic;
     63     private boolean mSensitive;
     64     private boolean mShowingPublicInitialized;
     65     private boolean mHideSensitiveForIntrinsicHeight;
     66 
     67     /**
     68      * Is this notification expanded by the system. The expansion state can be overridden by the
     69      * user expansion.
     70      */
     71     private boolean mIsSystemExpanded;
     72 
     73     /**
     74      * Whether the notification expansion is disabled. This is the case on Keyguard.
     75      */
     76     private boolean mExpansionDisabled;
     77 
     78     private NotificationContentView mPublicLayout;
     79     private NotificationContentView mPrivateLayout;
     80     private int mMaxExpandHeight;
     81     private int mHeadsUpHeight;
     82     private View mVetoButton;
     83     private boolean mClearable;
     84     private ExpansionLogger mLogger;
     85     private String mLoggingKey;
     86     private boolean mWasReset;
     87     private NotificationGuts mGuts;
     88     private StatusBarNotification mStatusBarNotification;
     89     private boolean mIsHeadsUp;
     90     private View mExpandButton;
     91     private View mExpandButtonDivider;
     92     private ViewStub mExpandButtonStub;
     93     private ViewStub mChildrenContainerStub;
     94     private NotificationGroupManager mGroupManager;
     95     private View mExpandButtonContainer;
     96     private boolean mChildrenExpanded;
     97     private NotificationChildrenContainer mChildrenContainer;
     98     private ValueAnimator mChildExpandAnimator;
     99     private float mChildrenExpandProgress;
    100     private float mExpandButtonStart;
    101     private ViewStub mGutsStub;
    102     private boolean mHasExpandAction;
    103     private boolean mIsSystemChildExpanded;
    104     private boolean mIsPinned;
    105     private OnClickListener mExpandClickListener = new OnClickListener() {
    106         @Override
    107         public void onClick(View v) {
    108             mGroupManager.setGroupExpanded(mStatusBarNotification,
    109                     !mChildrenExpanded);
    110         }
    111     };
    112 
    113     public NotificationContentView getPrivateLayout() {
    114         return mPrivateLayout;
    115     }
    116 
    117     public NotificationContentView getPublicLayout() {
    118         return mPublicLayout;
    119     }
    120 
    121     public void setIconAnimationRunning(boolean running) {
    122         setIconAnimationRunning(running, mPublicLayout);
    123         setIconAnimationRunning(running, mPrivateLayout);
    124     }
    125 
    126     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
    127         if (layout != null) {
    128             View contractedChild = layout.getContractedChild();
    129             View expandedChild = layout.getExpandedChild();
    130             View headsUpChild = layout.getHeadsUpChild();
    131             setIconAnimationRunningForChild(running, contractedChild);
    132             setIconAnimationRunningForChild(running, expandedChild);
    133             setIconAnimationRunningForChild(running, headsUpChild);
    134         }
    135     }
    136 
    137     private void setIconAnimationRunningForChild(boolean running, View child) {
    138         if (child != null) {
    139             ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
    140             setIconRunning(icon, running);
    141             ImageView rightIcon = (ImageView) child.findViewById(
    142                     com.android.internal.R.id.right_icon);
    143             setIconRunning(rightIcon, running);
    144         }
    145     }
    146 
    147     private void setIconRunning(ImageView imageView, boolean running) {
    148         if (imageView != null) {
    149             Drawable drawable = imageView.getDrawable();
    150             if (drawable instanceof AnimationDrawable) {
    151                 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
    152                 if (running) {
    153                     animationDrawable.start();
    154                 } else {
    155                     animationDrawable.stop();
    156                 }
    157             } else if (drawable instanceof AnimatedVectorDrawable) {
    158                 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
    159                 if (running) {
    160                     animationDrawable.start();
    161                 } else {
    162                     animationDrawable.stop();
    163                 }
    164             }
    165         }
    166     }
    167 
    168     public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
    169         mStatusBarNotification = statusBarNotification;
    170         updateVetoButton();
    171         updateExpandButton();
    172     }
    173 
    174     public StatusBarNotification getStatusBarNotification() {
    175         return mStatusBarNotification;
    176     }
    177 
    178     public boolean isHeadsUp() {
    179         return mIsHeadsUp;
    180     }
    181 
    182     public void setHeadsUp(boolean isHeadsUp) {
    183         int intrinsicBefore = getIntrinsicHeight();
    184         mIsHeadsUp = isHeadsUp;
    185         mPrivateLayout.setHeadsUp(isHeadsUp);
    186         if (intrinsicBefore != getIntrinsicHeight()) {
    187             notifyHeightChanged(false  /* needsAnimation */);
    188         }
    189     }
    190 
    191     public void setGroupManager(NotificationGroupManager groupManager) {
    192         mGroupManager = groupManager;
    193     }
    194 
    195     public void addChildNotification(ExpandableNotificationRow row) {
    196         addChildNotification(row, -1);
    197     }
    198 
    199     /**
    200      * Add a child notification to this view.
    201      *
    202      * @param row the row to add
    203      * @param childIndex the index to add it at, if -1 it will be added at the end
    204      */
    205     public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
    206         if (mChildrenContainer == null) {
    207             mChildrenContainerStub.inflate();
    208         }
    209         mChildrenContainer.addNotification(row, childIndex);
    210     }
    211 
    212     public void removeChildNotification(ExpandableNotificationRow row) {
    213         if (mChildrenContainer != null) {
    214             mChildrenContainer.removeNotification(row);
    215         }
    216     }
    217 
    218     @Override
    219     public boolean areChildrenExpanded() {
    220         return mChildrenExpanded;
    221     }
    222 
    223     public List<ExpandableNotificationRow> getNotificationChildren() {
    224         return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
    225     }
    226 
    227     /**
    228      * Apply the order given in the list to the children.
    229      *
    230      * @param childOrder the new list order
    231      * @return whether the list order has changed
    232      */
    233     public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
    234         return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder);
    235     }
    236 
    237     public void getChildrenStates(StackScrollState resultState) {
    238         if (mChildrenExpanded) {
    239             StackViewState parentState = resultState.getViewStateForView(this);
    240             mChildrenContainer.getState(resultState, parentState);
    241         }
    242     }
    243 
    244     public void applyChildrenState(StackScrollState state) {
    245         if (mChildrenExpanded) {
    246             mChildrenContainer.applyState(state);
    247         }
    248     }
    249 
    250     public void prepareExpansionChanged(StackScrollState state) {
    251         if (mChildrenExpanded) {
    252             mChildrenContainer.prepareExpansionChanged(state);
    253         }
    254     }
    255 
    256     public void startChildAnimation(StackScrollState finalState,
    257             StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
    258         if (mChildrenExpanded) {
    259             mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
    260                     duration);
    261         }
    262     }
    263 
    264     public ExpandableNotificationRow getViewAtPosition(float y) {
    265         if (!mChildrenExpanded) {
    266             return this;
    267         } else {
    268             ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
    269             return view == null ? this : view;
    270         }
    271     }
    272 
    273     public NotificationGuts getGuts() {
    274         return mGuts;
    275     }
    276 
    277     protected int calculateContentHeightFromActualHeight(int actualHeight) {
    278         int realActualHeight = actualHeight;
    279         if (hasBottomDecor()) {
    280             realActualHeight -= getBottomDecorHeight();
    281         }
    282         realActualHeight = Math.max(getMinHeight(), realActualHeight);
    283         return realActualHeight;
    284     }
    285 
    286     /**
    287      * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
    288      * the notification will be rendered on top of the screen.
    289      *
    290      * @param pinned whether it is pinned
    291      */
    292     public void setPinned(boolean pinned) {
    293         mIsPinned = pinned;
    294     }
    295 
    296     public boolean isPinned() {
    297         return mIsPinned;
    298     }
    299 
    300     public int getHeadsUpHeight() {
    301         return mHeadsUpHeight;
    302     }
    303 
    304     public interface ExpansionLogger {
    305         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
    306     }
    307 
    308     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
    309         super(context, attrs);
    310     }
    311 
    312     /**
    313      * Resets this view so it can be re-used for an updated notification.
    314      */
    315     @Override
    316     public void reset() {
    317         super.reset();
    318         mRowMinHeight = 0;
    319         final boolean wasExpanded = isExpanded();
    320         mMaxViewHeight = 0;
    321         mExpandable = false;
    322         mHasUserChangedExpansion = false;
    323         mUserLocked = false;
    324         mShowingPublic = false;
    325         mSensitive = false;
    326         mShowingPublicInitialized = false;
    327         mIsSystemExpanded = false;
    328         mExpansionDisabled = false;
    329         mPublicLayout.reset(mIsHeadsUp);
    330         mPrivateLayout.reset(mIsHeadsUp);
    331         resetHeight();
    332         logExpansionEvent(false, wasExpanded);
    333     }
    334 
    335     public void resetHeight() {
    336         if (mIsHeadsUp) {
    337             resetActualHeight();
    338         }
    339         mMaxExpandHeight = 0;
    340         mHeadsUpHeight = 0;
    341         mWasReset = true;
    342         onHeightReset();
    343         requestLayout();
    344     }
    345 
    346     @Override
    347     protected boolean filterMotionEvent(MotionEvent event) {
    348         return mIsHeadsUp || super.filterMotionEvent(event);
    349     }
    350 
    351     @Override
    352     protected void onFinishInflate() {
    353         super.onFinishInflate();
    354         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
    355         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
    356         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
    357         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    358             @Override
    359             public void onInflate(ViewStub stub, View inflated) {
    360                 mGuts = (NotificationGuts) inflated;
    361                 mGuts.setClipTopAmount(getClipTopAmount());
    362                 mGuts.setActualHeight(getActualHeight());
    363                 mGutsStub = null;
    364             }
    365         });
    366         mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub);
    367         mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    368 
    369             @Override
    370             public void onInflate(ViewStub stub, View inflated) {
    371                 mExpandButtonContainer = inflated;
    372                 mExpandButton = inflated.findViewById(R.id.notification_expand_button);
    373                 mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider);
    374                 mExpandButtonContainer.setOnClickListener(mExpandClickListener);
    375             }
    376         });
    377         mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
    378         mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    379 
    380             @Override
    381             public void onInflate(ViewStub stub, View inflated) {
    382                 mChildrenContainer = (NotificationChildrenContainer) inflated;
    383                 mChildrenContainer.setCollapseClickListener(mExpandClickListener);
    384                 updateChildrenVisibility(false);
    385             }
    386         });
    387         mVetoButton = findViewById(R.id.veto);
    388     }
    389 
    390     public void inflateGuts() {
    391         if (mGuts == null) {
    392             mGutsStub.inflate();
    393         }
    394     }
    395 
    396     private void updateChildrenVisibility(boolean animated) {
    397         if (mChildrenContainer == null) {
    398             return;
    399         }
    400         if (mChildExpandAnimator != null) {
    401             mChildExpandAnimator.cancel();
    402         }
    403         float targetProgress = mChildrenExpanded ? 1.0f : 0.0f;
    404         if (animated) {
    405             if (mChildrenExpanded) {
    406                 mChildrenContainer.setVisibility(VISIBLE);
    407             }
    408             mExpandButtonStart = mExpandButtonContainer.getTranslationY();
    409             mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress);
    410             mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    411                 @Override
    412                 public void onAnimationUpdate(ValueAnimator animation) {
    413                     setChildrenExpandProgress((float) animation.getAnimatedValue());
    414                 }
    415             });
    416             mChildExpandAnimator.addListener(new AnimatorListenerAdapter() {
    417                 @Override
    418                 public void onAnimationEnd(Animator animation) {
    419                     mChildExpandAnimator = null;
    420                     if (!mChildrenExpanded) {
    421                         mChildrenContainer.setVisibility(INVISIBLE);
    422                     }
    423                 }
    424             });
    425             mChildExpandAnimator.setInterpolator(mLinearInterpolator);
    426             mChildExpandAnimator.setDuration(
    427                     StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED);
    428             mChildExpandAnimator.start();
    429         } else {
    430             setChildrenExpandProgress(targetProgress);
    431             mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE);
    432         }
    433     }
    434 
    435     private void setChildrenExpandProgress(float progress) {
    436         mChildrenExpandProgress = progress;
    437         updateExpandButtonAppearance();
    438         NotificationContentView showingLayout = getShowingLayout();
    439         float alpha = 1.0f - mChildrenExpandProgress;
    440         alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha);
    441         showingLayout.setAlpha(alpha);
    442     }
    443 
    444     @Override
    445     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
    446         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
    447             // Add a record for the entire layout since its content is somehow small.
    448             // The event comes from a leaf view that is interacted with.
    449             AccessibilityEvent record = AccessibilityEvent.obtain();
    450             onInitializeAccessibilityEvent(record);
    451             dispatchPopulateAccessibilityEvent(record);
    452             event.appendRecord(record);
    453             return true;
    454         }
    455         return false;
    456     }
    457 
    458     @Override
    459     public void setDark(boolean dark, boolean fade, long delay) {
    460         super.setDark(dark, fade, delay);
    461         final NotificationContentView showing = getShowingLayout();
    462         if (showing != null) {
    463             showing.setDark(dark, fade, delay);
    464         }
    465     }
    466 
    467     public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
    468         mRowMinHeight = rowMinHeight;
    469         mMaxViewHeight = rowMaxHeight;
    470     }
    471 
    472     public boolean isExpandable() {
    473         return mExpandable;
    474     }
    475 
    476     public void setExpandable(boolean expandable) {
    477         mExpandable = expandable;
    478     }
    479 
    480     /**
    481      * @return whether the user has changed the expansion state
    482      */
    483     public boolean hasUserChangedExpansion() {
    484         return mHasUserChangedExpansion;
    485     }
    486 
    487     public boolean isUserExpanded() {
    488         return mUserExpanded;
    489     }
    490 
    491     /**
    492      * Set this notification to be expanded by the user
    493      *
    494      * @param userExpanded whether the user wants this notification to be expanded
    495      */
    496     public void setUserExpanded(boolean userExpanded) {
    497         if (userExpanded && !mExpandable) return;
    498         final boolean wasExpanded = isExpanded();
    499         mHasUserChangedExpansion = true;
    500         mUserExpanded = userExpanded;
    501         logExpansionEvent(true, wasExpanded);
    502     }
    503 
    504     public void resetUserExpansion() {
    505         mHasUserChangedExpansion = false;
    506         mUserExpanded = false;
    507     }
    508 
    509     public boolean isUserLocked() {
    510         return mUserLocked;
    511     }
    512 
    513     public void setUserLocked(boolean userLocked) {
    514         mUserLocked = userLocked;
    515     }
    516 
    517     /**
    518      * @return has the system set this notification to be expanded
    519      */
    520     public boolean isSystemExpanded() {
    521         return mIsSystemExpanded;
    522     }
    523 
    524     /**
    525      * Set this notification to be expanded by the system.
    526      *
    527      * @param expand whether the system wants this notification to be expanded.
    528      */
    529     public void setSystemExpanded(boolean expand) {
    530         if (expand != mIsSystemExpanded) {
    531             final boolean wasExpanded = isExpanded();
    532             mIsSystemExpanded = expand;
    533             notifyHeightChanged(false /* needsAnimation */);
    534             logExpansionEvent(false, wasExpanded);
    535         }
    536     }
    537 
    538     /**
    539      * @param expansionDisabled whether to prevent notification expansion
    540      */
    541     public void setExpansionDisabled(boolean expansionDisabled) {
    542         if (expansionDisabled != mExpansionDisabled) {
    543             final boolean wasExpanded = isExpanded();
    544             mExpansionDisabled = expansionDisabled;
    545             logExpansionEvent(false, wasExpanded);
    546             if (wasExpanded != isExpanded()) {
    547                 notifyHeightChanged(false  /* needsAnimation */);
    548             }
    549         }
    550     }
    551 
    552     /**
    553      * @return Can the underlying notification be cleared?
    554      */
    555     public boolean isClearable() {
    556         return mStatusBarNotification != null && mStatusBarNotification.isClearable();
    557     }
    558 
    559     /**
    560      * Apply an expansion state to the layout.
    561      */
    562     public void applyExpansionToLayout() {
    563         boolean expand = isExpanded();
    564         if (expand && mExpandable) {
    565             setContentHeight(mMaxExpandHeight);
    566         } else {
    567             setContentHeight(mRowMinHeight);
    568         }
    569     }
    570 
    571     @Override
    572     public int getIntrinsicHeight() {
    573         if (isUserLocked()) {
    574             return getActualHeight();
    575         }
    576         boolean inExpansionState = isExpanded();
    577         int maxContentHeight;
    578         if (mSensitive && mHideSensitiveForIntrinsicHeight) {
    579             return mRowMinHeight;
    580         } else if (mIsHeadsUp) {
    581             if (inExpansionState) {
    582                 maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight);
    583             } else {
    584                 maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight);
    585             }
    586         } else if ((!inExpansionState && !mChildrenExpanded)) {
    587             maxContentHeight = mRowMinHeight;
    588         } else if (mChildrenExpanded) {
    589             maxContentHeight = mChildrenContainer.getIntrinsicHeight();
    590         } else {
    591             maxContentHeight = getMaxExpandHeight();
    592         }
    593         return maxContentHeight + getBottomDecorHeight();
    594     }
    595 
    596     @Override
    597     protected boolean hasBottomDecor() {
    598         return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
    599                 && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification);
    600     }
    601 
    602     @Override
    603     protected boolean canHaveBottomDecor() {
    604         return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp;
    605     }
    606 
    607     /**
    608      * Check whether the view state is currently expanded. This is given by the system in {@link
    609      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
    610      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
    611      * view can differ from this state, if layout params are modified from outside.
    612      *
    613      * @return whether the view state is currently expanded.
    614      */
    615     private boolean isExpanded() {
    616         return !mExpansionDisabled
    617                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
    618                 || isUserExpanded());
    619     }
    620 
    621     private boolean isSystemChildExpanded() {
    622         return mIsSystemChildExpanded;
    623     }
    624 
    625     public void setSystemChildExpanded(boolean expanded) {
    626         mIsSystemChildExpanded = expanded;
    627     }
    628 
    629     @Override
    630     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    631         super.onLayout(changed, left, top, right, bottom);
    632         boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset;
    633         updateMaxHeights();
    634         if (updateExpandHeight) {
    635             applyExpansionToLayout();
    636         }
    637         mWasReset = false;
    638     }
    639 
    640     @Override
    641     protected boolean isChildInvisible(View child) {
    642 
    643         // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the
    644         // view will get too high and the shadows will be off.
    645         boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp;
    646         return super.isChildInvisible(child) || isInvisibleChildContainer;
    647     }
    648 
    649     private void updateMaxHeights() {
    650         int intrinsicBefore = getIntrinsicHeight();
    651         View expandedChild = mPrivateLayout.getExpandedChild();
    652         if (expandedChild == null) {
    653             expandedChild = mPrivateLayout.getContractedChild();
    654         }
    655         mMaxExpandHeight = expandedChild.getHeight();
    656         View headsUpChild = mPrivateLayout.getHeadsUpChild();
    657         if (headsUpChild == null) {
    658             headsUpChild = mPrivateLayout.getContractedChild();
    659         }
    660         mHeadsUpHeight = headsUpChild.getHeight();
    661         if (intrinsicBefore != getIntrinsicHeight()) {
    662             notifyHeightChanged(false  /* needsAnimation */);
    663         }
    664     }
    665 
    666     public void setSensitive(boolean sensitive) {
    667         mSensitive = sensitive;
    668     }
    669 
    670     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
    671         mHideSensitiveForIntrinsicHeight = hideSensitive;
    672     }
    673 
    674     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
    675             long duration) {
    676         boolean oldShowingPublic = mShowingPublic;
    677         mShowingPublic = mSensitive && hideSensitive;
    678         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
    679             return;
    680         }
    681 
    682         // bail out if no public version
    683         if (mPublicLayout.getChildCount() == 0) return;
    684 
    685         if (!animated) {
    686             mPublicLayout.animate().cancel();
    687             mPrivateLayout.animate().cancel();
    688             mPublicLayout.setAlpha(1f);
    689             mPrivateLayout.setAlpha(1f);
    690             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
    691             mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE);
    692         } else {
    693             animateShowingPublic(delay, duration);
    694         }
    695 
    696         updateVetoButton();
    697         mShowingPublicInitialized = true;
    698     }
    699 
    700     private void animateShowingPublic(long delay, long duration) {
    701         final View source = mShowingPublic ? mPrivateLayout : mPublicLayout;
    702         View target = mShowingPublic ? mPublicLayout : mPrivateLayout;
    703         source.setVisibility(View.VISIBLE);
    704         target.setVisibility(View.VISIBLE);
    705         target.setAlpha(0f);
    706         source.animate().cancel();
    707         target.animate().cancel();
    708         source.animate()
    709                 .alpha(0f)
    710                 .setStartDelay(delay)
    711                 .setDuration(duration)
    712                 .withEndAction(new Runnable() {
    713                     @Override
    714                     public void run() {
    715                         source.setVisibility(View.INVISIBLE);
    716                     }
    717                 });
    718         target.animate()
    719                 .alpha(1f)
    720                 .setStartDelay(delay)
    721                 .setDuration(duration);
    722     }
    723 
    724     private void updateVetoButton() {
    725         // public versions cannot be dismissed
    726         mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
    727     }
    728 
    729     public void setChildrenExpanded(boolean expanded, boolean animate) {
    730         mChildrenExpanded = expanded;
    731         updateChildrenVisibility(animate);
    732     }
    733 
    734     public void updateExpandButton() {
    735         boolean hasExpand = hasBottomDecor();
    736         if (hasExpand != mHasExpandAction) {
    737             if (hasExpand) {
    738                 if (mExpandButtonContainer == null) {
    739                     mExpandButtonStub.inflate();
    740                 }
    741                 mExpandButtonContainer.setVisibility(View.VISIBLE);
    742                 updateExpandButtonAppearance();
    743                 updateExpandButtonColor();
    744             } else if (mExpandButtonContainer != null) {
    745                 mExpandButtonContainer.setVisibility(View.GONE);
    746             }
    747             notifyHeightChanged(true  /* needsAnimation */);
    748         }
    749         mHasExpandAction = hasExpand;
    750     }
    751 
    752     private void updateExpandButtonAppearance() {
    753         if (mExpandButtonContainer == null) {
    754             return;
    755         }
    756         float expandButtonAlpha = 0.0f;
    757         float expandButtonTranslation = 0.0f;
    758         float containerTranslation = 0.0f;
    759         int minHeight = getMinHeight();
    760         if (!mChildrenExpanded || mChildExpandAnimator != null) {
    761             int expandActionHeight = getBottomDecorHeight();
    762             int translationY = getActualHeight() - expandActionHeight;
    763             if (translationY > minHeight) {
    764                 containerTranslation = translationY;
    765                 expandButtonAlpha = 1.0f;
    766                 expandButtonTranslation = 0.0f;
    767             } else {
    768                 containerTranslation = minHeight;
    769                 float progress = expandActionHeight != 0
    770                         ? (minHeight - translationY) / (float) expandActionHeight
    771                         : 1.0f;
    772                 expandButtonTranslation = -progress * expandActionHeight * 0.7f;
    773                 float alphaProgress = Math.min(progress / 0.7f, 1.0f);
    774                 alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress);
    775                 expandButtonAlpha = 1.0f - alphaProgress;
    776             }
    777         }
    778         if (mChildExpandAnimator != null || mChildrenExpanded) {
    779             expandButtonAlpha = (1.0f - mChildrenExpandProgress)
    780                     * expandButtonAlpha;
    781             expandButtonTranslation = (1.0f - mChildrenExpandProgress)
    782                     * expandButtonTranslation;
    783             float newTranslation = -getBottomDecorHeight();
    784 
    785             // We don't want to take the actual height of the view as this is already
    786             // interpolated by a custom interpolator leading to a confusing animation. We want
    787             // to have a stable end value to interpolate in between
    788             float collapsedHeight = !mChildrenExpanded
    789                     ? Math.max(StackStateAnimator.getFinalActualHeight(this)
    790                             - getBottomDecorHeight(), minHeight)
    791                     : mExpandButtonStart;
    792             float translationProgress = mFastOutSlowInInterpolator.getInterpolation(
    793                     mChildrenExpandProgress);
    794             containerTranslation = (1.0f - translationProgress) * collapsedHeight
    795                     + translationProgress * newTranslation;
    796         }
    797         mExpandButton.setAlpha(expandButtonAlpha);
    798         mExpandButtonDivider.setAlpha(expandButtonAlpha);
    799         mExpandButton.setTranslationY(expandButtonTranslation);
    800         mExpandButtonContainer.setTranslationY(containerTranslation);
    801         NotificationContentView showingLayout = getShowingLayout();
    802         float layoutTranslation =
    803                 mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight();
    804         layoutTranslation = Math.min(layoutTranslation, 0);
    805         if (!mChildrenExpanded && mChildExpandAnimator == null) {
    806             // Needed for the DragDownHelper in order not to jump there, as the position
    807             // can be negative for a short time.
    808             layoutTranslation = 0;
    809         }
    810         showingLayout.setTranslationY(layoutTranslation);
    811         if (mChildrenContainer != null) {
    812             mChildrenContainer.setTranslationY(
    813                     mExpandButtonContainer.getTranslationY() + getBottomDecorHeight());
    814         }
    815     }
    816 
    817     private void updateExpandButtonColor() {
    818         // TODO: This needs some more baking, currently only the divider is colored according to
    819         // the tint, but legacy black doesn't work yet perfectly for the button etc.
    820         int color = getRippleColor();
    821         if (color == mNormalRippleColor) {
    822             color = 0;
    823         }
    824         if (mExpandButtonDivider != null) {
    825             applyTint(mExpandButtonDivider, color);
    826         }
    827         if (mChildrenContainer != null) {
    828             mChildrenContainer.setTintColor(color);
    829         }
    830     }
    831 
    832     public static void applyTint(View v, int color) {
    833         int alpha;
    834         if (color != 0) {
    835             alpha = COLORED_DIVIDER_ALPHA;
    836         } else {
    837             color = 0xff000000;
    838             alpha = DEFAULT_DIVIDER_ALPHA;
    839         }
    840         if (v.getBackground() instanceof ColorDrawable) {
    841             ColorDrawable background = (ColorDrawable) v.getBackground();
    842             background.mutate();
    843             background.setColor(color);
    844             background.setAlpha(alpha);
    845         }
    846     }
    847 
    848     public int getMaxExpandHeight() {
    849         return mMaxExpandHeight;
    850     }
    851 
    852     @Override
    853     public boolean isContentExpandable() {
    854         NotificationContentView showingLayout = getShowingLayout();
    855         return showingLayout.isContentExpandable();
    856     }
    857 
    858     @Override
    859     protected View getContentView() {
    860         return getShowingLayout();
    861     }
    862 
    863     @Override
    864     public void setActualHeight(int height, boolean notifyListeners) {
    865         super.setActualHeight(height, notifyListeners);
    866         int contentHeight = calculateContentHeightFromActualHeight(height);
    867         mPrivateLayout.setContentHeight(contentHeight);
    868         mPublicLayout.setContentHeight(contentHeight);
    869         if (mGuts != null) {
    870             mGuts.setActualHeight(height);
    871         }
    872         invalidate();
    873         updateExpandButtonAppearance();
    874     }
    875 
    876     @Override
    877     public int getMaxContentHeight() {
    878         NotificationContentView showingLayout = getShowingLayout();
    879         return showingLayout.getMaxHeight();
    880     }
    881 
    882     @Override
    883     public int getMinHeight() {
    884         NotificationContentView showingLayout = getShowingLayout();
    885         return showingLayout.getMinHeight();
    886     }
    887 
    888     @Override
    889     public void setClipTopAmount(int clipTopAmount) {
    890         super.setClipTopAmount(clipTopAmount);
    891         mPrivateLayout.setClipTopAmount(clipTopAmount);
    892         mPublicLayout.setClipTopAmount(clipTopAmount);
    893         if (mGuts != null) {
    894             mGuts.setClipTopAmount(clipTopAmount);
    895         }
    896     }
    897 
    898     public void notifyContentUpdated() {
    899         mPublicLayout.notifyContentUpdated();
    900         mPrivateLayout.notifyContentUpdated();
    901     }
    902 
    903     public boolean isMaxExpandHeightInitialized() {
    904         return mMaxExpandHeight != 0;
    905     }
    906 
    907     private NotificationContentView getShowingLayout() {
    908         return mShowingPublic ? mPublicLayout : mPrivateLayout;
    909     }
    910 
    911     @Override
    912     public void setShowingLegacyBackground(boolean showing) {
    913         super.setShowingLegacyBackground(showing);
    914         mPrivateLayout.setShowingLegacyBackground(showing);
    915         mPublicLayout.setShowingLegacyBackground(showing);
    916     }
    917 
    918     public void setExpansionLogger(ExpansionLogger logger, String key) {
    919         mLogger = logger;
    920         mLoggingKey = key;
    921     }
    922 
    923     private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
    924         final boolean nowExpanded = isExpanded();
    925         if (wasExpanded != nowExpanded && mLogger != null) {
    926             mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
    927         }
    928     }
    929 }
    930