Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2014 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.app.Notification;
     20 import android.app.PendingIntent;
     21 import android.app.RemoteInput;
     22 import android.content.Context;
     23 import android.graphics.Rect;
     24 import android.os.Build;
     25 import android.service.notification.StatusBarNotification;
     26 import android.util.AttributeSet;
     27 import android.view.NotificationHeaderView;
     28 import android.view.View;
     29 import android.view.ViewGroup;
     30 import android.view.ViewTreeObserver;
     31 import android.widget.FrameLayout;
     32 import android.widget.ImageView;
     33 
     34 import com.android.internal.util.NotificationColorUtil;
     35 import com.android.systemui.R;
     36 import com.android.systemui.statusbar.notification.HybridNotificationView;
     37 import com.android.systemui.statusbar.notification.HybridGroupManager;
     38 import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
     39 import com.android.systemui.statusbar.notification.NotificationUtils;
     40 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
     41 import com.android.systemui.statusbar.phone.NotificationGroupManager;
     42 import com.android.systemui.statusbar.policy.RemoteInputView;
     43 
     44 /**
     45  * A frame layout containing the actual payload of the notification, including the contracted,
     46  * expanded and heads up layout. This class is responsible for clipping the content and and
     47  * switching between the expanded, contracted and the heads up view depending on its clipped size.
     48  */
     49 public class NotificationContentView extends FrameLayout {
     50 
     51     private static final int VISIBLE_TYPE_CONTRACTED = 0;
     52     private static final int VISIBLE_TYPE_EXPANDED = 1;
     53     private static final int VISIBLE_TYPE_HEADSUP = 2;
     54     private static final int VISIBLE_TYPE_SINGLELINE = 3;
     55     public static final int UNDEFINED = -1;
     56 
     57     private final Rect mClipBounds = new Rect();
     58     private final int mMinContractedHeight;
     59     private final int mNotificationContentMarginEnd;
     60 
     61     private View mContractedChild;
     62     private View mExpandedChild;
     63     private View mHeadsUpChild;
     64     private HybridNotificationView mSingleLineView;
     65 
     66     private RemoteInputView mExpandedRemoteInput;
     67     private RemoteInputView mHeadsUpRemoteInput;
     68 
     69     private NotificationViewWrapper mContractedWrapper;
     70     private NotificationViewWrapper mExpandedWrapper;
     71     private NotificationViewWrapper mHeadsUpWrapper;
     72     private HybridGroupManager mHybridGroupManager;
     73     private int mClipTopAmount;
     74     private int mContentHeight;
     75     private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
     76     private boolean mDark;
     77     private boolean mAnimate;
     78     private boolean mIsHeadsUp;
     79     private boolean mShowingLegacyBackground;
     80     private boolean mIsChildInGroup;
     81     private int mSmallHeight;
     82     private int mHeadsUpHeight;
     83     private int mNotificationMaxHeight;
     84     private StatusBarNotification mStatusBarNotification;
     85     private NotificationGroupManager mGroupManager;
     86     private RemoteInputController mRemoteInputController;
     87 
     88     private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
     89             = new ViewTreeObserver.OnPreDrawListener() {
     90         @Override
     91         public boolean onPreDraw() {
     92             // We need to post since we don't want the notification to animate on the very first
     93             // frame
     94             post(new Runnable() {
     95                 @Override
     96                 public void run() {
     97                     mAnimate = true;
     98                 }
     99             });
    100             getViewTreeObserver().removeOnPreDrawListener(this);
    101             return true;
    102         }
    103     };
    104 
    105     private OnClickListener mExpandClickListener;
    106     private boolean mBeforeN;
    107     private boolean mExpandable;
    108     private boolean mClipToActualHeight = true;
    109     private ExpandableNotificationRow mContainingNotification;
    110     /** The visible type at the start of a touch driven transformation */
    111     private int mTransformationStartVisibleType;
    112     /** The visible type at the start of an animation driven transformation */
    113     private int mAnimationStartVisibleType = UNDEFINED;
    114     private boolean mUserExpanding;
    115     private int mSingleLineWidthIndention;
    116     private boolean mForceSelectNextLayout = true;
    117     private PendingIntent mPreviousExpandedRemoteInputIntent;
    118     private PendingIntent mPreviousHeadsUpRemoteInputIntent;
    119     private RemoteInputView mCachedExpandedRemoteInput;
    120     private RemoteInputView mCachedHeadsUpRemoteInput;
    121 
    122     private int mContentHeightAtAnimationStart = UNDEFINED;
    123     private boolean mFocusOnVisibilityChange;
    124     private boolean mHeadsupDisappearRunning;
    125 
    126 
    127     public NotificationContentView(Context context, AttributeSet attrs) {
    128         super(context, attrs);
    129         mHybridGroupManager = new HybridGroupManager(getContext(), this);
    130         mMinContractedHeight = getResources().getDimensionPixelSize(
    131                 R.dimen.min_notification_layout_height);
    132         mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
    133                 com.android.internal.R.dimen.notification_content_margin_end);
    134         reset();
    135     }
    136 
    137     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight) {
    138         mSmallHeight = smallHeight;
    139         mHeadsUpHeight = headsUpMaxHeight;
    140         mNotificationMaxHeight = maxHeight;
    141     }
    142 
    143     @Override
    144     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    145         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    146         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
    147         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
    148         int maxSize = Integer.MAX_VALUE;
    149         int width = MeasureSpec.getSize(widthMeasureSpec);
    150         if (hasFixedHeight || isHeightLimited) {
    151             maxSize = MeasureSpec.getSize(heightMeasureSpec);
    152         }
    153         int maxChildHeight = 0;
    154         if (mExpandedChild != null) {
    155             int size = Math.min(maxSize, mNotificationMaxHeight);
    156             ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
    157             if (layoutParams.height >= 0) {
    158                 // An actual height is set
    159                 size = Math.min(maxSize, layoutParams.height);
    160             }
    161             int spec = size == Integer.MAX_VALUE
    162                     ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
    163                     : MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
    164             mExpandedChild.measure(widthMeasureSpec, spec);
    165             maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
    166         }
    167         if (mContractedChild != null) {
    168             int heightSpec;
    169             int size = Math.min(maxSize, mSmallHeight);
    170             if (shouldContractedBeFixedSize()) {
    171                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
    172             } else {
    173                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
    174             }
    175             mContractedChild.measure(widthMeasureSpec, heightSpec);
    176             int measuredHeight = mContractedChild.getMeasuredHeight();
    177             if (measuredHeight < mMinContractedHeight) {
    178                 heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
    179                 mContractedChild.measure(widthMeasureSpec, heightSpec);
    180             }
    181             maxChildHeight = Math.max(maxChildHeight, measuredHeight);
    182             if (updateContractedHeaderWidth()) {
    183                 mContractedChild.measure(widthMeasureSpec, heightSpec);
    184             }
    185             if (mExpandedChild != null
    186                     && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
    187                 // the Expanded child is smaller then the collapsed. Let's remeasure it.
    188                 heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
    189                         MeasureSpec.EXACTLY);
    190                 mExpandedChild.measure(widthMeasureSpec, heightSpec);
    191             }
    192         }
    193         if (mHeadsUpChild != null) {
    194             int size = Math.min(maxSize, mHeadsUpHeight);
    195             ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
    196             if (layoutParams.height >= 0) {
    197                 // An actual height is set
    198                 size = Math.min(size, layoutParams.height);
    199             }
    200             mHeadsUpChild.measure(widthMeasureSpec,
    201                     MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
    202             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
    203         }
    204         if (mSingleLineView != null) {
    205             int singleLineWidthSpec = widthMeasureSpec;
    206             if (mSingleLineWidthIndention != 0
    207                     && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
    208                 singleLineWidthSpec = MeasureSpec.makeMeasureSpec(
    209                         width - mSingleLineWidthIndention + mSingleLineView.getPaddingEnd(),
    210                         MeasureSpec.EXACTLY);
    211             }
    212             mSingleLineView.measure(singleLineWidthSpec,
    213                     MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST));
    214             maxChildHeight = Math.max(maxChildHeight, mSingleLineView.getMeasuredHeight());
    215         }
    216         int ownHeight = Math.min(maxChildHeight, maxSize);
    217         setMeasuredDimension(width, ownHeight);
    218     }
    219 
    220     private boolean updateContractedHeaderWidth() {
    221         // We need to update the expanded and the collapsed header to have exactly the same with to
    222         // have the expand buttons laid out at the same location.
    223         NotificationHeaderView contractedHeader = mContractedWrapper.getNotificationHeader();
    224         if (contractedHeader != null) {
    225             if (mExpandedChild != null
    226                     && mExpandedWrapper.getNotificationHeader() != null) {
    227                 NotificationHeaderView expandedHeader = mExpandedWrapper.getNotificationHeader();
    228                 int expandedSize = expandedHeader.getMeasuredWidth()
    229                         - expandedHeader.getPaddingEnd();
    230                 int collapsedSize = contractedHeader.getMeasuredWidth()
    231                         - expandedHeader.getPaddingEnd();
    232                 if (expandedSize != collapsedSize) {
    233                     int paddingEnd = contractedHeader.getMeasuredWidth() - expandedSize;
    234                     contractedHeader.setPadding(
    235                             contractedHeader.isLayoutRtl()
    236                                     ? paddingEnd
    237                                     : contractedHeader.getPaddingLeft(),
    238                             contractedHeader.getPaddingTop(),
    239                             contractedHeader.isLayoutRtl()
    240                                     ? contractedHeader.getPaddingLeft()
    241                                     : paddingEnd,
    242                             contractedHeader.getPaddingBottom());
    243                     contractedHeader.setShowWorkBadgeAtEnd(true);
    244                     return true;
    245                 }
    246             } else {
    247                 int paddingEnd = mNotificationContentMarginEnd;
    248                 if (contractedHeader.getPaddingEnd() != paddingEnd) {
    249                     contractedHeader.setPadding(
    250                             contractedHeader.isLayoutRtl()
    251                                     ? paddingEnd
    252                                     : contractedHeader.getPaddingLeft(),
    253                             contractedHeader.getPaddingTop(),
    254                             contractedHeader.isLayoutRtl()
    255                                     ? contractedHeader.getPaddingLeft()
    256                                     : paddingEnd,
    257                             contractedHeader.getPaddingBottom());
    258                     contractedHeader.setShowWorkBadgeAtEnd(false);
    259                     return true;
    260                 }
    261             }
    262         }
    263         return false;
    264     }
    265 
    266     private boolean shouldContractedBeFixedSize() {
    267         return mBeforeN && mContractedWrapper instanceof NotificationCustomViewWrapper;
    268     }
    269 
    270     @Override
    271     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    272         int previousHeight = 0;
    273         if (mExpandedChild != null) {
    274             previousHeight = mExpandedChild.getHeight();
    275         }
    276         super.onLayout(changed, left, top, right, bottom);
    277         if (previousHeight != 0 && mExpandedChild.getHeight() != previousHeight) {
    278             mContentHeightAtAnimationStart = previousHeight;
    279         }
    280         updateClipping();
    281         invalidateOutline();
    282         selectLayout(false /* animate */, mForceSelectNextLayout /* force */);
    283         mForceSelectNextLayout = false;
    284         updateExpandButtons(mExpandable);
    285     }
    286 
    287     @Override
    288     protected void onAttachedToWindow() {
    289         super.onAttachedToWindow();
    290         updateVisibility();
    291     }
    292 
    293     public void reset() {
    294         if (mContractedChild != null) {
    295             mContractedChild.animate().cancel();
    296             removeView(mContractedChild);
    297         }
    298         mPreviousExpandedRemoteInputIntent = null;
    299         if (mExpandedRemoteInput != null) {
    300             mExpandedRemoteInput.onNotificationUpdateOrReset();
    301             if (mExpandedRemoteInput.isActive()) {
    302                 mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent();
    303                 mCachedExpandedRemoteInput = mExpandedRemoteInput;
    304                 mExpandedRemoteInput.dispatchStartTemporaryDetach();
    305                 ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
    306             }
    307         }
    308         if (mExpandedChild != null) {
    309             mExpandedChild.animate().cancel();
    310             removeView(mExpandedChild);
    311             mExpandedRemoteInput = null;
    312         }
    313         mPreviousHeadsUpRemoteInputIntent = null;
    314         if (mHeadsUpRemoteInput != null) {
    315             mHeadsUpRemoteInput.onNotificationUpdateOrReset();
    316             if (mHeadsUpRemoteInput.isActive()) {
    317                 mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent();
    318                 mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
    319                 mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
    320                 ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
    321             }
    322         }
    323         if (mHeadsUpChild != null) {
    324             mHeadsUpChild.animate().cancel();
    325             removeView(mHeadsUpChild);
    326             mHeadsUpRemoteInput = null;
    327         }
    328         mContractedChild = null;
    329         mExpandedChild = null;
    330         mHeadsUpChild = null;
    331     }
    332 
    333     public View getContractedChild() {
    334         return mContractedChild;
    335     }
    336 
    337     public View getExpandedChild() {
    338         return mExpandedChild;
    339     }
    340 
    341     public View getHeadsUpChild() {
    342         return mHeadsUpChild;
    343     }
    344 
    345     public void setContractedChild(View child) {
    346         if (mContractedChild != null) {
    347             mContractedChild.animate().cancel();
    348             removeView(mContractedChild);
    349         }
    350         addView(child);
    351         mContractedChild = child;
    352         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
    353                 mContainingNotification);
    354         mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
    355     }
    356 
    357     public void setExpandedChild(View child) {
    358         if (mExpandedChild != null) {
    359             mExpandedChild.animate().cancel();
    360             removeView(mExpandedChild);
    361         }
    362         addView(child);
    363         mExpandedChild = child;
    364         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
    365                 mContainingNotification);
    366     }
    367 
    368     public void setHeadsUpChild(View child) {
    369         if (mHeadsUpChild != null) {
    370             mHeadsUpChild.animate().cancel();
    371             removeView(mHeadsUpChild);
    372         }
    373         addView(child);
    374         mHeadsUpChild = child;
    375         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
    376                 mContainingNotification);
    377     }
    378 
    379     @Override
    380     protected void onVisibilityChanged(View changedView, int visibility) {
    381         super.onVisibilityChanged(changedView, visibility);
    382         updateVisibility();
    383     }
    384 
    385     private void updateVisibility() {
    386         setVisible(isShown());
    387     }
    388 
    389     @Override
    390     protected void onDetachedFromWindow() {
    391         super.onDetachedFromWindow();
    392         getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
    393     }
    394 
    395     private void setVisible(final boolean isVisible) {
    396         if (isVisible) {
    397             // This call can happen multiple times, but removing only removes a single one.
    398             // We therefore need to remove the old one.
    399             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
    400             // We only animate if we are drawn at least once, otherwise the view might animate when
    401             // it's shown the first time
    402             getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
    403         } else {
    404             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
    405             mAnimate = false;
    406         }
    407     }
    408 
    409     private void focusExpandButtonIfNecessary() {
    410         if (mFocusOnVisibilityChange) {
    411             NotificationHeaderView header = getVisibleNotificationHeader();
    412             if (header != null) {
    413                 ImageView expandButton = header.getExpandButton();
    414                 if (expandButton != null) {
    415                     expandButton.requestAccessibilityFocus();
    416                 }
    417             }
    418             mFocusOnVisibilityChange = false;
    419         }
    420     }
    421 
    422     public void setContentHeight(int contentHeight) {
    423         mContentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());
    424         selectLayout(mAnimate /* animate */, false /* force */);
    425 
    426         int minHeightHint = getMinContentHeightHint();
    427 
    428         NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
    429         if (wrapper != null) {
    430             wrapper.setContentHeight(mContentHeight, minHeightHint);
    431         }
    432 
    433         wrapper = getVisibleWrapper(mTransformationStartVisibleType);
    434         if (wrapper != null) {
    435             wrapper.setContentHeight(mContentHeight, minHeightHint);
    436         }
    437 
    438         updateClipping();
    439         invalidateOutline();
    440     }
    441 
    442     /**
    443      * @return the minimum apparent height that the wrapper should allow for the purpose
    444      *         of aligning elements at the bottom edge. If this is larger than the content
    445      *         height, the notification is clipped instead of being further shrunk.
    446      */
    447     private int getMinContentHeightHint() {
    448         if (mIsChildInGroup && isVisibleOrTransitioning(VISIBLE_TYPE_SINGLELINE)) {
    449             return mContext.getResources().getDimensionPixelSize(
    450                         com.android.internal.R.dimen.notification_action_list_height);
    451         }
    452 
    453         // Transition between heads-up & expanded, or pinned.
    454         if (mHeadsUpChild != null && mExpandedChild != null) {
    455             boolean transitioningBetweenHunAndExpanded =
    456                     isTransitioningFromTo(VISIBLE_TYPE_HEADSUP, VISIBLE_TYPE_EXPANDED) ||
    457                     isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP);
    458             boolean pinned = !isVisibleOrTransitioning(VISIBLE_TYPE_CONTRACTED)
    459                     && (mIsHeadsUp || mHeadsupDisappearRunning);
    460             if (transitioningBetweenHunAndExpanded || pinned) {
    461                 return Math.min(mHeadsUpChild.getHeight(), mExpandedChild.getHeight());
    462             }
    463         }
    464 
    465         // Size change of the expanded version
    466         if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart >= 0
    467                 && mExpandedChild != null) {
    468             return Math.min(mContentHeightAtAnimationStart, mExpandedChild.getHeight());
    469         }
    470 
    471         int hint;
    472         if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
    473             hint = mHeadsUpChild.getHeight();
    474         } else if (mExpandedChild != null) {
    475             hint = mExpandedChild.getHeight();
    476         } else {
    477             hint = mContractedChild.getHeight() + mContext.getResources().getDimensionPixelSize(
    478                     com.android.internal.R.dimen.notification_action_list_height);
    479         }
    480 
    481         if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) {
    482             hint = Math.min(hint, mExpandedChild.getHeight());
    483         }
    484         return hint;
    485     }
    486 
    487     private boolean isTransitioningFromTo(int from, int to) {
    488         return (mTransformationStartVisibleType == from || mAnimationStartVisibleType == from)
    489                 && mVisibleType == to;
    490     }
    491 
    492     private boolean isVisibleOrTransitioning(int type) {
    493         return mVisibleType == type || mTransformationStartVisibleType == type
    494                 || mAnimationStartVisibleType == type;
    495     }
    496 
    497     private void updateContentTransformation() {
    498         int visibleType = calculateVisibleType();
    499         if (visibleType != mVisibleType) {
    500             // A new transformation starts
    501             mTransformationStartVisibleType = mVisibleType;
    502             final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
    503             final TransformableView hiddenView = getTransformableViewForVisibleType(
    504                     mTransformationStartVisibleType);
    505             shownView.transformFrom(hiddenView, 0.0f);
    506             getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
    507             hiddenView.transformTo(shownView, 0.0f);
    508             mVisibleType = visibleType;
    509             updateBackgroundColor(true /* animate */);
    510         }
    511         if (mForceSelectNextLayout) {
    512             forceUpdateVisibilities();
    513         }
    514         if (mTransformationStartVisibleType != UNDEFINED
    515                 && mVisibleType != mTransformationStartVisibleType
    516                 && getViewForVisibleType(mTransformationStartVisibleType) != null) {
    517             final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType);
    518             final TransformableView hiddenView = getTransformableViewForVisibleType(
    519                     mTransformationStartVisibleType);
    520             float transformationAmount = calculateTransformationAmount();
    521             shownView.transformFrom(hiddenView, transformationAmount);
    522             hiddenView.transformTo(shownView, transformationAmount);
    523             updateBackgroundTransformation(transformationAmount);
    524         } else {
    525             updateViewVisibilities(visibleType);
    526             updateBackgroundColor(false);
    527         }
    528     }
    529 
    530     private void updateBackgroundTransformation(float transformationAmount) {
    531         int endColor = getBackgroundColor(mVisibleType);
    532         int startColor = getBackgroundColor(mTransformationStartVisibleType);
    533         if (endColor != startColor) {
    534             if (startColor == 0) {
    535                 startColor = mContainingNotification.getBackgroundColorWithoutTint();
    536             }
    537             if (endColor == 0) {
    538                 endColor = mContainingNotification.getBackgroundColorWithoutTint();
    539             }
    540             endColor = NotificationUtils.interpolateColors(startColor, endColor,
    541                     transformationAmount);
    542         }
    543         mContainingNotification.updateBackgroundAlpha(transformationAmount);
    544         mContainingNotification.setContentBackground(endColor, false, this);
    545     }
    546 
    547     private float calculateTransformationAmount() {
    548         int startHeight = getViewForVisibleType(mTransformationStartVisibleType).getHeight();
    549         int endHeight = getViewForVisibleType(mVisibleType).getHeight();
    550         int progress = Math.abs(mContentHeight - startHeight);
    551         int totalDistance = Math.abs(endHeight - startHeight);
    552         float amount = (float) progress / (float) totalDistance;
    553         return Math.min(1.0f, amount);
    554     }
    555 
    556     public int getContentHeight() {
    557         return mContentHeight;
    558     }
    559 
    560     public int getMaxHeight() {
    561         if (mExpandedChild != null) {
    562             return mExpandedChild.getHeight();
    563         } else if (mIsHeadsUp && mHeadsUpChild != null) {
    564             return mHeadsUpChild.getHeight();
    565         }
    566         return mContractedChild.getHeight();
    567     }
    568 
    569     public int getMinHeight() {
    570         return getMinHeight(false /* likeGroupExpanded */);
    571     }
    572 
    573     public int getMinHeight(boolean likeGroupExpanded) {
    574         if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
    575             return mContractedChild.getHeight();
    576         } else {
    577             return mSingleLineView.getHeight();
    578         }
    579     }
    580 
    581     private boolean isGroupExpanded() {
    582         return mGroupManager.isGroupExpanded(mStatusBarNotification);
    583     }
    584 
    585     public void setClipTopAmount(int clipTopAmount) {
    586         mClipTopAmount = clipTopAmount;
    587         updateClipping();
    588     }
    589 
    590     private void updateClipping() {
    591         if (mClipToActualHeight) {
    592             mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
    593             setClipBounds(mClipBounds);
    594         } else {
    595             setClipBounds(null);
    596         }
    597     }
    598 
    599     public void setClipToActualHeight(boolean clipToActualHeight) {
    600         mClipToActualHeight = clipToActualHeight;
    601         updateClipping();
    602     }
    603 
    604     private void selectLayout(boolean animate, boolean force) {
    605         if (mContractedChild == null) {
    606             return;
    607         }
    608         if (mUserExpanding) {
    609             updateContentTransformation();
    610         } else {
    611             int visibleType = calculateVisibleType();
    612             boolean changedType = visibleType != mVisibleType;
    613             if (changedType || force) {
    614                 View visibleView = getViewForVisibleType(visibleType);
    615                 if (visibleView != null) {
    616                     visibleView.setVisibility(VISIBLE);
    617                     transferRemoteInputFocus(visibleType);
    618                 }
    619                 NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
    620                 if (visibleWrapper != null) {
    621                     visibleWrapper.setContentHeight(mContentHeight, getMinContentHeightHint());
    622                 }
    623 
    624                 if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
    625                         || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
    626                         || (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
    627                         || visibleType == VISIBLE_TYPE_CONTRACTED)) {
    628                     animateToVisibleType(visibleType);
    629                 } else {
    630                     updateViewVisibilities(visibleType);
    631                 }
    632                 mVisibleType = visibleType;
    633                 if (changedType) {
    634                     focusExpandButtonIfNecessary();
    635                 }
    636                 updateBackgroundColor(animate);
    637             }
    638         }
    639     }
    640 
    641     private void forceUpdateVisibilities() {
    642         boolean contractedVisible = mVisibleType == VISIBLE_TYPE_CONTRACTED
    643                 || mTransformationStartVisibleType == VISIBLE_TYPE_CONTRACTED;
    644         boolean expandedVisible = mVisibleType == VISIBLE_TYPE_EXPANDED
    645                 || mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED;
    646         boolean headsUpVisible = mVisibleType == VISIBLE_TYPE_HEADSUP
    647                 || mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP;
    648         boolean singleLineVisible = mVisibleType == VISIBLE_TYPE_SINGLELINE
    649                 || mTransformationStartVisibleType == VISIBLE_TYPE_SINGLELINE;
    650         if (!contractedVisible) {
    651             mContractedChild.setVisibility(View.INVISIBLE);
    652         } else {
    653             mContractedWrapper.setVisible(true);
    654         }
    655         if (mExpandedChild != null) {
    656             if (!expandedVisible) {
    657                 mExpandedChild.setVisibility(View.INVISIBLE);
    658             } else {
    659                 mExpandedWrapper.setVisible(true);
    660             }
    661         }
    662         if (mHeadsUpChild != null) {
    663             if (!headsUpVisible) {
    664                 mHeadsUpChild.setVisibility(View.INVISIBLE);
    665             } else {
    666                 mHeadsUpWrapper.setVisible(true);
    667             }
    668         }
    669         if (mSingleLineView != null) {
    670             if (!singleLineVisible) {
    671                 mSingleLineView.setVisibility(View.INVISIBLE);
    672             } else {
    673                 mSingleLineView.setVisible(true);
    674             }
    675         }
    676     }
    677 
    678     public void updateBackgroundColor(boolean animate) {
    679         int customBackgroundColor = getBackgroundColor(mVisibleType);
    680         mContainingNotification.resetBackgroundAlpha();
    681         mContainingNotification.setContentBackground(customBackgroundColor, animate, this);
    682     }
    683 
    684     public int getVisibleType() {
    685         return mVisibleType;
    686     }
    687 
    688     public int getBackgroundColorForExpansionState() {
    689         // When expanding or user locked we want the new type, when collapsing we want
    690         // the original type
    691         final int visibleType = (mContainingNotification.isGroupExpanded()
    692                 || mContainingNotification.isUserLocked())
    693                         ? calculateVisibleType()
    694                         : getVisibleType();
    695         return getBackgroundColor(visibleType);
    696     }
    697 
    698     public int getBackgroundColor(int visibleType) {
    699         NotificationViewWrapper currentVisibleWrapper = getVisibleWrapper(visibleType);
    700         int customBackgroundColor = 0;
    701         if (currentVisibleWrapper != null) {
    702             customBackgroundColor = currentVisibleWrapper.getCustomBackgroundColor();
    703         }
    704         return customBackgroundColor;
    705     }
    706 
    707     private void updateViewVisibilities(int visibleType) {
    708         boolean contractedVisible = visibleType == VISIBLE_TYPE_CONTRACTED;
    709         mContractedWrapper.setVisible(contractedVisible);
    710         if (mExpandedChild != null) {
    711             boolean expandedVisible = visibleType == VISIBLE_TYPE_EXPANDED;
    712             mExpandedWrapper.setVisible(expandedVisible);
    713         }
    714         if (mHeadsUpChild != null) {
    715             boolean headsUpVisible = visibleType == VISIBLE_TYPE_HEADSUP;
    716             mHeadsUpWrapper.setVisible(headsUpVisible);
    717         }
    718         if (mSingleLineView != null) {
    719             boolean singleLineVisible = visibleType == VISIBLE_TYPE_SINGLELINE;
    720             mSingleLineView.setVisible(singleLineVisible);
    721         }
    722     }
    723 
    724     private void animateToVisibleType(int visibleType) {
    725         final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
    726         final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
    727         if (shownView == hiddenView || hiddenView == null) {
    728             shownView.setVisible(true);
    729             return;
    730         }
    731         mAnimationStartVisibleType = mVisibleType;
    732         shownView.transformFrom(hiddenView);
    733         getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
    734         hiddenView.transformTo(shownView, new Runnable() {
    735             @Override
    736             public void run() {
    737                 if (hiddenView != getTransformableViewForVisibleType(mVisibleType)) {
    738                     hiddenView.setVisible(false);
    739                 }
    740                 mAnimationStartVisibleType = UNDEFINED;
    741             }
    742         });
    743     }
    744 
    745     private void transferRemoteInputFocus(int visibleType) {
    746         if (visibleType == VISIBLE_TYPE_HEADSUP
    747                 && mHeadsUpRemoteInput != null
    748                 && (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive())) {
    749             mHeadsUpRemoteInput.stealFocusFrom(mExpandedRemoteInput);
    750         }
    751         if (visibleType == VISIBLE_TYPE_EXPANDED
    752                 && mExpandedRemoteInput != null
    753                 && (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive())) {
    754             mExpandedRemoteInput.stealFocusFrom(mHeadsUpRemoteInput);
    755         }
    756     }
    757 
    758     /**
    759      * @param visibleType one of the static enum types in this view
    760      * @return the corresponding transformable view according to the given visible type
    761      */
    762     private TransformableView getTransformableViewForVisibleType(int visibleType) {
    763         switch (visibleType) {
    764             case VISIBLE_TYPE_EXPANDED:
    765                 return mExpandedWrapper;
    766             case VISIBLE_TYPE_HEADSUP:
    767                 return mHeadsUpWrapper;
    768             case VISIBLE_TYPE_SINGLELINE:
    769                 return mSingleLineView;
    770             default:
    771                 return mContractedWrapper;
    772         }
    773     }
    774 
    775     /**
    776      * @param visibleType one of the static enum types in this view
    777      * @return the corresponding view according to the given visible type
    778      */
    779     private View getViewForVisibleType(int visibleType) {
    780         switch (visibleType) {
    781             case VISIBLE_TYPE_EXPANDED:
    782                 return mExpandedChild;
    783             case VISIBLE_TYPE_HEADSUP:
    784                 return mHeadsUpChild;
    785             case VISIBLE_TYPE_SINGLELINE:
    786                 return mSingleLineView;
    787             default:
    788                 return mContractedChild;
    789         }
    790     }
    791 
    792     private NotificationViewWrapper getVisibleWrapper(int visibleType) {
    793         switch (visibleType) {
    794             case VISIBLE_TYPE_EXPANDED:
    795                 return mExpandedWrapper;
    796             case VISIBLE_TYPE_HEADSUP:
    797                 return mHeadsUpWrapper;
    798             case VISIBLE_TYPE_CONTRACTED:
    799                 return mContractedWrapper;
    800             default:
    801                 return null;
    802         }
    803     }
    804 
    805     /**
    806      * @return one of the static enum types in this view, calculated form the current state
    807      */
    808     public int calculateVisibleType() {
    809         if (mUserExpanding) {
    810             int height = !mIsChildInGroup || isGroupExpanded()
    811                     || mContainingNotification.isExpanded(true /* allowOnKeyguard */)
    812                     ? mContainingNotification.getMaxContentHeight()
    813                     : mContainingNotification.getShowingLayout().getMinHeight();
    814             if (height == 0) {
    815                 height = mContentHeight;
    816             }
    817             int expandedVisualType = getVisualTypeForHeight(height);
    818             int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
    819                     ? VISIBLE_TYPE_SINGLELINE
    820                     : getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
    821             return mTransformationStartVisibleType == collapsedVisualType
    822                     ? expandedVisualType
    823                     : collapsedVisualType;
    824         }
    825         int intrinsicHeight = mContainingNotification.getIntrinsicHeight();
    826         int viewHeight = mContentHeight;
    827         if (intrinsicHeight != 0) {
    828             // the intrinsicHeight might be 0 because it was just reset.
    829             viewHeight = Math.min(mContentHeight, intrinsicHeight);
    830         }
    831         return getVisualTypeForHeight(viewHeight);
    832     }
    833 
    834     private int getVisualTypeForHeight(float viewHeight) {
    835         boolean noExpandedChild = mExpandedChild == null;
    836         if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
    837             return VISIBLE_TYPE_EXPANDED;
    838         }
    839         if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
    840             return VISIBLE_TYPE_SINGLELINE;
    841         }
    842 
    843         if ((mIsHeadsUp || mHeadsupDisappearRunning) && mHeadsUpChild != null) {
    844             if (viewHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
    845                 return VISIBLE_TYPE_HEADSUP;
    846             } else {
    847                 return VISIBLE_TYPE_EXPANDED;
    848             }
    849         } else {
    850             if (noExpandedChild || (viewHeight <= mContractedChild.getHeight()
    851                     && (!mIsChildInGroup || isGroupExpanded()
    852                             || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
    853                 return VISIBLE_TYPE_CONTRACTED;
    854             } else {
    855                 return VISIBLE_TYPE_EXPANDED;
    856             }
    857         }
    858     }
    859 
    860     public boolean isContentExpandable() {
    861         return mExpandedChild != null;
    862     }
    863 
    864     public void setDark(boolean dark, boolean fade, long delay) {
    865         if (mContractedChild == null) {
    866             return;
    867         }
    868         mDark = dark;
    869         if (mVisibleType == VISIBLE_TYPE_CONTRACTED || !dark) {
    870             mContractedWrapper.setDark(dark, fade, delay);
    871         }
    872         if (mVisibleType == VISIBLE_TYPE_EXPANDED || (mExpandedChild != null && !dark)) {
    873             mExpandedWrapper.setDark(dark, fade, delay);
    874         }
    875         if (mVisibleType == VISIBLE_TYPE_HEADSUP || (mHeadsUpChild != null && !dark)) {
    876             mHeadsUpWrapper.setDark(dark, fade, delay);
    877         }
    878         if (mSingleLineView != null && (mVisibleType == VISIBLE_TYPE_SINGLELINE || !dark)) {
    879             mSingleLineView.setDark(dark, fade, delay);
    880         }
    881     }
    882 
    883     public void setHeadsUp(boolean headsUp) {
    884         mIsHeadsUp = headsUp;
    885         selectLayout(false /* animate */, true /* force */);
    886         updateExpandButtons(mExpandable);
    887     }
    888 
    889     @Override
    890     public boolean hasOverlappingRendering() {
    891 
    892         // This is not really true, but good enough when fading from the contracted to the expanded
    893         // layout, and saves us some layers.
    894         return false;
    895     }
    896 
    897     public void setShowingLegacyBackground(boolean showing) {
    898         mShowingLegacyBackground = showing;
    899         updateShowingLegacyBackground();
    900     }
    901 
    902     private void updateShowingLegacyBackground() {
    903         if (mContractedChild != null) {
    904             mContractedWrapper.setShowingLegacyBackground(mShowingLegacyBackground);
    905         }
    906         if (mExpandedChild != null) {
    907             mExpandedWrapper.setShowingLegacyBackground(mShowingLegacyBackground);
    908         }
    909         if (mHeadsUpChild != null) {
    910             mHeadsUpWrapper.setShowingLegacyBackground(mShowingLegacyBackground);
    911         }
    912     }
    913 
    914     public void setIsChildInGroup(boolean isChildInGroup) {
    915         mIsChildInGroup = isChildInGroup;
    916         updateSingleLineView();
    917     }
    918 
    919     public void onNotificationUpdated(NotificationData.Entry entry) {
    920         mStatusBarNotification = entry.notification;
    921         mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
    922         updateSingleLineView();
    923         applyRemoteInput(entry);
    924         if (mContractedChild != null) {
    925             mContractedWrapper.notifyContentUpdated(entry.notification);
    926         }
    927         if (mExpandedChild != null) {
    928             mExpandedWrapper.notifyContentUpdated(entry.notification);
    929         }
    930         if (mHeadsUpChild != null) {
    931             mHeadsUpWrapper.notifyContentUpdated(entry.notification);
    932         }
    933         updateShowingLegacyBackground();
    934         mForceSelectNextLayout = true;
    935         setDark(mDark, false /* animate */, 0 /* delay */);
    936         mPreviousExpandedRemoteInputIntent = null;
    937         mPreviousHeadsUpRemoteInputIntent = null;
    938     }
    939 
    940     private void updateSingleLineView() {
    941         if (mIsChildInGroup) {
    942             mSingleLineView = mHybridGroupManager.bindFromNotification(
    943                     mSingleLineView, mStatusBarNotification.getNotification());
    944         } else if (mSingleLineView != null) {
    945             removeView(mSingleLineView);
    946             mSingleLineView = null;
    947         }
    948     }
    949 
    950     private void applyRemoteInput(final NotificationData.Entry entry) {
    951         if (mRemoteInputController == null) {
    952             return;
    953         }
    954 
    955         boolean hasRemoteInput = false;
    956 
    957         Notification.Action[] actions = entry.notification.getNotification().actions;
    958         if (actions != null) {
    959             for (Notification.Action a : actions) {
    960                 if (a.getRemoteInputs() != null) {
    961                     for (RemoteInput ri : a.getRemoteInputs()) {
    962                         if (ri.getAllowFreeFormInput()) {
    963                             hasRemoteInput = true;
    964                             break;
    965                         }
    966                     }
    967                 }
    968             }
    969         }
    970 
    971         View bigContentView = mExpandedChild;
    972         if (bigContentView != null) {
    973             mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
    974                     mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput);
    975         } else {
    976             mExpandedRemoteInput = null;
    977         }
    978         if (mCachedExpandedRemoteInput != null
    979                 && mCachedExpandedRemoteInput != mExpandedRemoteInput) {
    980             // We had a cached remote input but didn't reuse it. Clean up required.
    981             mCachedExpandedRemoteInput.dispatchFinishTemporaryDetach();
    982         }
    983         mCachedExpandedRemoteInput = null;
    984 
    985         View headsUpContentView = mHeadsUpChild;
    986         if (headsUpContentView != null) {
    987             mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
    988                     mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput);
    989         } else {
    990             mHeadsUpRemoteInput = null;
    991         }
    992         if (mCachedHeadsUpRemoteInput != null
    993                 && mCachedHeadsUpRemoteInput != mHeadsUpRemoteInput) {
    994             // We had a cached remote input but didn't reuse it. Clean up required.
    995             mCachedHeadsUpRemoteInput.dispatchFinishTemporaryDetach();
    996         }
    997         mCachedHeadsUpRemoteInput = null;
    998     }
    999 
   1000     private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
   1001             boolean hasRemoteInput, PendingIntent existingPendingIntent,
   1002             RemoteInputView cachedView) {
   1003         View actionContainerCandidate = view.findViewById(
   1004                 com.android.internal.R.id.actions_container);
   1005         if (actionContainerCandidate instanceof FrameLayout) {
   1006             RemoteInputView existing = (RemoteInputView)
   1007                     view.findViewWithTag(RemoteInputView.VIEW_TAG);
   1008 
   1009             if (existing != null) {
   1010                 existing.onNotificationUpdateOrReset();
   1011             }
   1012 
   1013             if (existing == null && hasRemoteInput) {
   1014                 ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
   1015                 if (cachedView == null) {
   1016                     RemoteInputView riv = RemoteInputView.inflate(
   1017                             mContext, actionContainer, entry, mRemoteInputController);
   1018 
   1019                     riv.setVisibility(View.INVISIBLE);
   1020                     actionContainer.addView(riv, new LayoutParams(
   1021                             ViewGroup.LayoutParams.MATCH_PARENT,
   1022                             ViewGroup.LayoutParams.MATCH_PARENT)
   1023                     );
   1024                     existing = riv;
   1025                 } else {
   1026                     actionContainer.addView(cachedView);
   1027                     cachedView.dispatchFinishTemporaryDetach();
   1028                     cachedView.requestFocus();
   1029                     existing = cachedView;
   1030                 }
   1031             }
   1032             if (hasRemoteInput) {
   1033                 int color = entry.notification.getNotification().color;
   1034                 if (color == Notification.COLOR_DEFAULT) {
   1035                     color = mContext.getColor(R.color.default_remote_input_background);
   1036                 }
   1037                 existing.setBackgroundColor(NotificationColorUtil.ensureTextBackgroundColor(color,
   1038                         mContext.getColor(R.color.remote_input_text_enabled),
   1039                         mContext.getColor(R.color.remote_input_hint)));
   1040 
   1041                 if (existingPendingIntent != null || existing.isActive()) {
   1042                     // The current action could be gone, or the pending intent no longer valid.
   1043                     // If we find a matching action in the new notification, focus, otherwise close.
   1044                     Notification.Action[] actions = entry.notification.getNotification().actions;
   1045                     if (existingPendingIntent != null) {
   1046                         existing.setPendingIntent(existingPendingIntent);
   1047                     }
   1048                     if (existing.updatePendingIntentFromActions(actions)) {
   1049                         if (!existing.isActive()) {
   1050                             existing.focus();
   1051                         }
   1052                     } else {
   1053                         if (existing.isActive()) {
   1054                             existing.close();
   1055                         }
   1056                     }
   1057                 }
   1058             }
   1059             return existing;
   1060         }
   1061         return null;
   1062     }
   1063 
   1064     public void closeRemoteInput() {
   1065         if (mHeadsUpRemoteInput != null) {
   1066             mHeadsUpRemoteInput.close();
   1067         }
   1068         if (mExpandedRemoteInput != null) {
   1069             mExpandedRemoteInput.close();
   1070         }
   1071     }
   1072 
   1073     public void setGroupManager(NotificationGroupManager groupManager) {
   1074         mGroupManager = groupManager;
   1075     }
   1076 
   1077     public void setRemoteInputController(RemoteInputController r) {
   1078         mRemoteInputController = r;
   1079     }
   1080 
   1081     public void setExpandClickListener(OnClickListener expandClickListener) {
   1082         mExpandClickListener = expandClickListener;
   1083     }
   1084 
   1085     public void updateExpandButtons(boolean expandable) {
   1086         mExpandable = expandable;
   1087         // if the expanded child has the same height as the collapsed one we hide it.
   1088         if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
   1089             if ((!mIsHeadsUp || mHeadsUpChild == null)) {
   1090                 if (mExpandedChild.getHeight() == mContractedChild.getHeight()) {
   1091                     expandable = false;
   1092                 }
   1093             } else if (mExpandedChild.getHeight() == mHeadsUpChild.getHeight()) {
   1094                 expandable = false;
   1095             }
   1096         }
   1097         if (mExpandedChild != null) {
   1098             mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
   1099         }
   1100         if (mContractedChild != null) {
   1101             mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
   1102         }
   1103         if (mHeadsUpChild != null) {
   1104             mHeadsUpWrapper.updateExpandability(expandable,  mExpandClickListener);
   1105         }
   1106     }
   1107 
   1108     public NotificationHeaderView getNotificationHeader() {
   1109         NotificationHeaderView header = null;
   1110         if (mContractedChild != null) {
   1111             header = mContractedWrapper.getNotificationHeader();
   1112         }
   1113         if (header == null && mExpandedChild != null) {
   1114             header = mExpandedWrapper.getNotificationHeader();
   1115         }
   1116         if (header == null && mHeadsUpChild != null) {
   1117             header = mHeadsUpWrapper.getNotificationHeader();
   1118         }
   1119         return header;
   1120     }
   1121 
   1122     public NotificationHeaderView getVisibleNotificationHeader() {
   1123         NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
   1124         return wrapper == null ? null : wrapper.getNotificationHeader();
   1125     }
   1126 
   1127     public void setContainingNotification(ExpandableNotificationRow containingNotification) {
   1128         mContainingNotification = containingNotification;
   1129     }
   1130 
   1131     public void requestSelectLayout(boolean needsAnimation) {
   1132         selectLayout(needsAnimation, false);
   1133     }
   1134 
   1135     public void reInflateViews() {
   1136         if (mIsChildInGroup && mSingleLineView != null) {
   1137             removeView(mSingleLineView);
   1138             mSingleLineView = null;
   1139             updateSingleLineView();
   1140         }
   1141     }
   1142 
   1143     public void setUserExpanding(boolean userExpanding) {
   1144         mUserExpanding = userExpanding;
   1145         if (userExpanding) {
   1146             mTransformationStartVisibleType = mVisibleType;
   1147         } else {
   1148             mTransformationStartVisibleType = UNDEFINED;
   1149             mVisibleType = calculateVisibleType();
   1150             updateViewVisibilities(mVisibleType);
   1151             updateBackgroundColor(false);
   1152         }
   1153     }
   1154 
   1155     /**
   1156      * Set by how much the single line view should be indented. Used when a overflow indicator is
   1157      * present and only during measuring
   1158      */
   1159     public void setSingleLineWidthIndention(int singleLineWidthIndention) {
   1160         if (singleLineWidthIndention != mSingleLineWidthIndention) {
   1161             mSingleLineWidthIndention = singleLineWidthIndention;
   1162             mContainingNotification.forceLayout();
   1163             forceLayout();
   1164         }
   1165     }
   1166 
   1167     public HybridNotificationView getSingleLineView() {
   1168         return mSingleLineView;
   1169     }
   1170 
   1171     public void setRemoved() {
   1172         if (mExpandedRemoteInput != null) {
   1173             mExpandedRemoteInput.setRemoved();
   1174         }
   1175         if (mHeadsUpRemoteInput != null) {
   1176             mHeadsUpRemoteInput.setRemoved();
   1177         }
   1178     }
   1179 
   1180     public void setContentHeightAnimating(boolean animating) {
   1181         if (!animating) {
   1182             mContentHeightAtAnimationStart = UNDEFINED;
   1183         }
   1184     }
   1185 
   1186     public void setHeadsupDisappearRunning(boolean headsupDisappearRunning) {
   1187         mHeadsupDisappearRunning = headsupDisappearRunning;
   1188         selectLayout(false /* animate */, true /* force */);
   1189     }
   1190 
   1191     public void setFocusOnVisibilityChange() {
   1192         mFocusOnVisibilityChange = true;
   1193     }
   1194 }
   1195