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.ArraySet;
     27 import android.util.AttributeSet;
     28 import android.util.Log;
     29 import android.view.MotionEvent;
     30 import android.view.NotificationHeaderView;
     31 import android.view.View;
     32 import android.view.ViewGroup;
     33 import android.view.ViewTreeObserver;
     34 import android.widget.FrameLayout;
     35 import android.widget.ImageView;
     36 import android.widget.LinearLayout;
     37 
     38 import com.android.internal.annotations.VisibleForTesting;
     39 import com.android.internal.util.NotificationColorUtil;
     40 import com.android.systemui.Dependency;
     41 import com.android.systemui.R;
     42 import com.android.systemui.statusbar.notification.HybridGroupManager;
     43 import com.android.systemui.statusbar.notification.HybridNotificationView;
     44 import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
     45 import com.android.systemui.statusbar.notification.NotificationUtils;
     46 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
     47 import com.android.systemui.statusbar.phone.NotificationGroupManager;
     48 import com.android.systemui.statusbar.policy.RemoteInputView;
     49 import com.android.systemui.statusbar.policy.SmartReplyConstants;
     50 import com.android.systemui.statusbar.policy.SmartReplyView;
     51 
     52 /**
     53  * A frame layout containing the actual payload of the notification, including the contracted,
     54  * expanded and heads up layout. This class is responsible for clipping the content and and
     55  * switching between the expanded, contracted and the heads up view depending on its clipped size.
     56  */
     57 public class NotificationContentView extends FrameLayout {
     58 
     59     private static final String TAG = "NotificationContentView";
     60     public static final int VISIBLE_TYPE_CONTRACTED = 0;
     61     public static final int VISIBLE_TYPE_EXPANDED = 1;
     62     public static final int VISIBLE_TYPE_HEADSUP = 2;
     63     private static final int VISIBLE_TYPE_SINGLELINE = 3;
     64     public static final int VISIBLE_TYPE_AMBIENT = 4;
     65     private static final int VISIBLE_TYPE_AMBIENT_SINGLELINE = 5;
     66     public static final int UNDEFINED = -1;
     67 
     68     private final Rect mClipBounds = new Rect();
     69 
     70     private int mMinContractedHeight;
     71     private int mNotificationContentMarginEnd;
     72     private View mContractedChild;
     73     private View mExpandedChild;
     74     private View mHeadsUpChild;
     75     private HybridNotificationView mSingleLineView;
     76     private View mAmbientChild;
     77     private HybridNotificationView mAmbientSingleLineChild;
     78 
     79     private RemoteInputView mExpandedRemoteInput;
     80     private RemoteInputView mHeadsUpRemoteInput;
     81 
     82     private SmartReplyConstants mSmartReplyConstants;
     83     private SmartReplyView mExpandedSmartReplyView;
     84     private SmartReplyController mSmartReplyController;
     85 
     86     private NotificationViewWrapper mContractedWrapper;
     87     private NotificationViewWrapper mExpandedWrapper;
     88     private NotificationViewWrapper mHeadsUpWrapper;
     89     private NotificationViewWrapper mAmbientWrapper;
     90     private HybridGroupManager mHybridGroupManager;
     91     private int mClipTopAmount;
     92     private int mContentHeight;
     93     private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
     94     private boolean mDark;
     95     private boolean mAnimate;
     96     private boolean mIsHeadsUp;
     97     private boolean mLegacy;
     98     private boolean mIsChildInGroup;
     99     private int mSmallHeight;
    100     private int mHeadsUpHeight;
    101     private int mNotificationMaxHeight;
    102     private int mNotificationAmbientHeight;
    103     private StatusBarNotification mStatusBarNotification;
    104     private NotificationGroupManager mGroupManager;
    105     private RemoteInputController mRemoteInputController;
    106     private Runnable mExpandedVisibleListener;
    107 
    108     private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
    109             = new ViewTreeObserver.OnPreDrawListener() {
    110         @Override
    111         public boolean onPreDraw() {
    112             // We need to post since we don't want the notification to animate on the very first
    113             // frame
    114             post(new Runnable() {
    115                 @Override
    116                 public void run() {
    117                     mAnimate = true;
    118                 }
    119             });
    120             getViewTreeObserver().removeOnPreDrawListener(this);
    121             return true;
    122         }
    123     };
    124 
    125     private OnClickListener mExpandClickListener;
    126     private boolean mBeforeN;
    127     private boolean mExpandable;
    128     private boolean mClipToActualHeight = true;
    129     private ExpandableNotificationRow mContainingNotification;
    130     /** The visible type at the start of a touch driven transformation */
    131     private int mTransformationStartVisibleType;
    132     /** The visible type at the start of an animation driven transformation */
    133     private int mAnimationStartVisibleType = UNDEFINED;
    134     private boolean mUserExpanding;
    135     private int mSingleLineWidthIndention;
    136     private boolean mForceSelectNextLayout = true;
    137     private PendingIntent mPreviousExpandedRemoteInputIntent;
    138     private PendingIntent mPreviousHeadsUpRemoteInputIntent;
    139     private RemoteInputView mCachedExpandedRemoteInput;
    140     private RemoteInputView mCachedHeadsUpRemoteInput;
    141 
    142     private int mContentHeightAtAnimationStart = UNDEFINED;
    143     private boolean mFocusOnVisibilityChange;
    144     private boolean mHeadsUpAnimatingAway;
    145     private boolean mIconsVisible;
    146     private int mClipBottomAmount;
    147     private boolean mIsLowPriority;
    148     private boolean mIsContentExpandable;
    149     private boolean mRemoteInputVisible;
    150     private int mUnrestrictedContentHeight;
    151 
    152 
    153     public NotificationContentView(Context context, AttributeSet attrs) {
    154         super(context, attrs);
    155         mHybridGroupManager = new HybridGroupManager(getContext(), this);
    156         mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
    157         mSmartReplyController = Dependency.get(SmartReplyController.class);
    158         initView();
    159     }
    160 
    161     public void initView() {
    162         mMinContractedHeight = getResources().getDimensionPixelSize(
    163                 R.dimen.min_notification_layout_height);
    164         mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
    165                 com.android.internal.R.dimen.notification_content_margin_end);
    166     }
    167 
    168     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
    169             int ambientHeight) {
    170         mSmallHeight = smallHeight;
    171         mHeadsUpHeight = headsUpMaxHeight;
    172         mNotificationMaxHeight = maxHeight;
    173         mNotificationAmbientHeight = ambientHeight;
    174     }
    175 
    176     @Override
    177     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    178         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    179         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
    180         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
    181         int maxSize = Integer.MAX_VALUE / 2;
    182         int width = MeasureSpec.getSize(widthMeasureSpec);
    183         if (hasFixedHeight || isHeightLimited) {
    184             maxSize = MeasureSpec.getSize(heightMeasureSpec);
    185         }
    186         int maxChildHeight = 0;
    187         if (mExpandedChild != null) {
    188             int notificationMaxHeight = mNotificationMaxHeight;
    189             if (mExpandedSmartReplyView != null) {
    190                 notificationMaxHeight += mExpandedSmartReplyView.getHeightUpperLimit();
    191             }
    192             notificationMaxHeight += mExpandedWrapper.getExtraMeasureHeight();
    193             int size = notificationMaxHeight;
    194             ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
    195             boolean useExactly = false;
    196             if (layoutParams.height >= 0) {
    197                 // An actual height is set
    198                 size = Math.min(size, layoutParams.height);
    199                 useExactly = true;
    200             }
    201             int spec = MeasureSpec.makeMeasureSpec(size, useExactly
    202                             ? MeasureSpec.EXACTLY
    203                             : MeasureSpec.AT_MOST);
    204             measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
    205             maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
    206         }
    207         if (mContractedChild != null) {
    208             int heightSpec;
    209             int size = mSmallHeight;
    210             ViewGroup.LayoutParams layoutParams = mContractedChild.getLayoutParams();
    211             boolean useExactly = false;
    212             if (layoutParams.height >= 0) {
    213                 // An actual height is set
    214                 size = Math.min(size, layoutParams.height);
    215                 useExactly = true;
    216             }
    217             if (shouldContractedBeFixedSize() || useExactly) {
    218                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
    219             } else {
    220                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
    221             }
    222             measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
    223             int measuredHeight = mContractedChild.getMeasuredHeight();
    224             if (measuredHeight < mMinContractedHeight) {
    225                 heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
    226                 measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
    227             }
    228             maxChildHeight = Math.max(maxChildHeight, measuredHeight);
    229             if (updateContractedHeaderWidth()) {
    230                 measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
    231             }
    232             if (mExpandedChild != null
    233                     && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
    234                 // the Expanded child is smaller then the collapsed. Let's remeasure it.
    235                 heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
    236                         MeasureSpec.EXACTLY);
    237                 measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
    238             }
    239         }
    240         if (mHeadsUpChild != null) {
    241             int maxHeight = mHeadsUpHeight;
    242             maxHeight += mHeadsUpWrapper.getExtraMeasureHeight();
    243             int size = maxHeight;
    244             ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
    245             boolean useExactly = false;
    246             if (layoutParams.height >= 0) {
    247                 // An actual height is set
    248                 size = Math.min(size, layoutParams.height);
    249                 useExactly = true;
    250             }
    251             measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
    252                     MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
    253                             : MeasureSpec.AT_MOST), 0);
    254             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
    255         }
    256         if (mSingleLineView != null) {
    257             int singleLineWidthSpec = widthMeasureSpec;
    258             if (mSingleLineWidthIndention != 0
    259                     && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
    260                 singleLineWidthSpec = MeasureSpec.makeMeasureSpec(
    261                         width - mSingleLineWidthIndention + mSingleLineView.getPaddingEnd(),
    262                         MeasureSpec.EXACTLY);
    263             }
    264             mSingleLineView.measure(singleLineWidthSpec,
    265                     MeasureSpec.makeMeasureSpec(mNotificationMaxHeight, MeasureSpec.AT_MOST));
    266             maxChildHeight = Math.max(maxChildHeight, mSingleLineView.getMeasuredHeight());
    267         }
    268         if (mAmbientChild != null) {
    269             int size = mNotificationAmbientHeight;
    270             ViewGroup.LayoutParams layoutParams = mAmbientChild.getLayoutParams();
    271             boolean useExactly = false;
    272             if (layoutParams.height >= 0) {
    273                 // An actual height is set
    274                 size = Math.min(size, layoutParams.height);
    275                 useExactly = true;
    276             }
    277             mAmbientChild.measure(widthMeasureSpec,
    278                     MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
    279                             : MeasureSpec.AT_MOST));
    280             maxChildHeight = Math.max(maxChildHeight, mAmbientChild.getMeasuredHeight());
    281         }
    282         if (mAmbientSingleLineChild != null) {
    283             int size = mNotificationAmbientHeight;
    284             ViewGroup.LayoutParams layoutParams = mAmbientSingleLineChild.getLayoutParams();
    285             boolean useExactly = false;
    286             if (layoutParams.height >= 0) {
    287                 // An actual height is set
    288                 size = Math.min(size, layoutParams.height);
    289                 useExactly = true;
    290             }
    291             int ambientSingleLineWidthSpec = widthMeasureSpec;
    292             if (mSingleLineWidthIndention != 0
    293                     && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
    294                 ambientSingleLineWidthSpec = MeasureSpec.makeMeasureSpec(
    295                         width - mSingleLineWidthIndention + mAmbientSingleLineChild.getPaddingEnd(),
    296                         MeasureSpec.EXACTLY);
    297             }
    298             mAmbientSingleLineChild.measure(ambientSingleLineWidthSpec,
    299                     MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
    300                             : MeasureSpec.AT_MOST));
    301             maxChildHeight = Math.max(maxChildHeight, mAmbientSingleLineChild.getMeasuredHeight());
    302         }
    303         int ownHeight = Math.min(maxChildHeight, maxSize);
    304         setMeasuredDimension(width, ownHeight);
    305     }
    306 
    307     /**
    308      * Get the extra height that needs to be added to the notification height for a given
    309      * {@link RemoteInputView}.
    310      * This is needed when the user is inline replying in order to ensure that the reply bar has
    311      * enough padding.
    312      *
    313      * @param remoteInput The remote input to check.
    314      * @return The extra height needed.
    315      */
    316     private int getExtraRemoteInputHeight(RemoteInputView remoteInput) {
    317         if (remoteInput != null && (remoteInput.isActive() || remoteInput.isSending())) {
    318             return getResources().getDimensionPixelSize(
    319                     com.android.internal.R.dimen.notification_content_margin);
    320         }
    321         return 0;
    322     }
    323 
    324     private boolean updateContractedHeaderWidth() {
    325         // We need to update the expanded and the collapsed header to have exactly the same with to
    326         // have the expand buttons laid out at the same location.
    327         NotificationHeaderView contractedHeader = mContractedWrapper.getNotificationHeader();
    328         if (contractedHeader != null) {
    329             if (mExpandedChild != null
    330                     && mExpandedWrapper.getNotificationHeader() != null) {
    331                 NotificationHeaderView expandedHeader = mExpandedWrapper.getNotificationHeader();
    332                 int expandedSize = expandedHeader.getMeasuredWidth()
    333                         - expandedHeader.getPaddingEnd();
    334                 int collapsedSize = contractedHeader.getMeasuredWidth()
    335                         - expandedHeader.getPaddingEnd();
    336                 if (expandedSize != collapsedSize) {
    337                     int paddingEnd = contractedHeader.getMeasuredWidth() - expandedSize;
    338                     contractedHeader.setPadding(
    339                             contractedHeader.isLayoutRtl()
    340                                     ? paddingEnd
    341                                     : contractedHeader.getPaddingLeft(),
    342                             contractedHeader.getPaddingTop(),
    343                             contractedHeader.isLayoutRtl()
    344                                     ? contractedHeader.getPaddingLeft()
    345                                     : paddingEnd,
    346                             contractedHeader.getPaddingBottom());
    347                     contractedHeader.setShowWorkBadgeAtEnd(true);
    348                     return true;
    349                 }
    350             } else {
    351                 int paddingEnd = mNotificationContentMarginEnd;
    352                 if (contractedHeader.getPaddingEnd() != paddingEnd) {
    353                     contractedHeader.setPadding(
    354                             contractedHeader.isLayoutRtl()
    355                                     ? paddingEnd
    356                                     : contractedHeader.getPaddingLeft(),
    357                             contractedHeader.getPaddingTop(),
    358                             contractedHeader.isLayoutRtl()
    359                                     ? contractedHeader.getPaddingLeft()
    360                                     : paddingEnd,
    361                             contractedHeader.getPaddingBottom());
    362                     contractedHeader.setShowWorkBadgeAtEnd(false);
    363                     return true;
    364                 }
    365             }
    366         }
    367         return false;
    368     }
    369 
    370     private boolean shouldContractedBeFixedSize() {
    371         return mBeforeN && mContractedWrapper instanceof NotificationCustomViewWrapper;
    372     }
    373 
    374     @Override
    375     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    376         int previousHeight = 0;
    377         if (mExpandedChild != null) {
    378             previousHeight = mExpandedChild.getHeight();
    379         }
    380         super.onLayout(changed, left, top, right, bottom);
    381         if (previousHeight != 0 && mExpandedChild.getHeight() != previousHeight) {
    382             mContentHeightAtAnimationStart = previousHeight;
    383         }
    384         updateClipping();
    385         invalidateOutline();
    386         selectLayout(false /* animate */, mForceSelectNextLayout /* force */);
    387         mForceSelectNextLayout = false;
    388         updateExpandButtons(mExpandable);
    389     }
    390 
    391     @Override
    392     protected void onAttachedToWindow() {
    393         super.onAttachedToWindow();
    394         updateVisibility();
    395     }
    396 
    397     public View getContractedChild() {
    398         return mContractedChild;
    399     }
    400 
    401     public View getExpandedChild() {
    402         return mExpandedChild;
    403     }
    404 
    405     public View getHeadsUpChild() {
    406         return mHeadsUpChild;
    407     }
    408 
    409     public View getAmbientChild() {
    410         return mAmbientChild;
    411     }
    412 
    413     public HybridNotificationView getAmbientSingleLineChild() {
    414         return mAmbientSingleLineChild;
    415     }
    416 
    417     public void setContractedChild(View child) {
    418         if (mContractedChild != null) {
    419             mContractedChild.animate().cancel();
    420             removeView(mContractedChild);
    421         }
    422         addView(child);
    423         mContractedChild = child;
    424         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
    425                 mContainingNotification);
    426     }
    427 
    428     private NotificationViewWrapper getWrapperForView(View child) {
    429         if (child == mContractedChild) {
    430             return mContractedWrapper;
    431         }
    432         if (child == mExpandedChild) {
    433             return mExpandedWrapper;
    434         }
    435         if (child == mHeadsUpChild) {
    436             return mHeadsUpWrapper;
    437         }
    438         if (child == mAmbientChild) {
    439             return mAmbientWrapper;
    440         }
    441         return null;
    442     }
    443 
    444     public void setExpandedChild(View child) {
    445         if (mExpandedChild != null) {
    446             mPreviousExpandedRemoteInputIntent = null;
    447             if (mExpandedRemoteInput != null) {
    448                 mExpandedRemoteInput.onNotificationUpdateOrReset();
    449                 if (mExpandedRemoteInput.isActive()) {
    450                     mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent();
    451                     mCachedExpandedRemoteInput = mExpandedRemoteInput;
    452                     mExpandedRemoteInput.dispatchStartTemporaryDetach();
    453                     ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
    454                 }
    455             }
    456             mExpandedChild.animate().cancel();
    457             removeView(mExpandedChild);
    458             mExpandedRemoteInput = null;
    459         }
    460         if (child == null) {
    461             mExpandedChild = null;
    462             mExpandedWrapper = null;
    463             if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
    464                 mVisibleType = VISIBLE_TYPE_CONTRACTED;
    465             }
    466             if (mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED) {
    467                 mTransformationStartVisibleType = UNDEFINED;
    468             }
    469             return;
    470         }
    471         addView(child);
    472         mExpandedChild = child;
    473         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
    474                 mContainingNotification);
    475     }
    476 
    477     public void setHeadsUpChild(View child) {
    478         if (mHeadsUpChild != null) {
    479             mPreviousHeadsUpRemoteInputIntent = null;
    480             if (mHeadsUpRemoteInput != null) {
    481                 mHeadsUpRemoteInput.onNotificationUpdateOrReset();
    482                 if (mHeadsUpRemoteInput.isActive()) {
    483                     mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent();
    484                     mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput;
    485                     mHeadsUpRemoteInput.dispatchStartTemporaryDetach();
    486                     ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
    487                 }
    488             }
    489             mHeadsUpChild.animate().cancel();
    490             removeView(mHeadsUpChild);
    491             mHeadsUpRemoteInput = null;
    492         }
    493         if (child == null) {
    494             mHeadsUpChild = null;
    495             mHeadsUpWrapper = null;
    496             if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
    497                 mVisibleType = VISIBLE_TYPE_CONTRACTED;
    498             }
    499             if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
    500                 mTransformationStartVisibleType = UNDEFINED;
    501             }
    502             return;
    503         }
    504         addView(child);
    505         mHeadsUpChild = child;
    506         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
    507                 mContainingNotification);
    508     }
    509 
    510     public void setAmbientChild(View child) {
    511         if (mAmbientChild != null) {
    512             mAmbientChild.animate().cancel();
    513             removeView(mAmbientChild);
    514         }
    515         if (child == null) {
    516             return;
    517         }
    518         addView(child);
    519         mAmbientChild = child;
    520         mAmbientWrapper = NotificationViewWrapper.wrap(getContext(), child,
    521                 mContainingNotification);
    522     }
    523 
    524     @Override
    525     protected void onVisibilityChanged(View changedView, int visibility) {
    526         super.onVisibilityChanged(changedView, visibility);
    527         updateVisibility();
    528     }
    529 
    530     private void updateVisibility() {
    531         setVisible(isShown());
    532     }
    533 
    534     @Override
    535     protected void onDetachedFromWindow() {
    536         super.onDetachedFromWindow();
    537         getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
    538     }
    539 
    540     private void setVisible(final boolean isVisible) {
    541         if (isVisible) {
    542             // This call can happen multiple times, but removing only removes a single one.
    543             // We therefore need to remove the old one.
    544             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
    545             // We only animate if we are drawn at least once, otherwise the view might animate when
    546             // it's shown the first time
    547             getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
    548         } else {
    549             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
    550             mAnimate = false;
    551         }
    552     }
    553 
    554     private void focusExpandButtonIfNecessary() {
    555         if (mFocusOnVisibilityChange) {
    556             NotificationHeaderView header = getVisibleNotificationHeader();
    557             if (header != null) {
    558                 ImageView expandButton = header.getExpandButton();
    559                 if (expandButton != null) {
    560                     expandButton.requestAccessibilityFocus();
    561                 }
    562             }
    563             mFocusOnVisibilityChange = false;
    564         }
    565     }
    566 
    567     public void setContentHeight(int contentHeight) {
    568         mUnrestrictedContentHeight = Math.max(contentHeight, getMinHeight());
    569         int maxContentHeight = mContainingNotification.getIntrinsicHeight()
    570                 - getExtraRemoteInputHeight(mExpandedRemoteInput)
    571                 - getExtraRemoteInputHeight(mHeadsUpRemoteInput);
    572         mContentHeight = Math.min(mUnrestrictedContentHeight, maxContentHeight);
    573         selectLayout(mAnimate /* animate */, false /* force */);
    574 
    575         int minHeightHint = getMinContentHeightHint();
    576 
    577         NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
    578         if (wrapper != null) {
    579             wrapper.setContentHeight(mUnrestrictedContentHeight, minHeightHint);
    580         }
    581 
    582         wrapper = getVisibleWrapper(mTransformationStartVisibleType);
    583         if (wrapper != null) {
    584             wrapper.setContentHeight(mUnrestrictedContentHeight, minHeightHint);
    585         }
    586 
    587         updateClipping();
    588         invalidateOutline();
    589     }
    590 
    591     /**
    592      * @return the minimum apparent height that the wrapper should allow for the purpose
    593      *         of aligning elements at the bottom edge. If this is larger than the content
    594      *         height, the notification is clipped instead of being further shrunk.
    595      */
    596     private int getMinContentHeightHint() {
    597         if (mIsChildInGroup && isVisibleOrTransitioning(VISIBLE_TYPE_SINGLELINE)) {
    598             return mContext.getResources().getDimensionPixelSize(
    599                         com.android.internal.R.dimen.notification_action_list_height);
    600         }
    601 
    602         // Transition between heads-up & expanded, or pinned.
    603         if (mHeadsUpChild != null && mExpandedChild != null) {
    604             boolean transitioningBetweenHunAndExpanded =
    605                     isTransitioningFromTo(VISIBLE_TYPE_HEADSUP, VISIBLE_TYPE_EXPANDED) ||
    606                     isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP);
    607             boolean pinned = !isVisibleOrTransitioning(VISIBLE_TYPE_CONTRACTED)
    608                     && (mIsHeadsUp || mHeadsUpAnimatingAway)
    609                     && !mContainingNotification.isOnKeyguard();
    610             if (transitioningBetweenHunAndExpanded || pinned) {
    611                 return Math.min(getViewHeight(VISIBLE_TYPE_HEADSUP),
    612                         getViewHeight(VISIBLE_TYPE_EXPANDED));
    613             }
    614         }
    615 
    616         // Size change of the expanded version
    617         if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart >= 0
    618                 && mExpandedChild != null) {
    619             return Math.min(mContentHeightAtAnimationStart, getViewHeight(VISIBLE_TYPE_EXPANDED));
    620         }
    621 
    622         int hint;
    623         if (mAmbientChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_AMBIENT)) {
    624             hint = mAmbientChild.getHeight();
    625         } else if (mAmbientSingleLineChild != null && isVisibleOrTransitioning(
    626                 VISIBLE_TYPE_AMBIENT_SINGLELINE)) {
    627             hint = mAmbientSingleLineChild.getHeight();
    628         } else if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
    629             hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
    630         } else if (mExpandedChild != null) {
    631             hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
    632         } else {
    633             hint = getViewHeight(VISIBLE_TYPE_CONTRACTED)
    634                     + mContext.getResources().getDimensionPixelSize(
    635                             com.android.internal.R.dimen.notification_action_list_height);
    636         }
    637 
    638         if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) {
    639             hint = Math.min(hint, getViewHeight(VISIBLE_TYPE_EXPANDED));
    640         }
    641         return hint;
    642     }
    643 
    644     private boolean isTransitioningFromTo(int from, int to) {
    645         return (mTransformationStartVisibleType == from || mAnimationStartVisibleType == from)
    646                 && mVisibleType == to;
    647     }
    648 
    649     private boolean isVisibleOrTransitioning(int type) {
    650         return mVisibleType == type || mTransformationStartVisibleType == type
    651                 || mAnimationStartVisibleType == type;
    652     }
    653 
    654     private void updateContentTransformation() {
    655         int visibleType = calculateVisibleType();
    656         if (visibleType != mVisibleType) {
    657             // A new transformation starts
    658             mTransformationStartVisibleType = mVisibleType;
    659             final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
    660             final TransformableView hiddenView = getTransformableViewForVisibleType(
    661                     mTransformationStartVisibleType);
    662             shownView.transformFrom(hiddenView, 0.0f);
    663             getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
    664             hiddenView.transformTo(shownView, 0.0f);
    665             mVisibleType = visibleType;
    666             updateBackgroundColor(true /* animate */);
    667         }
    668         if (mForceSelectNextLayout) {
    669             forceUpdateVisibilities();
    670         }
    671         if (mTransformationStartVisibleType != UNDEFINED
    672                 && mVisibleType != mTransformationStartVisibleType
    673                 && getViewForVisibleType(mTransformationStartVisibleType) != null) {
    674             final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType);
    675             final TransformableView hiddenView = getTransformableViewForVisibleType(
    676                     mTransformationStartVisibleType);
    677             float transformationAmount = calculateTransformationAmount();
    678             shownView.transformFrom(hiddenView, transformationAmount);
    679             hiddenView.transformTo(shownView, transformationAmount);
    680             updateBackgroundTransformation(transformationAmount);
    681         } else {
    682             updateViewVisibilities(visibleType);
    683             updateBackgroundColor(false);
    684         }
    685     }
    686 
    687     private void updateBackgroundTransformation(float transformationAmount) {
    688         int endColor = getBackgroundColor(mVisibleType);
    689         int startColor = getBackgroundColor(mTransformationStartVisibleType);
    690         if (endColor != startColor) {
    691             if (startColor == 0) {
    692                 startColor = mContainingNotification.getBackgroundColorWithoutTint();
    693             }
    694             if (endColor == 0) {
    695                 endColor = mContainingNotification.getBackgroundColorWithoutTint();
    696             }
    697             endColor = NotificationUtils.interpolateColors(startColor, endColor,
    698                     transformationAmount);
    699         }
    700         mContainingNotification.updateBackgroundAlpha(transformationAmount);
    701         mContainingNotification.setContentBackground(endColor, false, this);
    702     }
    703 
    704     private float calculateTransformationAmount() {
    705         int startHeight = getViewHeight(mTransformationStartVisibleType);
    706         int endHeight = getViewHeight(mVisibleType);
    707         int progress = Math.abs(mContentHeight - startHeight);
    708         int totalDistance = Math.abs(endHeight - startHeight);
    709         if (totalDistance == 0) {
    710             Log.wtf(TAG, "the total transformation distance is 0"
    711                     + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
    712                     + "\n VisibleType: " + mVisibleType + " height: " + endHeight
    713                     + "\n mContentHeight: " + mContentHeight);
    714             return 1.0f;
    715         }
    716         float amount = (float) progress / (float) totalDistance;
    717         return Math.min(1.0f, amount);
    718     }
    719 
    720     public int getContentHeight() {
    721         return mContentHeight;
    722     }
    723 
    724     public int getMaxHeight() {
    725         if (mContainingNotification.isShowingAmbient()) {
    726             return getShowingAmbientView().getHeight();
    727         } else if (mExpandedChild != null) {
    728             return getViewHeight(VISIBLE_TYPE_EXPANDED)
    729                     + getExtraRemoteInputHeight(mExpandedRemoteInput);
    730         } else if (mIsHeadsUp && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) {
    731             return getViewHeight(VISIBLE_TYPE_HEADSUP)
    732                     + getExtraRemoteInputHeight(mHeadsUpRemoteInput);
    733         }
    734         return getViewHeight(VISIBLE_TYPE_CONTRACTED);
    735     }
    736 
    737     private int getViewHeight(int visibleType) {
    738         View view = getViewForVisibleType(visibleType);
    739         int height = view.getHeight();
    740         NotificationViewWrapper viewWrapper = getWrapperForView(view);
    741         if (viewWrapper != null) {
    742             height += viewWrapper.getHeaderTranslation();
    743         }
    744         return height;
    745     }
    746 
    747     public int getMinHeight() {
    748         return getMinHeight(false /* likeGroupExpanded */);
    749     }
    750 
    751     public int getMinHeight(boolean likeGroupExpanded) {
    752         if (mContainingNotification.isShowingAmbient()) {
    753             return getShowingAmbientView().getHeight();
    754         } else if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
    755             return getViewHeight(VISIBLE_TYPE_CONTRACTED);
    756         } else {
    757             return mSingleLineView.getHeight();
    758         }
    759     }
    760 
    761     public View getShowingAmbientView() {
    762         View v = mIsChildInGroup ? mAmbientSingleLineChild : mAmbientChild;
    763         if (v != null) {
    764             return v;
    765         } else {
    766             return mContractedChild;
    767         }
    768     }
    769 
    770     private boolean isGroupExpanded() {
    771         return mGroupManager.isGroupExpanded(mStatusBarNotification);
    772     }
    773 
    774     public void setClipTopAmount(int clipTopAmount) {
    775         mClipTopAmount = clipTopAmount;
    776         updateClipping();
    777     }
    778 
    779 
    780     public void setClipBottomAmount(int clipBottomAmount) {
    781         mClipBottomAmount = clipBottomAmount;
    782         updateClipping();
    783     }
    784 
    785     @Override
    786     public void setTranslationY(float translationY) {
    787         super.setTranslationY(translationY);
    788         updateClipping();
    789     }
    790 
    791     private void updateClipping() {
    792         if (mClipToActualHeight) {
    793             int top = (int) (mClipTopAmount - getTranslationY());
    794             int bottom = (int) (mUnrestrictedContentHeight - mClipBottomAmount - getTranslationY());
    795             bottom = Math.max(top, bottom);
    796             mClipBounds.set(0, top, getWidth(), bottom);
    797             setClipBounds(mClipBounds);
    798         } else {
    799             setClipBounds(null);
    800         }
    801     }
    802 
    803     public void setClipToActualHeight(boolean clipToActualHeight) {
    804         mClipToActualHeight = clipToActualHeight;
    805         updateClipping();
    806     }
    807 
    808     private void selectLayout(boolean animate, boolean force) {
    809         if (mContractedChild == null) {
    810             return;
    811         }
    812         if (mUserExpanding) {
    813             updateContentTransformation();
    814         } else {
    815             int visibleType = calculateVisibleType();
    816             boolean changedType = visibleType != mVisibleType;
    817             if (changedType || force) {
    818                 View visibleView = getViewForVisibleType(visibleType);
    819                 if (visibleView != null) {
    820                     visibleView.setVisibility(VISIBLE);
    821                     transferRemoteInputFocus(visibleType);
    822                 }
    823 
    824                 if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
    825                         || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
    826                         || (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
    827                         || visibleType == VISIBLE_TYPE_CONTRACTED)) {
    828                     animateToVisibleType(visibleType);
    829                 } else {
    830                     updateViewVisibilities(visibleType);
    831                 }
    832                 mVisibleType = visibleType;
    833                 if (changedType) {
    834                     focusExpandButtonIfNecessary();
    835                 }
    836                 NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
    837                 if (visibleWrapper != null) {
    838                     visibleWrapper.setContentHeight(mUnrestrictedContentHeight,
    839                             getMinContentHeightHint());
    840                 }
    841                 updateBackgroundColor(animate);
    842             }
    843         }
    844     }
    845 
    846     private void forceUpdateVisibilities() {
    847         forceUpdateVisibility(VISIBLE_TYPE_CONTRACTED, mContractedChild, mContractedWrapper);
    848         forceUpdateVisibility(VISIBLE_TYPE_EXPANDED, mExpandedChild, mExpandedWrapper);
    849         forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
    850         forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
    851         forceUpdateVisibility(VISIBLE_TYPE_AMBIENT, mAmbientChild, mAmbientWrapper);
    852         forceUpdateVisibility(VISIBLE_TYPE_AMBIENT_SINGLELINE, mAmbientSingleLineChild,
    853                 mAmbientSingleLineChild);
    854         fireExpandedVisibleListenerIfVisible();
    855         // forceUpdateVisibilities cancels outstanding animations without updating the
    856         // mAnimationStartVisibleType. Do so here instead.
    857         mAnimationStartVisibleType = UNDEFINED;
    858     }
    859 
    860     private void fireExpandedVisibleListenerIfVisible() {
    861         if (mExpandedVisibleListener != null && mExpandedChild != null && isShown()
    862                 && mExpandedChild.getVisibility() == VISIBLE) {
    863             Runnable listener = mExpandedVisibleListener;
    864             mExpandedVisibleListener = null;
    865             listener.run();
    866         }
    867     }
    868 
    869     private void forceUpdateVisibility(int type, View view, TransformableView wrapper) {
    870         if (view == null) {
    871             return;
    872         }
    873         boolean visible = mVisibleType == type
    874                 || mTransformationStartVisibleType == type;
    875         if (!visible) {
    876             view.setVisibility(INVISIBLE);
    877         } else {
    878             wrapper.setVisible(true);
    879         }
    880     }
    881 
    882     public void updateBackgroundColor(boolean animate) {
    883         int customBackgroundColor = getBackgroundColor(mVisibleType);
    884         mContainingNotification.resetBackgroundAlpha();
    885         mContainingNotification.setContentBackground(customBackgroundColor, animate, this);
    886     }
    887 
    888     public void setBackgroundTintColor(int color) {
    889         if (mExpandedSmartReplyView != null) {
    890             mExpandedSmartReplyView.setBackgroundTintColor(color);
    891         }
    892     }
    893 
    894     public int getVisibleType() {
    895         return mVisibleType;
    896     }
    897 
    898     public int getBackgroundColorForExpansionState() {
    899         // When expanding or user locked we want the new type, when collapsing we want
    900         // the original type
    901         final int visibleType = (mContainingNotification.isGroupExpanded()
    902                 || mContainingNotification.isUserLocked())
    903                         ? calculateVisibleType()
    904                         : getVisibleType();
    905         return getBackgroundColor(visibleType);
    906     }
    907 
    908     public int getBackgroundColor(int visibleType) {
    909         NotificationViewWrapper currentVisibleWrapper = getVisibleWrapper(visibleType);
    910         int customBackgroundColor = 0;
    911         if (currentVisibleWrapper != null) {
    912             customBackgroundColor = currentVisibleWrapper.getCustomBackgroundColor();
    913         }
    914         return customBackgroundColor;
    915     }
    916 
    917     private void updateViewVisibilities(int visibleType) {
    918         updateViewVisibility(visibleType, VISIBLE_TYPE_CONTRACTED,
    919                 mContractedChild, mContractedWrapper);
    920         updateViewVisibility(visibleType, VISIBLE_TYPE_EXPANDED,
    921                 mExpandedChild, mExpandedWrapper);
    922         updateViewVisibility(visibleType, VISIBLE_TYPE_HEADSUP,
    923                 mHeadsUpChild, mHeadsUpWrapper);
    924         updateViewVisibility(visibleType, VISIBLE_TYPE_SINGLELINE,
    925                 mSingleLineView, mSingleLineView);
    926         updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT,
    927                 mAmbientChild, mAmbientWrapper);
    928         updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT_SINGLELINE,
    929                 mAmbientSingleLineChild, mAmbientSingleLineChild);
    930         fireExpandedVisibleListenerIfVisible();
    931         // updateViewVisibilities cancels outstanding animations without updating the
    932         // mAnimationStartVisibleType. Do so here instead.
    933         mAnimationStartVisibleType = UNDEFINED;
    934     }
    935 
    936     private void updateViewVisibility(int visibleType, int type, View view,
    937             TransformableView wrapper) {
    938         if (view != null) {
    939             wrapper.setVisible(visibleType == type);
    940         }
    941     }
    942 
    943     private void animateToVisibleType(int visibleType) {
    944         final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
    945         final TransformableView hiddenView = getTransformableViewForVisibleType(mVisibleType);
    946         if (shownView == hiddenView || hiddenView == null) {
    947             shownView.setVisible(true);
    948             return;
    949         }
    950         mAnimationStartVisibleType = mVisibleType;
    951         shownView.transformFrom(hiddenView);
    952         getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
    953         hiddenView.transformTo(shownView, new Runnable() {
    954             @Override
    955             public void run() {
    956                 if (hiddenView != getTransformableViewForVisibleType(mVisibleType)) {
    957                     hiddenView.setVisible(false);
    958                 }
    959                 mAnimationStartVisibleType = UNDEFINED;
    960             }
    961         });
    962         fireExpandedVisibleListenerIfVisible();
    963     }
    964 
    965     private void transferRemoteInputFocus(int visibleType) {
    966         if (visibleType == VISIBLE_TYPE_HEADSUP
    967                 && mHeadsUpRemoteInput != null
    968                 && (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive())) {
    969             mHeadsUpRemoteInput.stealFocusFrom(mExpandedRemoteInput);
    970         }
    971         if (visibleType == VISIBLE_TYPE_EXPANDED
    972                 && mExpandedRemoteInput != null
    973                 && (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive())) {
    974             mExpandedRemoteInput.stealFocusFrom(mHeadsUpRemoteInput);
    975         }
    976     }
    977 
    978     /**
    979      * @param visibleType one of the static enum types in this view
    980      * @return the corresponding transformable view according to the given visible type
    981      */
    982     private TransformableView getTransformableViewForVisibleType(int visibleType) {
    983         switch (visibleType) {
    984             case VISIBLE_TYPE_EXPANDED:
    985                 return mExpandedWrapper;
    986             case VISIBLE_TYPE_HEADSUP:
    987                 return mHeadsUpWrapper;
    988             case VISIBLE_TYPE_SINGLELINE:
    989                 return mSingleLineView;
    990             case VISIBLE_TYPE_AMBIENT:
    991                 return mAmbientWrapper;
    992             case VISIBLE_TYPE_AMBIENT_SINGLELINE:
    993                 return mAmbientSingleLineChild;
    994             default:
    995                 return mContractedWrapper;
    996         }
    997     }
    998 
    999     /**
   1000      * @param visibleType one of the static enum types in this view
   1001      * @return the corresponding view according to the given visible type
   1002      */
   1003     private View getViewForVisibleType(int visibleType) {
   1004         switch (visibleType) {
   1005             case VISIBLE_TYPE_EXPANDED:
   1006                 return mExpandedChild;
   1007             case VISIBLE_TYPE_HEADSUP:
   1008                 return mHeadsUpChild;
   1009             case VISIBLE_TYPE_SINGLELINE:
   1010                 return mSingleLineView;
   1011             case VISIBLE_TYPE_AMBIENT:
   1012                 return mAmbientChild;
   1013             case VISIBLE_TYPE_AMBIENT_SINGLELINE:
   1014                 return mAmbientSingleLineChild;
   1015             default:
   1016                 return mContractedChild;
   1017         }
   1018     }
   1019 
   1020     public NotificationViewWrapper getVisibleWrapper(int visibleType) {
   1021         switch (visibleType) {
   1022             case VISIBLE_TYPE_EXPANDED:
   1023                 return mExpandedWrapper;
   1024             case VISIBLE_TYPE_HEADSUP:
   1025                 return mHeadsUpWrapper;
   1026             case VISIBLE_TYPE_CONTRACTED:
   1027                 return mContractedWrapper;
   1028             case VISIBLE_TYPE_AMBIENT:
   1029                 return mAmbientWrapper;
   1030             default:
   1031                 return null;
   1032         }
   1033     }
   1034 
   1035     /**
   1036      * @return one of the static enum types in this view, calculated form the current state
   1037      */
   1038     public int calculateVisibleType() {
   1039         if (mContainingNotification.isShowingAmbient()) {
   1040             if (mIsChildInGroup && mAmbientSingleLineChild != null) {
   1041                 return VISIBLE_TYPE_AMBIENT_SINGLELINE;
   1042             } else if (mAmbientChild != null) {
   1043                 return VISIBLE_TYPE_AMBIENT;
   1044             } else {
   1045                 return VISIBLE_TYPE_CONTRACTED;
   1046             }
   1047         }
   1048         if (mUserExpanding) {
   1049             int height = !mIsChildInGroup || isGroupExpanded()
   1050                     || mContainingNotification.isExpanded(true /* allowOnKeyguard */)
   1051                     ? mContainingNotification.getMaxContentHeight()
   1052                     : mContainingNotification.getShowingLayout().getMinHeight();
   1053             if (height == 0) {
   1054                 height = mContentHeight;
   1055             }
   1056             int expandedVisualType = getVisualTypeForHeight(height);
   1057             int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
   1058                     ? VISIBLE_TYPE_SINGLELINE
   1059                     : getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
   1060             return mTransformationStartVisibleType == collapsedVisualType
   1061                     ? expandedVisualType
   1062                     : collapsedVisualType;
   1063         }
   1064         int intrinsicHeight = mContainingNotification.getIntrinsicHeight();
   1065         int viewHeight = mContentHeight;
   1066         if (intrinsicHeight != 0) {
   1067             // the intrinsicHeight might be 0 because it was just reset.
   1068             viewHeight = Math.min(mContentHeight, intrinsicHeight);
   1069         }
   1070         return getVisualTypeForHeight(viewHeight);
   1071     }
   1072 
   1073     private int getVisualTypeForHeight(float viewHeight) {
   1074         boolean noExpandedChild = mExpandedChild == null;
   1075         if (!noExpandedChild && viewHeight == getViewHeight(VISIBLE_TYPE_EXPANDED)) {
   1076             return VISIBLE_TYPE_EXPANDED;
   1077         }
   1078         if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
   1079             return VISIBLE_TYPE_SINGLELINE;
   1080         }
   1081 
   1082         if ((mIsHeadsUp || mHeadsUpAnimatingAway) && mHeadsUpChild != null
   1083                 && !mContainingNotification.isOnKeyguard()) {
   1084             if (viewHeight <= getViewHeight(VISIBLE_TYPE_HEADSUP) || noExpandedChild) {
   1085                 return VISIBLE_TYPE_HEADSUP;
   1086             } else {
   1087                 return VISIBLE_TYPE_EXPANDED;
   1088             }
   1089         } else {
   1090             if (noExpandedChild || (viewHeight <= getViewHeight(VISIBLE_TYPE_CONTRACTED)
   1091                     && (!mIsChildInGroup || isGroupExpanded()
   1092                             || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
   1093                 return VISIBLE_TYPE_CONTRACTED;
   1094             } else {
   1095                 return VISIBLE_TYPE_EXPANDED;
   1096             }
   1097         }
   1098     }
   1099 
   1100     public boolean isContentExpandable() {
   1101         return mIsContentExpandable;
   1102     }
   1103 
   1104     public void setDark(boolean dark, boolean fade, long delay) {
   1105         if (mContractedChild == null) {
   1106             return;
   1107         }
   1108         mDark = dark;
   1109         selectLayout(!dark && fade /* animate */, false /* force */);
   1110     }
   1111 
   1112     public void setHeadsUp(boolean headsUp) {
   1113         mIsHeadsUp = headsUp;
   1114         selectLayout(false /* animate */, true /* force */);
   1115         updateExpandButtons(mExpandable);
   1116     }
   1117 
   1118     @Override
   1119     public boolean hasOverlappingRendering() {
   1120 
   1121         // This is not really true, but good enough when fading from the contracted to the expanded
   1122         // layout, and saves us some layers.
   1123         return false;
   1124     }
   1125 
   1126     public void setLegacy(boolean legacy) {
   1127         mLegacy = legacy;
   1128         updateLegacy();
   1129     }
   1130 
   1131     private void updateLegacy() {
   1132         if (mContractedChild != null) {
   1133             mContractedWrapper.setLegacy(mLegacy);
   1134         }
   1135         if (mExpandedChild != null) {
   1136             mExpandedWrapper.setLegacy(mLegacy);
   1137         }
   1138         if (mHeadsUpChild != null) {
   1139             mHeadsUpWrapper.setLegacy(mLegacy);
   1140         }
   1141     }
   1142 
   1143     public void setIsChildInGroup(boolean isChildInGroup) {
   1144         mIsChildInGroup = isChildInGroup;
   1145         if (mContractedChild != null) {
   1146             mContractedWrapper.setIsChildInGroup(mIsChildInGroup);
   1147         }
   1148         if (mExpandedChild != null) {
   1149             mExpandedWrapper.setIsChildInGroup(mIsChildInGroup);
   1150         }
   1151         if (mHeadsUpChild != null) {
   1152             mHeadsUpWrapper.setIsChildInGroup(mIsChildInGroup);
   1153         }
   1154         if (mAmbientChild != null) {
   1155             mAmbientWrapper.setIsChildInGroup(mIsChildInGroup);
   1156         }
   1157         updateAllSingleLineViews();
   1158     }
   1159 
   1160     public void onNotificationUpdated(NotificationData.Entry entry) {
   1161         mStatusBarNotification = entry.notification;
   1162         mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
   1163         updateAllSingleLineViews();
   1164         if (mContractedChild != null) {
   1165             mContractedWrapper.onContentUpdated(entry.row);
   1166         }
   1167         if (mExpandedChild != null) {
   1168             mExpandedWrapper.onContentUpdated(entry.row);
   1169         }
   1170         if (mHeadsUpChild != null) {
   1171             mHeadsUpWrapper.onContentUpdated(entry.row);
   1172         }
   1173         if (mAmbientChild != null) {
   1174             mAmbientWrapper.onContentUpdated(entry.row);
   1175         }
   1176         applyRemoteInputAndSmartReply(entry);
   1177         updateLegacy();
   1178         mForceSelectNextLayout = true;
   1179         setDark(mDark, false /* animate */, 0 /* delay */);
   1180         mPreviousExpandedRemoteInputIntent = null;
   1181         mPreviousHeadsUpRemoteInputIntent = null;
   1182     }
   1183 
   1184     private void updateAllSingleLineViews() {
   1185         updateSingleLineView();
   1186         updateAmbientSingleLineView();
   1187     }
   1188     private void updateSingleLineView() {
   1189         if (mIsChildInGroup) {
   1190             boolean isNewView = mSingleLineView == null;
   1191             mSingleLineView = mHybridGroupManager.bindFromNotification(
   1192                     mSingleLineView, mStatusBarNotification.getNotification());
   1193             if (isNewView) {
   1194                 updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
   1195                         mSingleLineView, mSingleLineView);
   1196             }
   1197         } else if (mSingleLineView != null) {
   1198             removeView(mSingleLineView);
   1199             mSingleLineView = null;
   1200         }
   1201     }
   1202 
   1203     private void updateAmbientSingleLineView() {
   1204         if (mIsChildInGroup) {
   1205             boolean isNewView = mAmbientSingleLineChild == null;
   1206             mAmbientSingleLineChild = mHybridGroupManager.bindAmbientFromNotification(
   1207                     mAmbientSingleLineChild, mStatusBarNotification.getNotification());
   1208             if (isNewView) {
   1209                 updateViewVisibility(mVisibleType, VISIBLE_TYPE_AMBIENT_SINGLELINE,
   1210                         mAmbientSingleLineChild, mAmbientSingleLineChild);
   1211             }
   1212         } else if (mAmbientSingleLineChild != null) {
   1213             removeView(mAmbientSingleLineChild);
   1214             mAmbientSingleLineChild = null;
   1215         }
   1216     }
   1217 
   1218     private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) {
   1219         if (mRemoteInputController == null) {
   1220             return;
   1221         }
   1222 
   1223         boolean enableSmartReplies = (mSmartReplyConstants.isEnabled()
   1224                 && (!mSmartReplyConstants.requiresTargetingP()
   1225                     || entry.targetSdk >= Build.VERSION_CODES.P));
   1226 
   1227         boolean hasRemoteInput = false;
   1228         RemoteInput remoteInputWithChoices = null;
   1229         PendingIntent pendingIntentWithChoices = null;
   1230 
   1231         Notification.Action[] actions = entry.notification.getNotification().actions;
   1232         if (actions != null) {
   1233             for (Notification.Action a : actions) {
   1234                 if (a.getRemoteInputs() != null) {
   1235                     for (RemoteInput ri : a.getRemoteInputs()) {
   1236                         boolean showRemoteInputView = ri.getAllowFreeFormInput();
   1237                         boolean showSmartReplyView = enableSmartReplies && ri.getChoices() != null
   1238                                 && ri.getChoices().length > 0;
   1239                         if (showRemoteInputView) {
   1240                             hasRemoteInput = true;
   1241                         }
   1242                         if (showSmartReplyView) {
   1243                             remoteInputWithChoices = ri;
   1244                             pendingIntentWithChoices = a.actionIntent;
   1245                         }
   1246                         if (showRemoteInputView || showSmartReplyView) {
   1247                             break;
   1248                         }
   1249                     }
   1250                 }
   1251             }
   1252         }
   1253 
   1254         applyRemoteInput(entry, hasRemoteInput);
   1255         applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices, entry);
   1256     }
   1257 
   1258     private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) {
   1259         View bigContentView = mExpandedChild;
   1260         if (bigContentView != null) {
   1261             mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
   1262                     mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput,
   1263                     mExpandedWrapper);
   1264         } else {
   1265             mExpandedRemoteInput = null;
   1266         }
   1267         if (mCachedExpandedRemoteInput != null
   1268                 && mCachedExpandedRemoteInput != mExpandedRemoteInput) {
   1269             // We had a cached remote input but didn't reuse it. Clean up required.
   1270             mCachedExpandedRemoteInput.dispatchFinishTemporaryDetach();
   1271         }
   1272         mCachedExpandedRemoteInput = null;
   1273 
   1274         View headsUpContentView = mHeadsUpChild;
   1275         if (headsUpContentView != null) {
   1276             mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
   1277                     mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput, mHeadsUpWrapper);
   1278         } else {
   1279             mHeadsUpRemoteInput = null;
   1280         }
   1281         if (mCachedHeadsUpRemoteInput != null
   1282                 && mCachedHeadsUpRemoteInput != mHeadsUpRemoteInput) {
   1283             // We had a cached remote input but didn't reuse it. Clean up required.
   1284             mCachedHeadsUpRemoteInput.dispatchFinishTemporaryDetach();
   1285         }
   1286         mCachedHeadsUpRemoteInput = null;
   1287     }
   1288 
   1289     private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
   1290             boolean hasRemoteInput, PendingIntent existingPendingIntent,
   1291             RemoteInputView cachedView, NotificationViewWrapper wrapper) {
   1292         View actionContainerCandidate = view.findViewById(
   1293                 com.android.internal.R.id.actions_container);
   1294         if (actionContainerCandidate instanceof FrameLayout) {
   1295             RemoteInputView existing = (RemoteInputView)
   1296                     view.findViewWithTag(RemoteInputView.VIEW_TAG);
   1297 
   1298             if (existing != null) {
   1299                 existing.onNotificationUpdateOrReset();
   1300             }
   1301 
   1302             if (existing == null && hasRemoteInput) {
   1303                 ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
   1304                 if (cachedView == null) {
   1305                     RemoteInputView riv = RemoteInputView.inflate(
   1306                             mContext, actionContainer, entry, mRemoteInputController);
   1307 
   1308                     riv.setVisibility(View.INVISIBLE);
   1309                     actionContainer.addView(riv, new LayoutParams(
   1310                             ViewGroup.LayoutParams.MATCH_PARENT,
   1311                             ViewGroup.LayoutParams.MATCH_PARENT)
   1312                     );
   1313                     existing = riv;
   1314                 } else {
   1315                     actionContainer.addView(cachedView);
   1316                     cachedView.dispatchFinishTemporaryDetach();
   1317                     cachedView.requestFocus();
   1318                     existing = cachedView;
   1319                 }
   1320             }
   1321             if (hasRemoteInput) {
   1322                 int color = entry.notification.getNotification().color;
   1323                 if (color == Notification.COLOR_DEFAULT) {
   1324                     color = mContext.getColor(R.color.default_remote_input_background);
   1325                 }
   1326                 existing.setBackgroundColor(NotificationColorUtil.ensureTextBackgroundColor(color,
   1327                         mContext.getColor(R.color.remote_input_text_enabled),
   1328                         mContext.getColor(R.color.remote_input_hint)));
   1329 
   1330                 existing.setWrapper(wrapper);
   1331                 existing.setOnVisibilityChangedListener(this::setRemoteInputVisible);
   1332 
   1333                 if (existingPendingIntent != null || existing.isActive()) {
   1334                     // The current action could be gone, or the pending intent no longer valid.
   1335                     // If we find a matching action in the new notification, focus, otherwise close.
   1336                     Notification.Action[] actions = entry.notification.getNotification().actions;
   1337                     if (existingPendingIntent != null) {
   1338                         existing.setPendingIntent(existingPendingIntent);
   1339                     }
   1340                     if (existing.updatePendingIntentFromActions(actions)) {
   1341                         if (!existing.isActive()) {
   1342                             existing.focus();
   1343                         }
   1344                     } else {
   1345                         if (existing.isActive()) {
   1346                             existing.close();
   1347                         }
   1348                     }
   1349                 }
   1350             }
   1351             return existing;
   1352         }
   1353         return null;
   1354     }
   1355 
   1356     private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent,
   1357             NotificationData.Entry entry) {
   1358         if (mExpandedChild != null) {
   1359             mExpandedSmartReplyView =
   1360                     applySmartReplyView(mExpandedChild, remoteInput, pendingIntent, entry);
   1361             if (mExpandedSmartReplyView != null && remoteInput != null
   1362                     && remoteInput.getChoices() != null && remoteInput.getChoices().length > 0) {
   1363                 mSmartReplyController.smartRepliesAdded(entry, remoteInput.getChoices().length);
   1364             }
   1365         }
   1366     }
   1367 
   1368     private SmartReplyView applySmartReplyView(
   1369             View view, RemoteInput remoteInput, PendingIntent pendingIntent,
   1370             NotificationData.Entry entry) {
   1371         View smartReplyContainerCandidate = view.findViewById(
   1372                 com.android.internal.R.id.smart_reply_container);
   1373         if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
   1374             return null;
   1375         }
   1376         LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
   1377         if (remoteInput == null || pendingIntent == null) {
   1378             smartReplyContainer.setVisibility(View.GONE);
   1379             return null;
   1380         }
   1381         // If we are showing the spinner we don't want to add the buttons.
   1382         boolean showingSpinner = entry.notification.getNotification()
   1383                 .extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
   1384         if (showingSpinner) {
   1385             smartReplyContainer.setVisibility(View.GONE);
   1386             return null;
   1387         }
   1388         // If we are keeping the notification around while sending we don't want to add the buttons.
   1389         boolean hideSmartReplies = entry.notification.getNotification()
   1390                 .extras.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false);
   1391         if (hideSmartReplies) {
   1392             smartReplyContainer.setVisibility(View.GONE);
   1393             return null;
   1394         }
   1395         SmartReplyView smartReplyView = null;
   1396         if (smartReplyContainer.getChildCount() == 0) {
   1397             smartReplyView = SmartReplyView.inflate(mContext, smartReplyContainer);
   1398             smartReplyContainer.addView(smartReplyView);
   1399         } else if (smartReplyContainer.getChildCount() == 1) {
   1400             View child = smartReplyContainer.getChildAt(0);
   1401             if (child instanceof SmartReplyView) {
   1402                 smartReplyView = (SmartReplyView) child;
   1403             }
   1404         }
   1405         if (smartReplyView != null) {
   1406             smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent,
   1407                     mSmartReplyController, entry, smartReplyContainer);
   1408             smartReplyContainer.setVisibility(View.VISIBLE);
   1409         }
   1410         return smartReplyView;
   1411     }
   1412 
   1413     public void closeRemoteInput() {
   1414         if (mHeadsUpRemoteInput != null) {
   1415             mHeadsUpRemoteInput.close();
   1416         }
   1417         if (mExpandedRemoteInput != null) {
   1418             mExpandedRemoteInput.close();
   1419         }
   1420     }
   1421 
   1422     public void setGroupManager(NotificationGroupManager groupManager) {
   1423         mGroupManager = groupManager;
   1424     }
   1425 
   1426     public void setRemoteInputController(RemoteInputController r) {
   1427         mRemoteInputController = r;
   1428     }
   1429 
   1430     public void setExpandClickListener(OnClickListener expandClickListener) {
   1431         mExpandClickListener = expandClickListener;
   1432     }
   1433 
   1434     public void updateExpandButtons(boolean expandable) {
   1435         mExpandable = expandable;
   1436         // if the expanded child has the same height as the collapsed one we hide it.
   1437         if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
   1438             if ((!mIsHeadsUp && !mHeadsUpAnimatingAway)
   1439                     || mHeadsUpChild == null || mContainingNotification.isOnKeyguard()) {
   1440                 if (mExpandedChild.getHeight() <= mContractedChild.getHeight()) {
   1441                     expandable = false;
   1442                 }
   1443             } else if (mExpandedChild.getHeight() <= mHeadsUpChild.getHeight()) {
   1444                 expandable = false;
   1445             }
   1446         }
   1447         if (mExpandedChild != null) {
   1448             mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
   1449         }
   1450         if (mContractedChild != null) {
   1451             mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
   1452         }
   1453         if (mHeadsUpChild != null) {
   1454             mHeadsUpWrapper.updateExpandability(expandable,  mExpandClickListener);
   1455         }
   1456         mIsContentExpandable = expandable;
   1457     }
   1458 
   1459     public NotificationHeaderView getNotificationHeader() {
   1460         NotificationHeaderView header = null;
   1461         if (mContractedChild != null) {
   1462             header = mContractedWrapper.getNotificationHeader();
   1463         }
   1464         if (header == null && mExpandedChild != null) {
   1465             header = mExpandedWrapper.getNotificationHeader();
   1466         }
   1467         if (header == null && mHeadsUpChild != null) {
   1468             header = mHeadsUpWrapper.getNotificationHeader();
   1469         }
   1470         if (header == null && mAmbientChild != null) {
   1471             header = mAmbientWrapper.getNotificationHeader();
   1472         }
   1473         return header;
   1474     }
   1475 
   1476     public void showAppOpsIcons(ArraySet<Integer> activeOps) {
   1477         if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
   1478             mContractedWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
   1479         }
   1480         if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
   1481             mExpandedWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
   1482         }
   1483         if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
   1484             mHeadsUpWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
   1485         }
   1486     }
   1487 
   1488     public NotificationHeaderView getContractedNotificationHeader() {
   1489         if (mContractedChild != null) {
   1490             return mContractedWrapper.getNotificationHeader();
   1491         }
   1492         return null;
   1493     }
   1494 
   1495     public NotificationHeaderView getVisibleNotificationHeader() {
   1496         NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
   1497         return wrapper == null ? null : wrapper.getNotificationHeader();
   1498     }
   1499 
   1500     public void setContainingNotification(ExpandableNotificationRow containingNotification) {
   1501         mContainingNotification = containingNotification;
   1502     }
   1503 
   1504     public void requestSelectLayout(boolean needsAnimation) {
   1505         selectLayout(needsAnimation, false);
   1506     }
   1507 
   1508     public void reInflateViews() {
   1509         if (mIsChildInGroup && mSingleLineView != null) {
   1510             removeView(mSingleLineView);
   1511             mSingleLineView = null;
   1512             updateAllSingleLineViews();
   1513         }
   1514     }
   1515 
   1516     public void setUserExpanding(boolean userExpanding) {
   1517         mUserExpanding = userExpanding;
   1518         if (userExpanding) {
   1519             mTransformationStartVisibleType = mVisibleType;
   1520         } else {
   1521             mTransformationStartVisibleType = UNDEFINED;
   1522             mVisibleType = calculateVisibleType();
   1523             updateViewVisibilities(mVisibleType);
   1524             updateBackgroundColor(false);
   1525         }
   1526     }
   1527 
   1528     /**
   1529      * Set by how much the single line view should be indented. Used when a overflow indicator is
   1530      * present and only during measuring
   1531      */
   1532     public void setSingleLineWidthIndention(int singleLineWidthIndention) {
   1533         if (singleLineWidthIndention != mSingleLineWidthIndention) {
   1534             mSingleLineWidthIndention = singleLineWidthIndention;
   1535             mContainingNotification.forceLayout();
   1536             forceLayout();
   1537         }
   1538     }
   1539 
   1540     public HybridNotificationView getSingleLineView() {
   1541         return mSingleLineView;
   1542     }
   1543 
   1544     public void setRemoved() {
   1545         if (mExpandedRemoteInput != null) {
   1546             mExpandedRemoteInput.setRemoved();
   1547         }
   1548         if (mHeadsUpRemoteInput != null) {
   1549             mHeadsUpRemoteInput.setRemoved();
   1550         }
   1551     }
   1552 
   1553     public void setContentHeightAnimating(boolean animating) {
   1554         if (!animating) {
   1555             mContentHeightAtAnimationStart = UNDEFINED;
   1556         }
   1557     }
   1558 
   1559     @VisibleForTesting
   1560     boolean isAnimatingVisibleType() {
   1561         return mAnimationStartVisibleType != UNDEFINED;
   1562     }
   1563 
   1564     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
   1565         mHeadsUpAnimatingAway = headsUpAnimatingAway;
   1566         selectLayout(false /* animate */, true /* force */);
   1567     }
   1568 
   1569     public void setFocusOnVisibilityChange() {
   1570         mFocusOnVisibilityChange = true;
   1571     }
   1572 
   1573     public void setIconsVisible(boolean iconsVisible) {
   1574         mIconsVisible = iconsVisible;
   1575         updateIconVisibilities();
   1576     }
   1577 
   1578     private void updateIconVisibilities() {
   1579         if (mContractedWrapper != null) {
   1580             NotificationHeaderView header = mContractedWrapper.getNotificationHeader();
   1581             if (header != null) {
   1582                 header.getIcon().setForceHidden(!mIconsVisible);
   1583             }
   1584         }
   1585         if (mHeadsUpWrapper != null) {
   1586             NotificationHeaderView header = mHeadsUpWrapper.getNotificationHeader();
   1587             if (header != null) {
   1588                 header.getIcon().setForceHidden(!mIconsVisible);
   1589             }
   1590         }
   1591         if (mExpandedWrapper != null) {
   1592             NotificationHeaderView header = mExpandedWrapper.getNotificationHeader();
   1593             if (header != null) {
   1594                 header.getIcon().setForceHidden(!mIconsVisible);
   1595             }
   1596         }
   1597     }
   1598 
   1599     @Override
   1600     public void onVisibilityAggregated(boolean isVisible) {
   1601         super.onVisibilityAggregated(isVisible);
   1602         if (isVisible) {
   1603             fireExpandedVisibleListenerIfVisible();
   1604         }
   1605     }
   1606 
   1607     /**
   1608      * Sets a one-shot listener for when the expanded view becomes visible.
   1609      *
   1610      * This will fire the listener immediately if the expanded view is already visible.
   1611      */
   1612     public void setOnExpandedVisibleListener(Runnable r) {
   1613         mExpandedVisibleListener = r;
   1614         fireExpandedVisibleListenerIfVisible();
   1615     }
   1616 
   1617     public void setIsLowPriority(boolean isLowPriority) {
   1618         mIsLowPriority = isLowPriority;
   1619     }
   1620 
   1621     public boolean isDimmable() {
   1622         if (!mContractedWrapper.isDimmable()) {
   1623             return false;
   1624         }
   1625         return true;
   1626     }
   1627 
   1628     /**
   1629      * Should a single click be disallowed on this view when on the keyguard?
   1630      */
   1631     public boolean disallowSingleClick(float x, float y) {
   1632         NotificationViewWrapper visibleWrapper = getVisibleWrapper(getVisibleType());
   1633         if (visibleWrapper != null) {
   1634             return visibleWrapper.disallowSingleClick(x, y);
   1635         }
   1636         return false;
   1637     }
   1638 
   1639     public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
   1640         boolean needsPaddings = shouldClipToRounding(getVisibleType(), topRounded, bottomRounded);
   1641         if (mUserExpanding) {
   1642              needsPaddings |= shouldClipToRounding(mTransformationStartVisibleType, topRounded,
   1643                      bottomRounded);
   1644         }
   1645         return needsPaddings;
   1646     }
   1647 
   1648     private boolean shouldClipToRounding(int visibleType, boolean topRounded,
   1649             boolean bottomRounded) {
   1650         NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
   1651         if (visibleWrapper == null) {
   1652             return false;
   1653         }
   1654         return visibleWrapper.shouldClipToRounding(topRounded, bottomRounded);
   1655     }
   1656 
   1657     public CharSequence getActiveRemoteInputText() {
   1658         if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
   1659             return mExpandedRemoteInput.getText();
   1660         }
   1661         if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
   1662             return mHeadsUpRemoteInput.getText();
   1663         }
   1664         return null;
   1665     }
   1666 
   1667     @Override
   1668     public boolean dispatchTouchEvent(MotionEvent ev) {
   1669         float y = ev.getY();
   1670         // We still want to distribute touch events to the remote input even if it's outside the
   1671         // view boundary. We're therefore manually dispatching these events to the remote view
   1672         RemoteInputView riv = getRemoteInputForView(getViewForVisibleType(mVisibleType));
   1673         if (riv != null && riv.getVisibility() == VISIBLE) {
   1674             int inputStart = mUnrestrictedContentHeight - riv.getHeight();
   1675             if (y <= mUnrestrictedContentHeight && y >= inputStart) {
   1676                 ev.offsetLocation(0, -inputStart);
   1677                 return riv.dispatchTouchEvent(ev);
   1678             }
   1679         }
   1680         return super.dispatchTouchEvent(ev);
   1681     }
   1682 
   1683     /**
   1684      * Overridden to make sure touches to the reply action bar actually go through to this view
   1685      */
   1686     @Override
   1687     public boolean pointInView(float localX, float localY, float slop) {
   1688         float top = mClipTopAmount;
   1689         float bottom = mUnrestrictedContentHeight;
   1690         return localX >= -slop && localY >= top - slop && localX < ((mRight - mLeft) + slop) &&
   1691                 localY < (bottom + slop);
   1692     }
   1693 
   1694     private RemoteInputView getRemoteInputForView(View child) {
   1695         if (child == mExpandedChild) {
   1696             return mExpandedRemoteInput;
   1697         } else if (child == mHeadsUpChild) {
   1698             return mHeadsUpRemoteInput;
   1699         }
   1700         return null;
   1701     }
   1702 
   1703     public int getExpandHeight() {
   1704         int viewType = VISIBLE_TYPE_EXPANDED;
   1705         if (mExpandedChild == null) {
   1706             viewType = VISIBLE_TYPE_CONTRACTED;
   1707         }
   1708         return getViewHeight(viewType) + getExtraRemoteInputHeight(mExpandedRemoteInput);
   1709     }
   1710 
   1711     public int getHeadsUpHeight() {
   1712         int viewType = VISIBLE_TYPE_HEADSUP;
   1713         if (mHeadsUpChild == null) {
   1714             viewType = VISIBLE_TYPE_CONTRACTED;
   1715         }
   1716         // The headsUp remote input quickly switches to the expanded one, so lets also include that
   1717         // one
   1718         return getViewHeight(viewType) + getExtraRemoteInputHeight(mHeadsUpRemoteInput)
   1719                 + getExtraRemoteInputHeight(mExpandedRemoteInput);
   1720     }
   1721 
   1722     public void setRemoteInputVisible(boolean remoteInputVisible) {
   1723         mRemoteInputVisible = remoteInputVisible;
   1724         setClipChildren(!remoteInputVisible);
   1725     }
   1726 
   1727     @Override
   1728     public void setClipChildren(boolean clipChildren) {
   1729         clipChildren = clipChildren && !mRemoteInputVisible;
   1730         super.setClipChildren(clipChildren);
   1731     }
   1732 
   1733     public void setHeaderVisibleAmount(float headerVisibleAmount) {
   1734         if (mContractedWrapper != null) {
   1735             mContractedWrapper.setHeaderVisibleAmount(headerVisibleAmount);
   1736         }
   1737         if (mHeadsUpWrapper != null) {
   1738             mHeadsUpWrapper.setHeaderVisibleAmount(headerVisibleAmount);
   1739         }
   1740         if (mExpandedWrapper != null) {
   1741             mExpandedWrapper.setHeaderVisibleAmount(headerVisibleAmount);
   1742         }
   1743     }
   1744 }
   1745