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