• 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