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