1 /* 2 * Copyright (C) 2013 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.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.content.Context; 23 import android.graphics.drawable.AnimatedVectorDrawable; 24 import android.graphics.drawable.AnimationDrawable; 25 import android.graphics.drawable.ColorDrawable; 26 import android.graphics.drawable.Drawable; 27 import android.service.notification.StatusBarNotification; 28 import android.util.AttributeSet; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewStub; 32 import android.view.accessibility.AccessibilityEvent; 33 import android.view.animation.LinearInterpolator; 34 import android.widget.ImageView; 35 36 import com.android.systemui.R; 37 import com.android.systemui.statusbar.phone.NotificationGroupManager; 38 import com.android.systemui.statusbar.phone.PhoneStatusBar; 39 import com.android.systemui.statusbar.stack.NotificationChildrenContainer; 40 import com.android.systemui.statusbar.stack.StackScrollState; 41 import com.android.systemui.statusbar.stack.StackStateAnimator; 42 import com.android.systemui.statusbar.stack.StackViewState; 43 44 import java.util.List; 45 46 public class ExpandableNotificationRow extends ActivatableNotificationView { 47 48 private static final int DEFAULT_DIVIDER_ALPHA = 0x29; 49 private static final int COLORED_DIVIDER_ALPHA = 0x7B; 50 private final LinearInterpolator mLinearInterpolator = new LinearInterpolator(); 51 private int mRowMinHeight; 52 53 /** Does this row contain layouts that can adapt to row expansion */ 54 private boolean mExpandable; 55 /** Has the user actively changed the expansion state of this row */ 56 private boolean mHasUserChangedExpansion; 57 /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ 58 private boolean mUserExpanded; 59 /** Is the user touching this row */ 60 private boolean mUserLocked; 61 /** Are we showing the "public" version */ 62 private boolean mShowingPublic; 63 private boolean mSensitive; 64 private boolean mShowingPublicInitialized; 65 private boolean mHideSensitiveForIntrinsicHeight; 66 67 /** 68 * Is this notification expanded by the system. The expansion state can be overridden by the 69 * user expansion. 70 */ 71 private boolean mIsSystemExpanded; 72 73 /** 74 * Whether the notification expansion is disabled. This is the case on Keyguard. 75 */ 76 private boolean mExpansionDisabled; 77 78 private NotificationContentView mPublicLayout; 79 private NotificationContentView mPrivateLayout; 80 private int mMaxExpandHeight; 81 private int mHeadsUpHeight; 82 private View mVetoButton; 83 private boolean mClearable; 84 private ExpansionLogger mLogger; 85 private String mLoggingKey; 86 private boolean mWasReset; 87 private NotificationGuts mGuts; 88 private StatusBarNotification mStatusBarNotification; 89 private boolean mIsHeadsUp; 90 private View mExpandButton; 91 private View mExpandButtonDivider; 92 private ViewStub mExpandButtonStub; 93 private ViewStub mChildrenContainerStub; 94 private NotificationGroupManager mGroupManager; 95 private View mExpandButtonContainer; 96 private boolean mChildrenExpanded; 97 private NotificationChildrenContainer mChildrenContainer; 98 private ValueAnimator mChildExpandAnimator; 99 private float mChildrenExpandProgress; 100 private float mExpandButtonStart; 101 private ViewStub mGutsStub; 102 private boolean mHasExpandAction; 103 private boolean mIsSystemChildExpanded; 104 private boolean mIsPinned; 105 private OnClickListener mExpandClickListener = new OnClickListener() { 106 @Override 107 public void onClick(View v) { 108 mGroupManager.setGroupExpanded(mStatusBarNotification, 109 !mChildrenExpanded); 110 } 111 }; 112 getPrivateLayout()113 public NotificationContentView getPrivateLayout() { 114 return mPrivateLayout; 115 } 116 getPublicLayout()117 public NotificationContentView getPublicLayout() { 118 return mPublicLayout; 119 } 120 setIconAnimationRunning(boolean running)121 public void setIconAnimationRunning(boolean running) { 122 setIconAnimationRunning(running, mPublicLayout); 123 setIconAnimationRunning(running, mPrivateLayout); 124 } 125 setIconAnimationRunning(boolean running, NotificationContentView layout)126 private void setIconAnimationRunning(boolean running, NotificationContentView layout) { 127 if (layout != null) { 128 View contractedChild = layout.getContractedChild(); 129 View expandedChild = layout.getExpandedChild(); 130 View headsUpChild = layout.getHeadsUpChild(); 131 setIconAnimationRunningForChild(running, contractedChild); 132 setIconAnimationRunningForChild(running, expandedChild); 133 setIconAnimationRunningForChild(running, headsUpChild); 134 } 135 } 136 setIconAnimationRunningForChild(boolean running, View child)137 private void setIconAnimationRunningForChild(boolean running, View child) { 138 if (child != null) { 139 ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon); 140 setIconRunning(icon, running); 141 ImageView rightIcon = (ImageView) child.findViewById( 142 com.android.internal.R.id.right_icon); 143 setIconRunning(rightIcon, running); 144 } 145 } 146 setIconRunning(ImageView imageView, boolean running)147 private void setIconRunning(ImageView imageView, boolean running) { 148 if (imageView != null) { 149 Drawable drawable = imageView.getDrawable(); 150 if (drawable instanceof AnimationDrawable) { 151 AnimationDrawable animationDrawable = (AnimationDrawable) drawable; 152 if (running) { 153 animationDrawable.start(); 154 } else { 155 animationDrawable.stop(); 156 } 157 } else if (drawable instanceof AnimatedVectorDrawable) { 158 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable; 159 if (running) { 160 animationDrawable.start(); 161 } else { 162 animationDrawable.stop(); 163 } 164 } 165 } 166 } 167 setStatusBarNotification(StatusBarNotification statusBarNotification)168 public void setStatusBarNotification(StatusBarNotification statusBarNotification) { 169 mStatusBarNotification = statusBarNotification; 170 updateVetoButton(); 171 updateExpandButton(); 172 } 173 getStatusBarNotification()174 public StatusBarNotification getStatusBarNotification() { 175 return mStatusBarNotification; 176 } 177 isHeadsUp()178 public boolean isHeadsUp() { 179 return mIsHeadsUp; 180 } 181 setHeadsUp(boolean isHeadsUp)182 public void setHeadsUp(boolean isHeadsUp) { 183 int intrinsicBefore = getIntrinsicHeight(); 184 mIsHeadsUp = isHeadsUp; 185 mPrivateLayout.setHeadsUp(isHeadsUp); 186 if (intrinsicBefore != getIntrinsicHeight()) { 187 notifyHeightChanged(false /* needsAnimation */); 188 } 189 } 190 setGroupManager(NotificationGroupManager groupManager)191 public void setGroupManager(NotificationGroupManager groupManager) { 192 mGroupManager = groupManager; 193 } 194 addChildNotification(ExpandableNotificationRow row)195 public void addChildNotification(ExpandableNotificationRow row) { 196 addChildNotification(row, -1); 197 } 198 199 /** 200 * Add a child notification to this view. 201 * 202 * @param row the row to add 203 * @param childIndex the index to add it at, if -1 it will be added at the end 204 */ addChildNotification(ExpandableNotificationRow row, int childIndex)205 public void addChildNotification(ExpandableNotificationRow row, int childIndex) { 206 if (mChildrenContainer == null) { 207 mChildrenContainerStub.inflate(); 208 } 209 mChildrenContainer.addNotification(row, childIndex); 210 } 211 removeChildNotification(ExpandableNotificationRow row)212 public void removeChildNotification(ExpandableNotificationRow row) { 213 if (mChildrenContainer != null) { 214 mChildrenContainer.removeNotification(row); 215 } 216 } 217 218 @Override areChildrenExpanded()219 public boolean areChildrenExpanded() { 220 return mChildrenExpanded; 221 } 222 getNotificationChildren()223 public List<ExpandableNotificationRow> getNotificationChildren() { 224 return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); 225 } 226 227 /** 228 * Apply the order given in the list to the children. 229 * 230 * @param childOrder the new list order 231 * @return whether the list order has changed 232 */ applyChildOrder(List<ExpandableNotificationRow> childOrder)233 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { 234 return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder); 235 } 236 getChildrenStates(StackScrollState resultState)237 public void getChildrenStates(StackScrollState resultState) { 238 if (mChildrenExpanded) { 239 StackViewState parentState = resultState.getViewStateForView(this); 240 mChildrenContainer.getState(resultState, parentState); 241 } 242 } 243 applyChildrenState(StackScrollState state)244 public void applyChildrenState(StackScrollState state) { 245 if (mChildrenExpanded) { 246 mChildrenContainer.applyState(state); 247 } 248 } 249 prepareExpansionChanged(StackScrollState state)250 public void prepareExpansionChanged(StackScrollState state) { 251 if (mChildrenExpanded) { 252 mChildrenContainer.prepareExpansionChanged(state); 253 } 254 } 255 startChildAnimation(StackScrollState finalState, StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration)256 public void startChildAnimation(StackScrollState finalState, 257 StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) { 258 if (mChildrenExpanded) { 259 mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay, 260 duration); 261 } 262 } 263 getViewAtPosition(float y)264 public ExpandableNotificationRow getViewAtPosition(float y) { 265 if (!mChildrenExpanded) { 266 return this; 267 } else { 268 ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); 269 return view == null ? this : view; 270 } 271 } 272 getGuts()273 public NotificationGuts getGuts() { 274 return mGuts; 275 } 276 calculateContentHeightFromActualHeight(int actualHeight)277 protected int calculateContentHeightFromActualHeight(int actualHeight) { 278 int realActualHeight = actualHeight; 279 if (hasBottomDecor()) { 280 realActualHeight -= getBottomDecorHeight(); 281 } 282 realActualHeight = Math.max(getMinHeight(), realActualHeight); 283 return realActualHeight; 284 } 285 286 /** 287 * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this 288 * the notification will be rendered on top of the screen. 289 * 290 * @param pinned whether it is pinned 291 */ setPinned(boolean pinned)292 public void setPinned(boolean pinned) { 293 mIsPinned = pinned; 294 } 295 isPinned()296 public boolean isPinned() { 297 return mIsPinned; 298 } 299 getHeadsUpHeight()300 public int getHeadsUpHeight() { 301 return mHeadsUpHeight; 302 } 303 304 public interface ExpansionLogger { logNotificationExpansion(String key, boolean userAction, boolean expanded)305 public void logNotificationExpansion(String key, boolean userAction, boolean expanded); 306 } 307 ExpandableNotificationRow(Context context, AttributeSet attrs)308 public ExpandableNotificationRow(Context context, AttributeSet attrs) { 309 super(context, attrs); 310 } 311 312 /** 313 * Resets this view so it can be re-used for an updated notification. 314 */ 315 @Override reset()316 public void reset() { 317 super.reset(); 318 mRowMinHeight = 0; 319 final boolean wasExpanded = isExpanded(); 320 mMaxViewHeight = 0; 321 mExpandable = false; 322 mHasUserChangedExpansion = false; 323 mUserLocked = false; 324 mShowingPublic = false; 325 mSensitive = false; 326 mShowingPublicInitialized = false; 327 mIsSystemExpanded = false; 328 mExpansionDisabled = false; 329 mPublicLayout.reset(mIsHeadsUp); 330 mPrivateLayout.reset(mIsHeadsUp); 331 resetHeight(); 332 logExpansionEvent(false, wasExpanded); 333 } 334 resetHeight()335 public void resetHeight() { 336 if (mIsHeadsUp) { 337 resetActualHeight(); 338 } 339 mMaxExpandHeight = 0; 340 mHeadsUpHeight = 0; 341 mWasReset = true; 342 onHeightReset(); 343 requestLayout(); 344 } 345 346 @Override filterMotionEvent(MotionEvent event)347 protected boolean filterMotionEvent(MotionEvent event) { 348 return mIsHeadsUp || super.filterMotionEvent(event); 349 } 350 351 @Override onFinishInflate()352 protected void onFinishInflate() { 353 super.onFinishInflate(); 354 mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); 355 mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); 356 mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); 357 mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { 358 @Override 359 public void onInflate(ViewStub stub, View inflated) { 360 mGuts = (NotificationGuts) inflated; 361 mGuts.setClipTopAmount(getClipTopAmount()); 362 mGuts.setActualHeight(getActualHeight()); 363 mGutsStub = null; 364 } 365 }); 366 mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub); 367 mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() { 368 369 @Override 370 public void onInflate(ViewStub stub, View inflated) { 371 mExpandButtonContainer = inflated; 372 mExpandButton = inflated.findViewById(R.id.notification_expand_button); 373 mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider); 374 mExpandButtonContainer.setOnClickListener(mExpandClickListener); 375 } 376 }); 377 mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub); 378 mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() { 379 380 @Override 381 public void onInflate(ViewStub stub, View inflated) { 382 mChildrenContainer = (NotificationChildrenContainer) inflated; 383 mChildrenContainer.setCollapseClickListener(mExpandClickListener); 384 updateChildrenVisibility(false); 385 } 386 }); 387 mVetoButton = findViewById(R.id.veto); 388 } 389 inflateGuts()390 public void inflateGuts() { 391 if (mGuts == null) { 392 mGutsStub.inflate(); 393 } 394 } 395 updateChildrenVisibility(boolean animated)396 private void updateChildrenVisibility(boolean animated) { 397 if (mChildrenContainer == null) { 398 return; 399 } 400 if (mChildExpandAnimator != null) { 401 mChildExpandAnimator.cancel(); 402 } 403 float targetProgress = mChildrenExpanded ? 1.0f : 0.0f; 404 if (animated) { 405 if (mChildrenExpanded) { 406 mChildrenContainer.setVisibility(VISIBLE); 407 } 408 mExpandButtonStart = mExpandButtonContainer.getTranslationY(); 409 mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress); 410 mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 411 @Override 412 public void onAnimationUpdate(ValueAnimator animation) { 413 setChildrenExpandProgress((float) animation.getAnimatedValue()); 414 } 415 }); 416 mChildExpandAnimator.addListener(new AnimatorListenerAdapter() { 417 @Override 418 public void onAnimationEnd(Animator animation) { 419 mChildExpandAnimator = null; 420 if (!mChildrenExpanded) { 421 mChildrenContainer.setVisibility(INVISIBLE); 422 } 423 } 424 }); 425 mChildExpandAnimator.setInterpolator(mLinearInterpolator); 426 mChildExpandAnimator.setDuration( 427 StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED); 428 mChildExpandAnimator.start(); 429 } else { 430 setChildrenExpandProgress(targetProgress); 431 mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE); 432 } 433 } 434 setChildrenExpandProgress(float progress)435 private void setChildrenExpandProgress(float progress) { 436 mChildrenExpandProgress = progress; 437 updateExpandButtonAppearance(); 438 NotificationContentView showingLayout = getShowingLayout(); 439 float alpha = 1.0f - mChildrenExpandProgress; 440 alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha); 441 showingLayout.setAlpha(alpha); 442 } 443 444 @Override onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event)445 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 446 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 447 // Add a record for the entire layout since its content is somehow small. 448 // The event comes from a leaf view that is interacted with. 449 AccessibilityEvent record = AccessibilityEvent.obtain(); 450 onInitializeAccessibilityEvent(record); 451 dispatchPopulateAccessibilityEvent(record); 452 event.appendRecord(record); 453 return true; 454 } 455 return false; 456 } 457 458 @Override setDark(boolean dark, boolean fade, long delay)459 public void setDark(boolean dark, boolean fade, long delay) { 460 super.setDark(dark, fade, delay); 461 final NotificationContentView showing = getShowingLayout(); 462 if (showing != null) { 463 showing.setDark(dark, fade, delay); 464 } 465 } 466 setHeightRange(int rowMinHeight, int rowMaxHeight)467 public void setHeightRange(int rowMinHeight, int rowMaxHeight) { 468 mRowMinHeight = rowMinHeight; 469 mMaxViewHeight = rowMaxHeight; 470 } 471 isExpandable()472 public boolean isExpandable() { 473 return mExpandable; 474 } 475 setExpandable(boolean expandable)476 public void setExpandable(boolean expandable) { 477 mExpandable = expandable; 478 } 479 480 /** 481 * @return whether the user has changed the expansion state 482 */ hasUserChangedExpansion()483 public boolean hasUserChangedExpansion() { 484 return mHasUserChangedExpansion; 485 } 486 isUserExpanded()487 public boolean isUserExpanded() { 488 return mUserExpanded; 489 } 490 491 /** 492 * Set this notification to be expanded by the user 493 * 494 * @param userExpanded whether the user wants this notification to be expanded 495 */ setUserExpanded(boolean userExpanded)496 public void setUserExpanded(boolean userExpanded) { 497 if (userExpanded && !mExpandable) return; 498 final boolean wasExpanded = isExpanded(); 499 mHasUserChangedExpansion = true; 500 mUserExpanded = userExpanded; 501 logExpansionEvent(true, wasExpanded); 502 } 503 resetUserExpansion()504 public void resetUserExpansion() { 505 mHasUserChangedExpansion = false; 506 mUserExpanded = false; 507 } 508 isUserLocked()509 public boolean isUserLocked() { 510 return mUserLocked; 511 } 512 setUserLocked(boolean userLocked)513 public void setUserLocked(boolean userLocked) { 514 mUserLocked = userLocked; 515 } 516 517 /** 518 * @return has the system set this notification to be expanded 519 */ isSystemExpanded()520 public boolean isSystemExpanded() { 521 return mIsSystemExpanded; 522 } 523 524 /** 525 * Set this notification to be expanded by the system. 526 * 527 * @param expand whether the system wants this notification to be expanded. 528 */ setSystemExpanded(boolean expand)529 public void setSystemExpanded(boolean expand) { 530 if (expand != mIsSystemExpanded) { 531 final boolean wasExpanded = isExpanded(); 532 mIsSystemExpanded = expand; 533 notifyHeightChanged(false /* needsAnimation */); 534 logExpansionEvent(false, wasExpanded); 535 } 536 } 537 538 /** 539 * @param expansionDisabled whether to prevent notification expansion 540 */ setExpansionDisabled(boolean expansionDisabled)541 public void setExpansionDisabled(boolean expansionDisabled) { 542 if (expansionDisabled != mExpansionDisabled) { 543 final boolean wasExpanded = isExpanded(); 544 mExpansionDisabled = expansionDisabled; 545 logExpansionEvent(false, wasExpanded); 546 if (wasExpanded != isExpanded()) { 547 notifyHeightChanged(false /* needsAnimation */); 548 } 549 } 550 } 551 552 /** 553 * @return Can the underlying notification be cleared? 554 */ isClearable()555 public boolean isClearable() { 556 return mStatusBarNotification != null && mStatusBarNotification.isClearable(); 557 } 558 559 /** 560 * Apply an expansion state to the layout. 561 */ applyExpansionToLayout()562 public void applyExpansionToLayout() { 563 boolean expand = isExpanded(); 564 if (expand && mExpandable) { 565 setContentHeight(mMaxExpandHeight); 566 } else { 567 setContentHeight(mRowMinHeight); 568 } 569 } 570 571 @Override getIntrinsicHeight()572 public int getIntrinsicHeight() { 573 if (isUserLocked()) { 574 return getActualHeight(); 575 } 576 boolean inExpansionState = isExpanded(); 577 int maxContentHeight; 578 if (mSensitive && mHideSensitiveForIntrinsicHeight) { 579 return mRowMinHeight; 580 } else if (mIsHeadsUp) { 581 if (inExpansionState) { 582 maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight); 583 } else { 584 maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight); 585 } 586 } else if ((!inExpansionState && !mChildrenExpanded)) { 587 maxContentHeight = mRowMinHeight; 588 } else if (mChildrenExpanded) { 589 maxContentHeight = mChildrenContainer.getIntrinsicHeight(); 590 } else { 591 maxContentHeight = getMaxExpandHeight(); 592 } 593 return maxContentHeight + getBottomDecorHeight(); 594 } 595 596 @Override hasBottomDecor()597 protected boolean hasBottomDecor() { 598 return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS 599 && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification); 600 } 601 602 @Override canHaveBottomDecor()603 protected boolean canHaveBottomDecor() { 604 return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp; 605 } 606 607 /** 608 * Check whether the view state is currently expanded. This is given by the system in {@link 609 * #setSystemExpanded(boolean)} and can be overridden by user expansion or 610 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this 611 * view can differ from this state, if layout params are modified from outside. 612 * 613 * @return whether the view state is currently expanded. 614 */ isExpanded()615 private boolean isExpanded() { 616 return !mExpansionDisabled 617 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) 618 || isUserExpanded()); 619 } 620 isSystemChildExpanded()621 private boolean isSystemChildExpanded() { 622 return mIsSystemChildExpanded; 623 } 624 setSystemChildExpanded(boolean expanded)625 public void setSystemChildExpanded(boolean expanded) { 626 mIsSystemChildExpanded = expanded; 627 } 628 629 @Override onLayout(boolean changed, int left, int top, int right, int bottom)630 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 631 super.onLayout(changed, left, top, right, bottom); 632 boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset; 633 updateMaxHeights(); 634 if (updateExpandHeight) { 635 applyExpansionToLayout(); 636 } 637 mWasReset = false; 638 } 639 640 @Override isChildInvisible(View child)641 protected boolean isChildInvisible(View child) { 642 643 // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the 644 // view will get too high and the shadows will be off. 645 boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp; 646 return super.isChildInvisible(child) || isInvisibleChildContainer; 647 } 648 updateMaxHeights()649 private void updateMaxHeights() { 650 int intrinsicBefore = getIntrinsicHeight(); 651 View expandedChild = mPrivateLayout.getExpandedChild(); 652 if (expandedChild == null) { 653 expandedChild = mPrivateLayout.getContractedChild(); 654 } 655 mMaxExpandHeight = expandedChild.getHeight(); 656 View headsUpChild = mPrivateLayout.getHeadsUpChild(); 657 if (headsUpChild == null) { 658 headsUpChild = mPrivateLayout.getContractedChild(); 659 } 660 mHeadsUpHeight = headsUpChild.getHeight(); 661 if (intrinsicBefore != getIntrinsicHeight()) { 662 notifyHeightChanged(false /* needsAnimation */); 663 } 664 } 665 setSensitive(boolean sensitive)666 public void setSensitive(boolean sensitive) { 667 mSensitive = sensitive; 668 } 669 setHideSensitiveForIntrinsicHeight(boolean hideSensitive)670 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { 671 mHideSensitiveForIntrinsicHeight = hideSensitive; 672 } 673 setHideSensitive(boolean hideSensitive, boolean animated, long delay, long duration)674 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, 675 long duration) { 676 boolean oldShowingPublic = mShowingPublic; 677 mShowingPublic = mSensitive && hideSensitive; 678 if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { 679 return; 680 } 681 682 // bail out if no public version 683 if (mPublicLayout.getChildCount() == 0) return; 684 685 if (!animated) { 686 mPublicLayout.animate().cancel(); 687 mPrivateLayout.animate().cancel(); 688 mPublicLayout.setAlpha(1f); 689 mPrivateLayout.setAlpha(1f); 690 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); 691 mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE); 692 } else { 693 animateShowingPublic(delay, duration); 694 } 695 696 updateVetoButton(); 697 mShowingPublicInitialized = true; 698 } 699 animateShowingPublic(long delay, long duration)700 private void animateShowingPublic(long delay, long duration) { 701 final View source = mShowingPublic ? mPrivateLayout : mPublicLayout; 702 View target = mShowingPublic ? mPublicLayout : mPrivateLayout; 703 source.setVisibility(View.VISIBLE); 704 target.setVisibility(View.VISIBLE); 705 target.setAlpha(0f); 706 source.animate().cancel(); 707 target.animate().cancel(); 708 source.animate() 709 .alpha(0f) 710 .setStartDelay(delay) 711 .setDuration(duration) 712 .withEndAction(new Runnable() { 713 @Override 714 public void run() { 715 source.setVisibility(View.INVISIBLE); 716 } 717 }); 718 target.animate() 719 .alpha(1f) 720 .setStartDelay(delay) 721 .setDuration(duration); 722 } 723 updateVetoButton()724 private void updateVetoButton() { 725 // public versions cannot be dismissed 726 mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE); 727 } 728 setChildrenExpanded(boolean expanded, boolean animate)729 public void setChildrenExpanded(boolean expanded, boolean animate) { 730 mChildrenExpanded = expanded; 731 updateChildrenVisibility(animate); 732 } 733 updateExpandButton()734 public void updateExpandButton() { 735 boolean hasExpand = hasBottomDecor(); 736 if (hasExpand != mHasExpandAction) { 737 if (hasExpand) { 738 if (mExpandButtonContainer == null) { 739 mExpandButtonStub.inflate(); 740 } 741 mExpandButtonContainer.setVisibility(View.VISIBLE); 742 updateExpandButtonAppearance(); 743 updateExpandButtonColor(); 744 } else if (mExpandButtonContainer != null) { 745 mExpandButtonContainer.setVisibility(View.GONE); 746 } 747 notifyHeightChanged(true /* needsAnimation */); 748 } 749 mHasExpandAction = hasExpand; 750 } 751 updateExpandButtonAppearance()752 private void updateExpandButtonAppearance() { 753 if (mExpandButtonContainer == null) { 754 return; 755 } 756 float expandButtonAlpha = 0.0f; 757 float expandButtonTranslation = 0.0f; 758 float containerTranslation = 0.0f; 759 int minHeight = getMinHeight(); 760 if (!mChildrenExpanded || mChildExpandAnimator != null) { 761 int expandActionHeight = getBottomDecorHeight(); 762 int translationY = getActualHeight() - expandActionHeight; 763 if (translationY > minHeight) { 764 containerTranslation = translationY; 765 expandButtonAlpha = 1.0f; 766 expandButtonTranslation = 0.0f; 767 } else { 768 containerTranslation = minHeight; 769 float progress = expandActionHeight != 0 770 ? (minHeight - translationY) / (float) expandActionHeight 771 : 1.0f; 772 expandButtonTranslation = -progress * expandActionHeight * 0.7f; 773 float alphaProgress = Math.min(progress / 0.7f, 1.0f); 774 alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress); 775 expandButtonAlpha = 1.0f - alphaProgress; 776 } 777 } 778 if (mChildExpandAnimator != null || mChildrenExpanded) { 779 expandButtonAlpha = (1.0f - mChildrenExpandProgress) 780 * expandButtonAlpha; 781 expandButtonTranslation = (1.0f - mChildrenExpandProgress) 782 * expandButtonTranslation; 783 float newTranslation = -getBottomDecorHeight(); 784 785 // We don't want to take the actual height of the view as this is already 786 // interpolated by a custom interpolator leading to a confusing animation. We want 787 // to have a stable end value to interpolate in between 788 float collapsedHeight = !mChildrenExpanded 789 ? Math.max(StackStateAnimator.getFinalActualHeight(this) 790 - getBottomDecorHeight(), minHeight) 791 : mExpandButtonStart; 792 float translationProgress = mFastOutSlowInInterpolator.getInterpolation( 793 mChildrenExpandProgress); 794 containerTranslation = (1.0f - translationProgress) * collapsedHeight 795 + translationProgress * newTranslation; 796 } 797 mExpandButton.setAlpha(expandButtonAlpha); 798 mExpandButtonDivider.setAlpha(expandButtonAlpha); 799 mExpandButton.setTranslationY(expandButtonTranslation); 800 mExpandButtonContainer.setTranslationY(containerTranslation); 801 NotificationContentView showingLayout = getShowingLayout(); 802 float layoutTranslation = 803 mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight(); 804 layoutTranslation = Math.min(layoutTranslation, 0); 805 if (!mChildrenExpanded && mChildExpandAnimator == null) { 806 // Needed for the DragDownHelper in order not to jump there, as the position 807 // can be negative for a short time. 808 layoutTranslation = 0; 809 } 810 showingLayout.setTranslationY(layoutTranslation); 811 if (mChildrenContainer != null) { 812 mChildrenContainer.setTranslationY( 813 mExpandButtonContainer.getTranslationY() + getBottomDecorHeight()); 814 } 815 } 816 updateExpandButtonColor()817 private void updateExpandButtonColor() { 818 // TODO: This needs some more baking, currently only the divider is colored according to 819 // the tint, but legacy black doesn't work yet perfectly for the button etc. 820 int color = getRippleColor(); 821 if (color == mNormalRippleColor) { 822 color = 0; 823 } 824 if (mExpandButtonDivider != null) { 825 applyTint(mExpandButtonDivider, color); 826 } 827 if (mChildrenContainer != null) { 828 mChildrenContainer.setTintColor(color); 829 } 830 } 831 applyTint(View v, int color)832 public static void applyTint(View v, int color) { 833 int alpha; 834 if (color != 0) { 835 alpha = COLORED_DIVIDER_ALPHA; 836 } else { 837 color = 0xff000000; 838 alpha = DEFAULT_DIVIDER_ALPHA; 839 } 840 if (v.getBackground() instanceof ColorDrawable) { 841 ColorDrawable background = (ColorDrawable) v.getBackground(); 842 background.mutate(); 843 background.setColor(color); 844 background.setAlpha(alpha); 845 } 846 } 847 getMaxExpandHeight()848 public int getMaxExpandHeight() { 849 return mMaxExpandHeight; 850 } 851 852 @Override isContentExpandable()853 public boolean isContentExpandable() { 854 NotificationContentView showingLayout = getShowingLayout(); 855 return showingLayout.isContentExpandable(); 856 } 857 858 @Override getContentView()859 protected View getContentView() { 860 return getShowingLayout(); 861 } 862 863 @Override setActualHeight(int height, boolean notifyListeners)864 public void setActualHeight(int height, boolean notifyListeners) { 865 super.setActualHeight(height, notifyListeners); 866 int contentHeight = calculateContentHeightFromActualHeight(height); 867 mPrivateLayout.setContentHeight(contentHeight); 868 mPublicLayout.setContentHeight(contentHeight); 869 if (mGuts != null) { 870 mGuts.setActualHeight(height); 871 } 872 invalidate(); 873 updateExpandButtonAppearance(); 874 } 875 876 @Override getMaxContentHeight()877 public int getMaxContentHeight() { 878 NotificationContentView showingLayout = getShowingLayout(); 879 return showingLayout.getMaxHeight(); 880 } 881 882 @Override getMinHeight()883 public int getMinHeight() { 884 NotificationContentView showingLayout = getShowingLayout(); 885 return showingLayout.getMinHeight(); 886 } 887 888 @Override setClipTopAmount(int clipTopAmount)889 public void setClipTopAmount(int clipTopAmount) { 890 super.setClipTopAmount(clipTopAmount); 891 mPrivateLayout.setClipTopAmount(clipTopAmount); 892 mPublicLayout.setClipTopAmount(clipTopAmount); 893 if (mGuts != null) { 894 mGuts.setClipTopAmount(clipTopAmount); 895 } 896 } 897 notifyContentUpdated()898 public void notifyContentUpdated() { 899 mPublicLayout.notifyContentUpdated(); 900 mPrivateLayout.notifyContentUpdated(); 901 } 902 isMaxExpandHeightInitialized()903 public boolean isMaxExpandHeightInitialized() { 904 return mMaxExpandHeight != 0; 905 } 906 getShowingLayout()907 private NotificationContentView getShowingLayout() { 908 return mShowingPublic ? mPublicLayout : mPrivateLayout; 909 } 910 911 @Override setShowingLegacyBackground(boolean showing)912 public void setShowingLegacyBackground(boolean showing) { 913 super.setShowingLegacyBackground(showing); 914 mPrivateLayout.setShowingLegacyBackground(showing); 915 mPublicLayout.setShowingLegacyBackground(showing); 916 } 917 setExpansionLogger(ExpansionLogger logger, String key)918 public void setExpansionLogger(ExpansionLogger logger, String key) { 919 mLogger = logger; 920 mLoggingKey = key; 921 } 922 logExpansionEvent(boolean userAction, boolean wasExpanded)923 private void logExpansionEvent(boolean userAction, boolean wasExpanded) { 924 final boolean nowExpanded = isExpanded(); 925 if (wasExpanded != nowExpanded && mLogger != null) { 926 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ; 927 } 928 } 929 } 930