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