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