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