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