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