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.ObjectAnimator; 22 import android.animation.ValueAnimator.AnimatorUpdateListener; 23 import android.annotation.Nullable; 24 import android.content.Context; 25 import android.graphics.drawable.AnimatedVectorDrawable; 26 import android.graphics.drawable.AnimationDrawable; 27 import android.graphics.drawable.ColorDrawable; 28 import android.graphics.drawable.Drawable; 29 import android.os.Build; 30 import android.os.Bundle; 31 import android.service.notification.StatusBarNotification; 32 import android.util.AttributeSet; 33 import android.util.FloatProperty; 34 import android.util.Property; 35 import android.view.LayoutInflater; 36 import android.view.MotionEvent; 37 import android.view.NotificationHeaderView; 38 import android.view.View; 39 import android.view.ViewStub; 40 import android.view.accessibility.AccessibilityEvent; 41 import android.view.accessibility.AccessibilityNodeInfo; 42 import android.widget.Chronometer; 43 import android.widget.ImageView; 44 45 import com.android.internal.logging.MetricsLogger; 46 import com.android.internal.logging.MetricsProto.MetricsEvent; 47 import com.android.internal.util.NotificationColorUtil; 48 import com.android.systemui.R; 49 import com.android.systemui.classifier.FalsingManager; 50 import com.android.systemui.statusbar.notification.HybridNotificationView; 51 import com.android.systemui.statusbar.phone.NotificationGroupManager; 52 import com.android.systemui.statusbar.policy.HeadsUpManager; 53 import com.android.systemui.statusbar.stack.NotificationChildrenContainer; 54 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 55 import com.android.systemui.statusbar.stack.StackScrollState; 56 import com.android.systemui.statusbar.stack.StackStateAnimator; 57 import com.android.systemui.statusbar.stack.StackViewState; 58 59 import java.util.ArrayList; 60 import java.util.List; 61 62 public class ExpandableNotificationRow extends ActivatableNotificationView { 63 64 private static final int DEFAULT_DIVIDER_ALPHA = 0x29; 65 private static final int COLORED_DIVIDER_ALPHA = 0x7B; 66 private int mNotificationMinHeightLegacy; 67 private int mMaxHeadsUpHeightLegacy; 68 private int mMaxHeadsUpHeight; 69 private int mNotificationMinHeight; 70 private int mNotificationMaxHeight; 71 private int mIncreasedPaddingBetweenElements; 72 73 /** Does this row contain layouts that can adapt to row expansion */ 74 private boolean mExpandable; 75 /** Has the user actively changed the expansion state of this row */ 76 private boolean mHasUserChangedExpansion; 77 /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ 78 private boolean mUserExpanded; 79 80 /** 81 * Has this notification been expanded while it was pinned 82 */ 83 private boolean mExpandedWhenPinned; 84 /** Is the user touching this row */ 85 private boolean mUserLocked; 86 /** Are we showing the "public" version */ 87 private boolean mShowingPublic; 88 private boolean mSensitive; 89 private boolean mSensitiveHiddenInGeneral; 90 private boolean mShowingPublicInitialized; 91 private boolean mHideSensitiveForIntrinsicHeight; 92 93 /** 94 * Is this notification expanded by the system. The expansion state can be overridden by the 95 * user expansion. 96 */ 97 private boolean mIsSystemExpanded; 98 99 /** 100 * Whether the notification is on the keyguard and the expansion is disabled. 101 */ 102 private boolean mOnKeyguard; 103 104 private Animator mTranslateAnim; 105 private ArrayList<View> mTranslateableViews; 106 private NotificationContentView mPublicLayout; 107 private NotificationContentView mPrivateLayout; 108 private int mMaxExpandHeight; 109 private int mHeadsUpHeight; 110 private View mVetoButton; 111 private int mNotificationColor; 112 private ExpansionLogger mLogger; 113 private String mLoggingKey; 114 private NotificationSettingsIconRow mSettingsIconRow; 115 private NotificationGuts mGuts; 116 private NotificationData.Entry mEntry; 117 private StatusBarNotification mStatusBarNotification; 118 private String mAppName; 119 private boolean mIsHeadsUp; 120 private boolean mLastChronometerRunning = true; 121 private ViewStub mChildrenContainerStub; 122 private NotificationGroupManager mGroupManager; 123 private boolean mChildrenExpanded; 124 private boolean mIsSummaryWithChildren; 125 private NotificationChildrenContainer mChildrenContainer; 126 private ViewStub mSettingsIconRowStub; 127 private ViewStub mGutsStub; 128 private boolean mIsSystemChildExpanded; 129 private boolean mIsPinned; 130 private FalsingManager mFalsingManager; 131 private HeadsUpManager mHeadsUpManager; 132 133 private boolean mJustClicked; 134 private boolean mIconAnimationRunning; 135 private boolean mShowNoBackground; 136 private ExpandableNotificationRow mNotificationParent; 137 private OnExpandClickListener mOnExpandClickListener; 138 private boolean mGroupExpansionChanging; 139 140 private OnClickListener mExpandClickListener = new OnClickListener() { 141 @Override 142 public void onClick(View v) { 143 if (!mShowingPublic && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) { 144 mGroupExpansionChanging = true; 145 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); 146 boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification); 147 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded); 148 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, 149 nowExpanded); 150 logExpansionEvent(true /* userAction */, wasExpanded); 151 } else { 152 if (v.isAccessibilityFocused()) { 153 mPrivateLayout.setFocusOnVisibilityChange(); 154 } 155 boolean nowExpanded; 156 if (isPinned()) { 157 nowExpanded = !mExpandedWhenPinned; 158 mExpandedWhenPinned = nowExpanded; 159 } else { 160 nowExpanded = !isExpanded(); 161 setUserExpanded(nowExpanded); 162 } 163 notifyHeightChanged(true); 164 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded); 165 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_EXPANDER, 166 nowExpanded); 167 } 168 } 169 }; 170 private boolean mForceUnlocked; 171 private boolean mDismissed; 172 private boolean mKeepInParent; 173 private boolean mRemoved; 174 private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT = 175 new FloatProperty<ExpandableNotificationRow>("translate") { 176 @Override 177 public void setValue(ExpandableNotificationRow object, float value) { 178 object.setTranslation(value); 179 } 180 181 @Override 182 public Float get(ExpandableNotificationRow object) { 183 return object.getTranslation(); 184 } 185 }; 186 private OnClickListener mOnClickListener; 187 private boolean mHeadsupDisappearRunning; 188 private View mChildAfterViewWhenDismissed; 189 private View mGroupParentWhenDismissed; 190 private boolean mRefocusOnDismiss; 191 192 public boolean isGroupExpansionChanging() { 193 if (isChildInGroup()) { 194 return mNotificationParent.isGroupExpansionChanging(); 195 } 196 return mGroupExpansionChanging; 197 } 198 199 public void setGroupExpansionChanging(boolean changing) { 200 mGroupExpansionChanging = changing; 201 } 202 203 @Override 204 public void setActualHeightAnimating(boolean animating) { 205 if (mPrivateLayout != null) { 206 mPrivateLayout.setContentHeightAnimating(animating); 207 } 208 } 209 210 public NotificationContentView getPrivateLayout() { 211 return mPrivateLayout; 212 } 213 214 public NotificationContentView getPublicLayout() { 215 return mPublicLayout; 216 } 217 218 public void setIconAnimationRunning(boolean running) { 219 setIconAnimationRunning(running, mPublicLayout); 220 setIconAnimationRunning(running, mPrivateLayout); 221 if (mIsSummaryWithChildren) { 222 setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView()); 223 List<ExpandableNotificationRow> notificationChildren = 224 mChildrenContainer.getNotificationChildren(); 225 for (int i = 0; i < notificationChildren.size(); i++) { 226 ExpandableNotificationRow child = notificationChildren.get(i); 227 child.setIconAnimationRunning(running); 228 } 229 } 230 mIconAnimationRunning = running; 231 } 232 233 private void setIconAnimationRunning(boolean running, NotificationContentView layout) { 234 if (layout != null) { 235 View contractedChild = layout.getContractedChild(); 236 View expandedChild = layout.getExpandedChild(); 237 View headsUpChild = layout.getHeadsUpChild(); 238 setIconAnimationRunningForChild(running, contractedChild); 239 setIconAnimationRunningForChild(running, expandedChild); 240 setIconAnimationRunningForChild(running, headsUpChild); 241 } 242 } 243 244 private void setIconAnimationRunningForChild(boolean running, View child) { 245 if (child != null) { 246 ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon); 247 setIconRunning(icon, running); 248 ImageView rightIcon = (ImageView) child.findViewById( 249 com.android.internal.R.id.right_icon); 250 setIconRunning(rightIcon, running); 251 } 252 } 253 254 private void setIconRunning(ImageView imageView, boolean running) { 255 if (imageView != null) { 256 Drawable drawable = imageView.getDrawable(); 257 if (drawable instanceof AnimationDrawable) { 258 AnimationDrawable animationDrawable = (AnimationDrawable) drawable; 259 if (running) { 260 animationDrawable.start(); 261 } else { 262 animationDrawable.stop(); 263 } 264 } else if (drawable instanceof AnimatedVectorDrawable) { 265 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable; 266 if (running) { 267 animationDrawable.start(); 268 } else { 269 animationDrawable.stop(); 270 } 271 } 272 } 273 } 274 275 public void onNotificationUpdated(NotificationData.Entry entry) { 276 mEntry = entry; 277 mStatusBarNotification = entry.notification; 278 mPrivateLayout.onNotificationUpdated(entry); 279 mPublicLayout.onNotificationUpdated(entry); 280 mShowingPublicInitialized = false; 281 updateNotificationColor(); 282 if (mIsSummaryWithChildren) { 283 mChildrenContainer.recreateNotificationHeader(mExpandClickListener, mEntry.notification); 284 mChildrenContainer.onNotificationUpdated(); 285 } 286 if (mIconAnimationRunning) { 287 setIconAnimationRunning(true); 288 } 289 if (mNotificationParent != null) { 290 mNotificationParent.updateChildrenHeaderAppearance(); 291 } 292 onChildrenCountChanged(); 293 // The public layouts expand button is always visible 294 mPublicLayout.updateExpandButtons(true); 295 updateLimits(); 296 } 297 298 private void updateLimits() { 299 updateLimitsForView(mPrivateLayout); 300 updateLimitsForView(mPublicLayout); 301 } 302 303 private void updateLimitsForView(NotificationContentView layout) { 304 boolean customView = layout.getContractedChild().getId() 305 != com.android.internal.R.id.status_bar_latest_event_content; 306 boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; 307 int minHeight = customView && beforeN && !mIsSummaryWithChildren ? 308 mNotificationMinHeightLegacy : mNotificationMinHeight; 309 boolean headsUpCustom = layout.getHeadsUpChild() != null && 310 layout.getHeadsUpChild().getId() 311 != com.android.internal.R.id.status_bar_latest_event_content; 312 int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy 313 : mMaxHeadsUpHeight; 314 layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight); 315 } 316 317 public StatusBarNotification getStatusBarNotification() { 318 return mStatusBarNotification; 319 } 320 321 public boolean isHeadsUp() { 322 return mIsHeadsUp; 323 } 324 325 public void setHeadsUp(boolean isHeadsUp) { 326 int intrinsicBefore = getIntrinsicHeight(); 327 mIsHeadsUp = isHeadsUp; 328 mPrivateLayout.setHeadsUp(isHeadsUp); 329 if (mIsSummaryWithChildren) { 330 // The overflow might change since we allow more lines as HUN. 331 mChildrenContainer.updateGroupOverflow(); 332 } 333 if (intrinsicBefore != getIntrinsicHeight()) { 334 notifyHeightChanged(false /* needsAnimation */); 335 } 336 } 337 338 public void setGroupManager(NotificationGroupManager groupManager) { 339 mGroupManager = groupManager; 340 mPrivateLayout.setGroupManager(groupManager); 341 } 342 343 public void setRemoteInputController(RemoteInputController r) { 344 mPrivateLayout.setRemoteInputController(r); 345 } 346 347 public void setAppName(String appName) { 348 mAppName = appName; 349 if (mSettingsIconRow != null) { 350 mSettingsIconRow.setAppName(mAppName); 351 } 352 } 353 354 public void addChildNotification(ExpandableNotificationRow row) { 355 addChildNotification(row, -1); 356 } 357 358 /** 359 * Add a child notification to this view. 360 * 361 * @param row the row to add 362 * @param childIndex the index to add it at, if -1 it will be added at the end 363 */ 364 public void addChildNotification(ExpandableNotificationRow row, int childIndex) { 365 if (mChildrenContainer == null) { 366 mChildrenContainerStub.inflate(); 367 } 368 mChildrenContainer.addNotification(row, childIndex); 369 onChildrenCountChanged(); 370 row.setIsChildInGroup(true, this); 371 } 372 373 public void removeChildNotification(ExpandableNotificationRow row) { 374 if (mChildrenContainer != null) { 375 mChildrenContainer.removeNotification(row); 376 } 377 onChildrenCountChanged(); 378 row.setIsChildInGroup(false, null); 379 } 380 381 public boolean isChildInGroup() { 382 return mNotificationParent != null; 383 } 384 385 public ExpandableNotificationRow getNotificationParent() { 386 return mNotificationParent; 387 } 388 389 /** 390 * @param isChildInGroup Is this notification now in a group 391 * @param parent the new parent notification 392 */ 393 public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {; 394 boolean childInGroup = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup; 395 mNotificationParent = childInGroup ? parent : null; 396 mPrivateLayout.setIsChildInGroup(childInGroup); 397 resetBackgroundAlpha(); 398 updateBackgroundForGroupState(); 399 updateClickAndFocus(); 400 if (mNotificationParent != null) { 401 mNotificationParent.updateBackgroundForGroupState(); 402 } 403 } 404 405 @Override 406 public boolean onTouchEvent(MotionEvent event) { 407 if (event.getActionMasked() != MotionEvent.ACTION_DOWN 408 || !isChildInGroup() || isGroupExpanded()) { 409 return super.onTouchEvent(event); 410 } else { 411 return false; 412 } 413 } 414 415 @Override 416 protected boolean handleSlideBack() { 417 if (mSettingsIconRow != null && mSettingsIconRow.isVisible()) { 418 animateTranslateNotification(0 /* targetLeft */); 419 return true; 420 } 421 return false; 422 } 423 424 @Override 425 protected boolean shouldHideBackground() { 426 return super.shouldHideBackground() || mShowNoBackground; 427 } 428 429 @Override 430 public boolean isSummaryWithChildren() { 431 return mIsSummaryWithChildren; 432 } 433 434 @Override 435 public boolean areChildrenExpanded() { 436 return mChildrenExpanded; 437 } 438 439 public List<ExpandableNotificationRow> getNotificationChildren() { 440 return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); 441 } 442 443 public int getNumberOfNotificationChildren() { 444 if (mChildrenContainer == null) { 445 return 0; 446 } 447 return mChildrenContainer.getNotificationChildren().size(); 448 } 449 450 /** 451 * Apply the order given in the list to the children. 452 * 453 * @param childOrder the new list order 454 * @return whether the list order has changed 455 */ 456 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { 457 return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder); 458 } 459 460 public void getChildrenStates(StackScrollState resultState) { 461 if (mIsSummaryWithChildren) { 462 StackViewState parentState = resultState.getViewStateForView(this); 463 mChildrenContainer.getState(resultState, parentState); 464 } 465 } 466 467 public void applyChildrenState(StackScrollState state) { 468 if (mIsSummaryWithChildren) { 469 mChildrenContainer.applyState(state); 470 } 471 } 472 473 public void prepareExpansionChanged(StackScrollState state) { 474 if (mIsSummaryWithChildren) { 475 mChildrenContainer.prepareExpansionChanged(state); 476 } 477 } 478 479 public void startChildAnimation(StackScrollState finalState, 480 StackStateAnimator stateAnimator, long delay, long duration) { 481 if (mIsSummaryWithChildren) { 482 mChildrenContainer.startAnimationToState(finalState, stateAnimator, delay, 483 duration); 484 } 485 } 486 487 public ExpandableNotificationRow getViewAtPosition(float y) { 488 if (!mIsSummaryWithChildren || !mChildrenExpanded) { 489 return this; 490 } else { 491 ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); 492 return view == null ? this : view; 493 } 494 } 495 496 public NotificationGuts getGuts() { 497 return mGuts; 498 } 499 500 /** 501 * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this 502 * the notification will be rendered on top of the screen. 503 * 504 * @param pinned whether it is pinned 505 */ 506 public void setPinned(boolean pinned) { 507 int intrinsicHeight = getIntrinsicHeight(); 508 mIsPinned = pinned; 509 if (intrinsicHeight != getIntrinsicHeight()) { 510 notifyHeightChanged(false); 511 } 512 if (pinned) { 513 setIconAnimationRunning(true); 514 mExpandedWhenPinned = false; 515 } else if (mExpandedWhenPinned) { 516 setUserExpanded(true); 517 } 518 setChronometerRunning(mLastChronometerRunning); 519 } 520 521 public boolean isPinned() { 522 return mIsPinned; 523 } 524 525 /** 526 * @param atLeastMinHeight should the value returned be at least the minimum height. 527 * Used to avoid cyclic calls 528 * @return the height of the heads up notification when pinned 529 */ 530 public int getPinnedHeadsUpHeight(boolean atLeastMinHeight) { 531 if (mIsSummaryWithChildren) { 532 return mChildrenContainer.getIntrinsicHeight(); 533 } 534 if(mExpandedWhenPinned) { 535 return Math.max(getMaxExpandHeight(), mHeadsUpHeight); 536 } else if (atLeastMinHeight) { 537 return Math.max(getCollapsedHeight(), mHeadsUpHeight); 538 } else { 539 return mHeadsUpHeight; 540 } 541 } 542 543 /** 544 * Mark whether this notification was just clicked, i.e. the user has just clicked this 545 * notification in this frame. 546 */ 547 public void setJustClicked(boolean justClicked) { 548 mJustClicked = justClicked; 549 } 550 551 /** 552 * @return true if this notification has been clicked in this frame, false otherwise 553 */ 554 public boolean wasJustClicked() { 555 return mJustClicked; 556 } 557 558 public void setChronometerRunning(boolean running) { 559 mLastChronometerRunning = running; 560 setChronometerRunning(running, mPrivateLayout); 561 setChronometerRunning(running, mPublicLayout); 562 if (mChildrenContainer != null) { 563 List<ExpandableNotificationRow> notificationChildren = 564 mChildrenContainer.getNotificationChildren(); 565 for (int i = 0; i < notificationChildren.size(); i++) { 566 ExpandableNotificationRow child = notificationChildren.get(i); 567 child.setChronometerRunning(running); 568 } 569 } 570 } 571 572 private void setChronometerRunning(boolean running, NotificationContentView layout) { 573 if (layout != null) { 574 running = running || isPinned(); 575 View contractedChild = layout.getContractedChild(); 576 View expandedChild = layout.getExpandedChild(); 577 View headsUpChild = layout.getHeadsUpChild(); 578 setChronometerRunningForChild(running, contractedChild); 579 setChronometerRunningForChild(running, expandedChild); 580 setChronometerRunningForChild(running, headsUpChild); 581 } 582 } 583 584 private void setChronometerRunningForChild(boolean running, View child) { 585 if (child != null) { 586 View chronometer = child.findViewById(com.android.internal.R.id.chronometer); 587 if (chronometer instanceof Chronometer) { 588 ((Chronometer) chronometer).setStarted(running); 589 } 590 } 591 } 592 593 public NotificationHeaderView getNotificationHeader() { 594 if (mIsSummaryWithChildren) { 595 return mChildrenContainer.getHeaderView(); 596 } 597 return mPrivateLayout.getNotificationHeader(); 598 } 599 600 private NotificationHeaderView getVisibleNotificationHeader() { 601 if (mIsSummaryWithChildren && !mShowingPublic) { 602 return mChildrenContainer.getHeaderView(); 603 } 604 return getShowingLayout().getVisibleNotificationHeader(); 605 } 606 607 public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { 608 mOnExpandClickListener = onExpandClickListener; 609 } 610 611 @Override 612 public void setOnClickListener(@Nullable OnClickListener l) { 613 super.setOnClickListener(l); 614 mOnClickListener = l; 615 updateClickAndFocus(); 616 } 617 618 private void updateClickAndFocus() { 619 boolean normalChild = !isChildInGroup() || isGroupExpanded(); 620 boolean clickable = mOnClickListener != null && normalChild; 621 if (isFocusable() != normalChild) { 622 setFocusable(normalChild); 623 } 624 if (isClickable() != clickable) { 625 setClickable(clickable); 626 } 627 } 628 629 public void setHeadsUpManager(HeadsUpManager headsUpManager) { 630 mHeadsUpManager = headsUpManager; 631 } 632 633 public void reInflateViews() { 634 initDimens(); 635 if (mIsSummaryWithChildren) { 636 if (mChildrenContainer != null) { 637 mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.notification); 638 } 639 } 640 if (mGuts != null) { 641 View oldGuts = mGuts; 642 int index = indexOfChild(oldGuts); 643 removeView(oldGuts); 644 mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate( 645 R.layout.notification_guts, this, false); 646 mGuts.setVisibility(oldGuts.getVisibility()); 647 addView(mGuts, index); 648 } 649 if (mSettingsIconRow != null) { 650 View oldSettings = mSettingsIconRow; 651 int settingsIndex = indexOfChild(oldSettings); 652 removeView(oldSettings); 653 mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate( 654 R.layout.notification_settings_icon_row, this, false); 655 mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this); 656 mSettingsIconRow.setAppName(mAppName); 657 mSettingsIconRow.setVisibility(oldSettings.getVisibility()); 658 addView(mSettingsIconRow, settingsIndex); 659 660 } 661 mPrivateLayout.reInflateViews(); 662 mPublicLayout.reInflateViews(); 663 } 664 665 public void setContentBackground(int customBackgroundColor, boolean animate, 666 NotificationContentView notificationContentView) { 667 if (getShowingLayout() == notificationContentView) { 668 setTintColor(customBackgroundColor, animate); 669 } 670 } 671 672 public void closeRemoteInput() { 673 mPrivateLayout.closeRemoteInput(); 674 mPublicLayout.closeRemoteInput(); 675 } 676 677 /** 678 * Set by how much the single line view should be indented. 679 */ 680 public void setSingleLineWidthIndention(int indention) { 681 mPrivateLayout.setSingleLineWidthIndention(indention); 682 } 683 684 public int getNotificationColor() { 685 return mNotificationColor; 686 } 687 688 private void updateNotificationColor() { 689 mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext, 690 getStatusBarNotification().getNotification().color); 691 } 692 693 public HybridNotificationView getSingleLineView() { 694 return mPrivateLayout.getSingleLineView(); 695 } 696 697 public boolean isOnKeyguard() { 698 return mOnKeyguard; 699 } 700 701 public void removeAllChildren() { 702 List<ExpandableNotificationRow> notificationChildren 703 = mChildrenContainer.getNotificationChildren(); 704 ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren); 705 for (int i = 0; i < clonedList.size(); i++) { 706 ExpandableNotificationRow row = clonedList.get(i); 707 if (row.keepInParent()) { 708 continue; 709 } 710 mChildrenContainer.removeNotification(row); 711 row.setIsChildInGroup(false, null); 712 } 713 onChildrenCountChanged(); 714 } 715 716 public void setForceUnlocked(boolean forceUnlocked) { 717 mForceUnlocked = forceUnlocked; 718 if (mIsSummaryWithChildren) { 719 List<ExpandableNotificationRow> notificationChildren = getNotificationChildren(); 720 for (ExpandableNotificationRow child : notificationChildren) { 721 child.setForceUnlocked(forceUnlocked); 722 } 723 } 724 } 725 726 public void setDismissed(boolean dismissed, boolean fromAccessibility) { 727 mDismissed = dismissed; 728 mGroupParentWhenDismissed = mNotificationParent; 729 mRefocusOnDismiss = fromAccessibility; 730 mChildAfterViewWhenDismissed = null; 731 if (isChildInGroup()) { 732 List<ExpandableNotificationRow> notificationChildren = 733 mNotificationParent.getNotificationChildren(); 734 int i = notificationChildren.indexOf(this); 735 if (i != -1 && i < notificationChildren.size() - 1) { 736 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1); 737 } 738 } 739 } 740 741 public boolean isDismissed() { 742 return mDismissed; 743 } 744 745 public boolean keepInParent() { 746 return mKeepInParent; 747 } 748 749 public void setKeepInParent(boolean keepInParent) { 750 mKeepInParent = keepInParent; 751 } 752 753 public boolean isRemoved() { 754 return mRemoved; 755 } 756 757 public void setRemoved() { 758 mRemoved = true; 759 760 mPrivateLayout.setRemoved(); 761 } 762 763 public NotificationChildrenContainer getChildrenContainer() { 764 return mChildrenContainer; 765 } 766 767 public void setHeadsupDisappearRunning(boolean running) { 768 mHeadsupDisappearRunning = running; 769 mPrivateLayout.setHeadsupDisappearRunning(running); 770 } 771 772 public View getChildAfterViewWhenDismissed() { 773 return mChildAfterViewWhenDismissed; 774 } 775 776 public View getGroupParentWhenDismissed() { 777 return mGroupParentWhenDismissed; 778 } 779 780 public void performDismiss() { 781 mVetoButton.performClick(); 782 } 783 784 public void setOnDismissListener(OnClickListener listener) { 785 mVetoButton.setOnClickListener(listener); 786 } 787 788 public interface ExpansionLogger { 789 public void logNotificationExpansion(String key, boolean userAction, boolean expanded); 790 } 791 792 public ExpandableNotificationRow(Context context, AttributeSet attrs) { 793 super(context, attrs); 794 mFalsingManager = FalsingManager.getInstance(context); 795 initDimens(); 796 } 797 798 private void initDimens() { 799 mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy); 800 mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height); 801 mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height); 802 mMaxHeadsUpHeightLegacy = getFontScaledHeight( 803 R.dimen.notification_max_heads_up_height_legacy); 804 mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height); 805 mIncreasedPaddingBetweenElements = getResources() 806 .getDimensionPixelSize(R.dimen.notification_divider_height_increased); 807 } 808 809 /** 810 * @param dimenId the dimen to look up 811 * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp 812 */ 813 private int getFontScaledHeight(int dimenId) { 814 int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId); 815 float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity / 816 getResources().getDisplayMetrics().density); 817 return (int) (dimensionPixelSize * factor); 818 } 819 820 /** 821 * Resets this view so it can be re-used for an updated notification. 822 */ 823 @Override 824 public void reset() { 825 super.reset(); 826 final boolean wasExpanded = isExpanded(); 827 mExpandable = false; 828 mHasUserChangedExpansion = false; 829 mUserLocked = false; 830 mShowingPublic = false; 831 mSensitive = false; 832 mShowingPublicInitialized = false; 833 mIsSystemExpanded = false; 834 mOnKeyguard = false; 835 mPublicLayout.reset(); 836 mPrivateLayout.reset(); 837 resetHeight(); 838 resetTranslation(); 839 logExpansionEvent(false, wasExpanded); 840 } 841 842 public void resetHeight() { 843 mMaxExpandHeight = 0; 844 mHeadsUpHeight = 0; 845 onHeightReset(); 846 requestLayout(); 847 } 848 849 @Override 850 protected void onFinishInflate() { 851 super.onFinishInflate(); 852 mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); 853 mPublicLayout.setContainingNotification(this); 854 mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); 855 mPrivateLayout.setExpandClickListener(mExpandClickListener); 856 mPrivateLayout.setContainingNotification(this); 857 mPublicLayout.setExpandClickListener(mExpandClickListener); 858 mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub); 859 mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() { 860 @Override 861 public void onInflate(ViewStub stub, View inflated) { 862 mSettingsIconRow = (NotificationSettingsIconRow) inflated; 863 mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this); 864 mSettingsIconRow.setAppName(mAppName); 865 } 866 }); 867 mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); 868 mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { 869 @Override 870 public void onInflate(ViewStub stub, View inflated) { 871 mGuts = (NotificationGuts) inflated; 872 mGuts.setClipTopAmount(getClipTopAmount()); 873 mGuts.setActualHeight(getActualHeight()); 874 mGutsStub = null; 875 } 876 }); 877 mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub); 878 mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() { 879 880 @Override 881 public void onInflate(ViewStub stub, View inflated) { 882 mChildrenContainer = (NotificationChildrenContainer) inflated; 883 mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this); 884 mChildrenContainer.onNotificationUpdated(); 885 mTranslateableViews.add(mChildrenContainer); 886 } 887 }); 888 mVetoButton = findViewById(R.id.veto); 889 mVetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 890 mVetoButton.setContentDescription(mContext.getString( 891 R.string.accessibility_remove_notification)); 892 893 // Add the views that we translate to reveal the gear 894 mTranslateableViews = new ArrayList<View>(); 895 for (int i = 0; i < getChildCount(); i++) { 896 mTranslateableViews.add(getChildAt(i)); 897 } 898 // Remove views that don't translate 899 mTranslateableViews.remove(mVetoButton); 900 mTranslateableViews.remove(mSettingsIconRowStub); 901 mTranslateableViews.remove(mChildrenContainerStub); 902 mTranslateableViews.remove(mGutsStub); 903 } 904 905 public View getVetoButton() { 906 return mVetoButton; 907 } 908 909 public void resetTranslation() { 910 if (mTranslateableViews != null) { 911 for (int i = 0; i < mTranslateableViews.size(); i++) { 912 mTranslateableViews.get(i).setTranslationX(0); 913 } 914 } 915 invalidateOutline(); 916 if (mSettingsIconRow != null) { 917 mSettingsIconRow.resetState(); 918 } 919 } 920 921 public void animateTranslateNotification(final float leftTarget) { 922 if (mTranslateAnim != null) { 923 mTranslateAnim.cancel(); 924 } 925 mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */); 926 if (mTranslateAnim != null) { 927 mTranslateAnim.start(); 928 } 929 } 930 931 @Override 932 public void setTranslation(float translationX) { 933 if (areGutsExposed()) { 934 // Don't translate if guts are showing. 935 return; 936 } 937 // Translate the group of views 938 for (int i = 0; i < mTranslateableViews.size(); i++) { 939 if (mTranslateableViews.get(i) != null) { 940 mTranslateableViews.get(i).setTranslationX(translationX); 941 } 942 } 943 invalidateOutline(); 944 if (mSettingsIconRow != null) { 945 mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth()); 946 } 947 } 948 949 @Override 950 public float getTranslation() { 951 if (mTranslateableViews != null && mTranslateableViews.size() > 0) { 952 // All of the views in the list should have same translation, just use first one. 953 return mTranslateableViews.get(0).getTranslationX(); 954 } 955 return 0; 956 } 957 958 public Animator getTranslateViewAnimator(final float leftTarget, 959 AnimatorUpdateListener listener) { 960 if (mTranslateAnim != null) { 961 mTranslateAnim.cancel(); 962 } 963 if (areGutsExposed()) { 964 // No translation if guts are exposed. 965 return null; 966 } 967 final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT, 968 leftTarget); 969 if (listener != null) { 970 translateAnim.addUpdateListener(listener); 971 } 972 translateAnim.addListener(new AnimatorListenerAdapter() { 973 boolean cancelled = false; 974 975 @Override 976 public void onAnimationCancel(Animator anim) { 977 cancelled = true; 978 } 979 980 @Override 981 public void onAnimationEnd(Animator anim) { 982 if (!cancelled && mSettingsIconRow != null && leftTarget == 0) { 983 mSettingsIconRow.resetState(); 984 mTranslateAnim = null; 985 } 986 } 987 }); 988 mTranslateAnim = translateAnim; 989 return translateAnim; 990 } 991 992 public float getSpaceForGear() { 993 if (mSettingsIconRow != null) { 994 return mSettingsIconRow.getSpaceForGear(); 995 } 996 return 0; 997 } 998 999 public NotificationSettingsIconRow getSettingsRow() { 1000 if (mSettingsIconRow == null) { 1001 mSettingsIconRowStub.inflate(); 1002 } 1003 return mSettingsIconRow; 1004 } 1005 1006 public void inflateGuts() { 1007 if (mGuts == null) { 1008 mGutsStub.inflate(); 1009 } 1010 } 1011 1012 private void updateChildrenVisibility() { 1013 mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE 1014 : INVISIBLE); 1015 if (mChildrenContainer != null) { 1016 mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE 1017 : INVISIBLE); 1018 mChildrenContainer.updateHeaderVisibility(!mShowingPublic && mIsSummaryWithChildren 1019 ? VISIBLE 1020 : INVISIBLE); 1021 } 1022 // The limits might have changed if the view suddenly became a group or vice versa 1023 updateLimits(); 1024 } 1025 1026 @Override 1027 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 1028 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 1029 // Add a record for the entire layout since its content is somehow small. 1030 // The event comes from a leaf view that is interacted with. 1031 AccessibilityEvent record = AccessibilityEvent.obtain(); 1032 onInitializeAccessibilityEvent(record); 1033 dispatchPopulateAccessibilityEvent(record); 1034 event.appendRecord(record); 1035 return true; 1036 } 1037 return false; 1038 } 1039 1040 @Override 1041 public void setDark(boolean dark, boolean fade, long delay) { 1042 super.setDark(dark, fade, delay); 1043 final NotificationContentView showing = getShowingLayout(); 1044 if (showing != null) { 1045 showing.setDark(dark, fade, delay); 1046 } 1047 if (mIsSummaryWithChildren) { 1048 mChildrenContainer.setDark(dark, fade, delay); 1049 } 1050 } 1051 1052 public boolean isExpandable() { 1053 if (mIsSummaryWithChildren && !mShowingPublic) { 1054 return !mChildrenExpanded; 1055 } 1056 return mExpandable; 1057 } 1058 1059 public void setExpandable(boolean expandable) { 1060 mExpandable = expandable; 1061 mPrivateLayout.updateExpandButtons(isExpandable()); 1062 } 1063 1064 @Override 1065 public void setClipToActualHeight(boolean clipToActualHeight) { 1066 super.setClipToActualHeight(clipToActualHeight || isUserLocked()); 1067 getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked()); 1068 } 1069 1070 /** 1071 * @return whether the user has changed the expansion state 1072 */ 1073 public boolean hasUserChangedExpansion() { 1074 return mHasUserChangedExpansion; 1075 } 1076 1077 public boolean isUserExpanded() { 1078 return mUserExpanded; 1079 } 1080 1081 /** 1082 * Set this notification to be expanded by the user 1083 * 1084 * @param userExpanded whether the user wants this notification to be expanded 1085 */ 1086 public void setUserExpanded(boolean userExpanded) { 1087 setUserExpanded(userExpanded, false /* allowChildExpansion */); 1088 } 1089 1090 /** 1091 * Set this notification to be expanded by the user 1092 * 1093 * @param userExpanded whether the user wants this notification to be expanded 1094 * @param allowChildExpansion whether a call to this method allows expanding children 1095 */ 1096 public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { 1097 mFalsingManager.setNotificationExpanded(); 1098 if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion) { 1099 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); 1100 mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded); 1101 logExpansionEvent(true /* userAction */, wasExpanded); 1102 return; 1103 } 1104 if (userExpanded && !mExpandable) return; 1105 final boolean wasExpanded = isExpanded(); 1106 mHasUserChangedExpansion = true; 1107 mUserExpanded = userExpanded; 1108 logExpansionEvent(true, wasExpanded); 1109 } 1110 1111 public void resetUserExpansion() { 1112 mHasUserChangedExpansion = false; 1113 mUserExpanded = false; 1114 } 1115 1116 public boolean isUserLocked() { 1117 return mUserLocked && !mForceUnlocked; 1118 } 1119 1120 public void setUserLocked(boolean userLocked) { 1121 mUserLocked = userLocked; 1122 mPrivateLayout.setUserExpanding(userLocked); 1123 if (mIsSummaryWithChildren) { 1124 mChildrenContainer.setUserLocked(userLocked); 1125 if (userLocked || !isGroupExpanded()) { 1126 updateBackgroundForGroupState(); 1127 } 1128 } 1129 } 1130 1131 /** 1132 * @return has the system set this notification to be expanded 1133 */ 1134 public boolean isSystemExpanded() { 1135 return mIsSystemExpanded; 1136 } 1137 1138 /** 1139 * Set this notification to be expanded by the system. 1140 * 1141 * @param expand whether the system wants this notification to be expanded. 1142 */ 1143 public void setSystemExpanded(boolean expand) { 1144 if (expand != mIsSystemExpanded) { 1145 final boolean wasExpanded = isExpanded(); 1146 mIsSystemExpanded = expand; 1147 notifyHeightChanged(false /* needsAnimation */); 1148 logExpansionEvent(false, wasExpanded); 1149 if (mIsSummaryWithChildren) { 1150 mChildrenContainer.updateGroupOverflow(); 1151 } 1152 } 1153 } 1154 1155 /** 1156 * @param onKeyguard whether to prevent notification expansion 1157 */ 1158 public void setOnKeyguard(boolean onKeyguard) { 1159 if (onKeyguard != mOnKeyguard) { 1160 final boolean wasExpanded = isExpanded(); 1161 mOnKeyguard = onKeyguard; 1162 logExpansionEvent(false, wasExpanded); 1163 if (wasExpanded != isExpanded()) { 1164 if (mIsSummaryWithChildren) { 1165 mChildrenContainer.updateGroupOverflow(); 1166 } 1167 notifyHeightChanged(false /* needsAnimation */); 1168 } 1169 } 1170 } 1171 1172 /** 1173 * @return Can the underlying notification be cleared? This can be different from whether the 1174 * notification can be dismissed in case notifications are sensitive on the lockscreen. 1175 * @see #canViewBeDismissed() 1176 */ 1177 public boolean isClearable() { 1178 if (mStatusBarNotification == null || !mStatusBarNotification.isClearable()) { 1179 return false; 1180 } 1181 if (mIsSummaryWithChildren) { 1182 List<ExpandableNotificationRow> notificationChildren = 1183 mChildrenContainer.getNotificationChildren(); 1184 for (int i = 0; i < notificationChildren.size(); i++) { 1185 ExpandableNotificationRow child = notificationChildren.get(i); 1186 if (!child.isClearable()) { 1187 return false; 1188 } 1189 } 1190 } 1191 return true; 1192 } 1193 1194 @Override 1195 public int getIntrinsicHeight() { 1196 if (isUserLocked()) { 1197 return getActualHeight(); 1198 } 1199 if (mGuts != null && mGuts.areGutsExposed()) { 1200 return mGuts.getHeight(); 1201 } else if ((isChildInGroup() && !isGroupExpanded())) { 1202 return mPrivateLayout.getMinHeight(); 1203 } else if (mSensitive && mHideSensitiveForIntrinsicHeight) { 1204 return getMinHeight(); 1205 } else if (mIsSummaryWithChildren && !mOnKeyguard) { 1206 return mChildrenContainer.getIntrinsicHeight(); 1207 } else if (mIsHeadsUp || mHeadsupDisappearRunning) { 1208 if (isPinned() || mHeadsupDisappearRunning) { 1209 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); 1210 } else if (isExpanded()) { 1211 return Math.max(getMaxExpandHeight(), mHeadsUpHeight); 1212 } else { 1213 return Math.max(getCollapsedHeight(), mHeadsUpHeight); 1214 } 1215 } else if (isExpanded()) { 1216 return getMaxExpandHeight(); 1217 } else { 1218 return getCollapsedHeight(); 1219 } 1220 } 1221 1222 public boolean isGroupExpanded() { 1223 return mGroupManager.isGroupExpanded(mStatusBarNotification); 1224 } 1225 1226 private void onChildrenCountChanged() { 1227 mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS 1228 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0; 1229 if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) { 1230 mChildrenContainer.recreateNotificationHeader(mExpandClickListener, 1231 mEntry.notification); 1232 } 1233 getShowingLayout().updateBackgroundColor(false /* animate */); 1234 mPrivateLayout.updateExpandButtons(isExpandable()); 1235 updateChildrenHeaderAppearance(); 1236 updateChildrenVisibility(); 1237 } 1238 1239 public void updateChildrenHeaderAppearance() { 1240 if (mIsSummaryWithChildren) { 1241 mChildrenContainer.updateChildrenHeaderAppearance(); 1242 } 1243 } 1244 1245 /** 1246 * Check whether the view state is currently expanded. This is given by the system in {@link 1247 * #setSystemExpanded(boolean)} and can be overridden by user expansion or 1248 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this 1249 * view can differ from this state, if layout params are modified from outside. 1250 * 1251 * @return whether the view state is currently expanded. 1252 */ 1253 public boolean isExpanded() { 1254 return isExpanded(false /* allowOnKeyguard */); 1255 } 1256 1257 public boolean isExpanded(boolean allowOnKeyguard) { 1258 return (!mOnKeyguard || allowOnKeyguard) 1259 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) 1260 || isUserExpanded()); 1261 } 1262 1263 private boolean isSystemChildExpanded() { 1264 return mIsSystemChildExpanded; 1265 } 1266 1267 public void setSystemChildExpanded(boolean expanded) { 1268 mIsSystemChildExpanded = expanded; 1269 } 1270 1271 @Override 1272 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1273 super.onLayout(changed, left, top, right, bottom); 1274 updateMaxHeights(); 1275 if (mSettingsIconRow != null) { 1276 mSettingsIconRow.updateVerticalLocation(); 1277 } 1278 } 1279 1280 private void updateMaxHeights() { 1281 int intrinsicBefore = getIntrinsicHeight(); 1282 View expandedChild = mPrivateLayout.getExpandedChild(); 1283 if (expandedChild == null) { 1284 expandedChild = mPrivateLayout.getContractedChild(); 1285 } 1286 mMaxExpandHeight = expandedChild.getHeight(); 1287 View headsUpChild = mPrivateLayout.getHeadsUpChild(); 1288 if (headsUpChild == null) { 1289 headsUpChild = mPrivateLayout.getContractedChild(); 1290 } 1291 mHeadsUpHeight = headsUpChild.getHeight(); 1292 if (intrinsicBefore != getIntrinsicHeight()) { 1293 notifyHeightChanged(false /* needsAnimation */); 1294 } 1295 } 1296 1297 @Override 1298 public void notifyHeightChanged(boolean needsAnimation) { 1299 super.notifyHeightChanged(needsAnimation); 1300 getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked()); 1301 } 1302 1303 public void setSensitive(boolean sensitive, boolean hideSensitive) { 1304 mSensitive = sensitive; 1305 mSensitiveHiddenInGeneral = hideSensitive; 1306 } 1307 1308 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { 1309 mHideSensitiveForIntrinsicHeight = hideSensitive; 1310 if (mIsSummaryWithChildren) { 1311 List<ExpandableNotificationRow> notificationChildren = 1312 mChildrenContainer.getNotificationChildren(); 1313 for (int i = 0; i < notificationChildren.size(); i++) { 1314 ExpandableNotificationRow child = notificationChildren.get(i); 1315 child.setHideSensitiveForIntrinsicHeight(hideSensitive); 1316 } 1317 } 1318 } 1319 1320 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, 1321 long duration) { 1322 boolean oldShowingPublic = mShowingPublic; 1323 mShowingPublic = mSensitive && hideSensitive; 1324 if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { 1325 return; 1326 } 1327 1328 // bail out if no public version 1329 if (mPublicLayout.getChildCount() == 0) return; 1330 1331 if (!animated) { 1332 mPublicLayout.animate().cancel(); 1333 mPrivateLayout.animate().cancel(); 1334 if (mChildrenContainer != null) { 1335 mChildrenContainer.animate().cancel(); 1336 mChildrenContainer.setAlpha(1f); 1337 } 1338 mPublicLayout.setAlpha(1f); 1339 mPrivateLayout.setAlpha(1f); 1340 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); 1341 updateChildrenVisibility(); 1342 } else { 1343 animateShowingPublic(delay, duration); 1344 } 1345 NotificationContentView showingLayout = getShowingLayout(); 1346 showingLayout.updateBackgroundColor(animated); 1347 mPrivateLayout.updateExpandButtons(isExpandable()); 1348 mShowingPublicInitialized = true; 1349 } 1350 1351 private void animateShowingPublic(long delay, long duration) { 1352 View[] privateViews = mIsSummaryWithChildren 1353 ? new View[] {mChildrenContainer} 1354 : new View[] {mPrivateLayout}; 1355 View[] publicViews = new View[] {mPublicLayout}; 1356 View[] hiddenChildren = mShowingPublic ? privateViews : publicViews; 1357 View[] shownChildren = mShowingPublic ? publicViews : privateViews; 1358 for (final View hiddenView : hiddenChildren) { 1359 hiddenView.setVisibility(View.VISIBLE); 1360 hiddenView.animate().cancel(); 1361 hiddenView.animate() 1362 .alpha(0f) 1363 .setStartDelay(delay) 1364 .setDuration(duration) 1365 .withEndAction(new Runnable() { 1366 @Override 1367 public void run() { 1368 hiddenView.setVisibility(View.INVISIBLE); 1369 } 1370 }); 1371 } 1372 for (View showView : shownChildren) { 1373 showView.setVisibility(View.VISIBLE); 1374 showView.setAlpha(0f); 1375 showView.animate().cancel(); 1376 showView.animate() 1377 .alpha(1f) 1378 .setStartDelay(delay) 1379 .setDuration(duration); 1380 } 1381 } 1382 1383 public boolean mustStayOnScreen() { 1384 return mIsHeadsUp; 1385 } 1386 1387 /** 1388 * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as 1389 * otherwise some state might not be updated. To request about the general clearability 1390 * see {@link #isClearable()}. 1391 */ 1392 public boolean canViewBeDismissed() { 1393 return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral); 1394 } 1395 1396 public void makeActionsVisibile() { 1397 setUserExpanded(true, true); 1398 if (isChildInGroup()) { 1399 mGroupManager.setGroupExpanded(mStatusBarNotification, true); 1400 } 1401 notifyHeightChanged(false); 1402 } 1403 1404 public void setChildrenExpanded(boolean expanded, boolean animate) { 1405 mChildrenExpanded = expanded; 1406 if (mChildrenContainer != null) { 1407 mChildrenContainer.setChildrenExpanded(expanded); 1408 } 1409 updateBackgroundForGroupState(); 1410 updateClickAndFocus(); 1411 } 1412 1413 public static void applyTint(View v, int color) { 1414 int alpha; 1415 if (color != 0) { 1416 alpha = COLORED_DIVIDER_ALPHA; 1417 } else { 1418 color = 0xff000000; 1419 alpha = DEFAULT_DIVIDER_ALPHA; 1420 } 1421 if (v.getBackground() instanceof ColorDrawable) { 1422 ColorDrawable background = (ColorDrawable) v.getBackground(); 1423 background.mutate(); 1424 background.setColor(color); 1425 background.setAlpha(alpha); 1426 } 1427 } 1428 1429 public int getMaxExpandHeight() { 1430 return mMaxExpandHeight; 1431 } 1432 1433 public boolean areGutsExposed() { 1434 return (mGuts != null && mGuts.areGutsExposed()); 1435 } 1436 1437 @Override 1438 public boolean isContentExpandable() { 1439 NotificationContentView showingLayout = getShowingLayout(); 1440 return showingLayout.isContentExpandable(); 1441 } 1442 1443 @Override 1444 protected View getContentView() { 1445 if (mIsSummaryWithChildren && !mShowingPublic) { 1446 return mChildrenContainer; 1447 } 1448 return getShowingLayout(); 1449 } 1450 1451 @Override 1452 protected void onAppearAnimationFinished(boolean wasAppearing) { 1453 super.onAppearAnimationFinished(wasAppearing); 1454 if (wasAppearing) { 1455 // During the animation the visible view might have changed, so let's make sure all 1456 // alphas are reset 1457 if (mChildrenContainer != null) { 1458 mChildrenContainer.setAlpha(1.0f); 1459 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null); 1460 } 1461 mPrivateLayout.setAlpha(1.0f); 1462 mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null); 1463 mPublicLayout.setAlpha(1.0f); 1464 mPublicLayout.setLayerType(LAYER_TYPE_NONE, null); 1465 } 1466 } 1467 1468 @Override 1469 public int getExtraBottomPadding() { 1470 if (mIsSummaryWithChildren && isGroupExpanded()) { 1471 return mIncreasedPaddingBetweenElements; 1472 } 1473 return 0; 1474 } 1475 1476 @Override 1477 public void setActualHeight(int height, boolean notifyListeners) { 1478 super.setActualHeight(height, notifyListeners); 1479 if (mGuts != null && mGuts.areGutsExposed()) { 1480 mGuts.setActualHeight(height); 1481 return; 1482 } 1483 int contentHeight = Math.max(getMinHeight(), height); 1484 mPrivateLayout.setContentHeight(contentHeight); 1485 mPublicLayout.setContentHeight(contentHeight); 1486 if (mIsSummaryWithChildren) { 1487 mChildrenContainer.setActualHeight(height); 1488 } 1489 if (mGuts != null) { 1490 mGuts.setActualHeight(height); 1491 } 1492 } 1493 1494 @Override 1495 public int getMaxContentHeight() { 1496 if (mIsSummaryWithChildren && !mShowingPublic) { 1497 return mChildrenContainer.getMaxContentHeight(); 1498 } 1499 NotificationContentView showingLayout = getShowingLayout(); 1500 return showingLayout.getMaxHeight(); 1501 } 1502 1503 @Override 1504 public int getMinHeight() { 1505 if (mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) { 1506 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); 1507 } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) { 1508 return mChildrenContainer.getMinHeight(); 1509 } else if (mIsHeadsUp) { 1510 return mHeadsUpHeight; 1511 } 1512 NotificationContentView showingLayout = getShowingLayout(); 1513 return showingLayout.getMinHeight(); 1514 } 1515 1516 @Override 1517 public int getCollapsedHeight() { 1518 if (mIsSummaryWithChildren && !mShowingPublic) { 1519 return mChildrenContainer.getCollapsedHeight(); 1520 } 1521 return getMinHeight(); 1522 } 1523 1524 @Override 1525 public void setClipTopAmount(int clipTopAmount) { 1526 super.setClipTopAmount(clipTopAmount); 1527 mPrivateLayout.setClipTopAmount(clipTopAmount); 1528 mPublicLayout.setClipTopAmount(clipTopAmount); 1529 if (mGuts != null) { 1530 mGuts.setClipTopAmount(clipTopAmount); 1531 } 1532 } 1533 1534 public boolean isMaxExpandHeightInitialized() { 1535 return mMaxExpandHeight != 0; 1536 } 1537 1538 public NotificationContentView getShowingLayout() { 1539 return mShowingPublic ? mPublicLayout : mPrivateLayout; 1540 } 1541 1542 @Override 1543 public void setShowingLegacyBackground(boolean showing) { 1544 super.setShowingLegacyBackground(showing); 1545 mPrivateLayout.setShowingLegacyBackground(showing); 1546 mPublicLayout.setShowingLegacyBackground(showing); 1547 } 1548 1549 @Override 1550 protected void updateBackgroundTint() { 1551 super.updateBackgroundTint(); 1552 updateBackgroundForGroupState(); 1553 if (mIsSummaryWithChildren) { 1554 List<ExpandableNotificationRow> notificationChildren = 1555 mChildrenContainer.getNotificationChildren(); 1556 for (int i = 0; i < notificationChildren.size(); i++) { 1557 ExpandableNotificationRow child = notificationChildren.get(i); 1558 child.updateBackgroundForGroupState(); 1559 } 1560 } 1561 } 1562 1563 /** 1564 * Called when a group has finished animating from collapsed or expanded state. 1565 */ 1566 public void onFinishedExpansionChange() { 1567 mGroupExpansionChanging = false; 1568 updateBackgroundForGroupState(); 1569 } 1570 1571 /** 1572 * Updates the parent and children backgrounds in a group based on the expansion state. 1573 */ 1574 public void updateBackgroundForGroupState() { 1575 if (mIsSummaryWithChildren) { 1576 // Only when the group has finished expanding do we hide its background. 1577 mShowNoBackground = isGroupExpanded() && !isGroupExpansionChanging() && !isUserLocked(); 1578 mChildrenContainer.updateHeaderForExpansion(mShowNoBackground); 1579 List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren(); 1580 for (int i = 0; i < children.size(); i++) { 1581 children.get(i).updateBackgroundForGroupState(); 1582 } 1583 } else if (isChildInGroup()) { 1584 final int childColor = getShowingLayout().getBackgroundColorForExpansionState(); 1585 // Only show a background if the group is expanded OR if it is expanding / collapsing 1586 // and has a custom background color 1587 final boolean showBackground = isGroupExpanded() 1588 || ((mNotificationParent.isGroupExpansionChanging() 1589 || mNotificationParent.isUserLocked()) && childColor != 0); 1590 mShowNoBackground = !showBackground; 1591 } else { 1592 // Only children or parents ever need no background. 1593 mShowNoBackground = false; 1594 } 1595 updateOutline(); 1596 updateBackground(); 1597 } 1598 1599 public int getPositionOfChild(ExpandableNotificationRow childRow) { 1600 if (mIsSummaryWithChildren) { 1601 return mChildrenContainer.getPositionInLinearLayout(childRow); 1602 } 1603 return 0; 1604 } 1605 1606 public void setExpansionLogger(ExpansionLogger logger, String key) { 1607 mLogger = logger; 1608 mLoggingKey = key; 1609 } 1610 1611 public void onExpandedByGesture(boolean userExpanded) { 1612 int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; 1613 if (mGroupManager.isSummaryOfGroup(getStatusBarNotification())) { 1614 event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER; 1615 } 1616 MetricsLogger.action(mContext, event, userExpanded); 1617 } 1618 1619 @Override 1620 public float getIncreasedPaddingAmount() { 1621 if (mIsSummaryWithChildren) { 1622 if (isGroupExpanded()) { 1623 return 1.0f; 1624 } else if (isUserLocked()) { 1625 return mChildrenContainer.getGroupExpandFraction(); 1626 } 1627 } 1628 return 0.0f; 1629 } 1630 1631 @Override 1632 protected boolean disallowSingleClick(MotionEvent event) { 1633 float x = event.getX(); 1634 float y = event.getY(); 1635 NotificationHeaderView header = getVisibleNotificationHeader(); 1636 if (header != null) { 1637 return header.isInTouchRect(x - getTranslation(), y); 1638 } 1639 return super.disallowSingleClick(event); 1640 } 1641 1642 private void logExpansionEvent(boolean userAction, boolean wasExpanded) { 1643 boolean nowExpanded = isExpanded(); 1644 if (mIsSummaryWithChildren) { 1645 nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); 1646 } 1647 if (wasExpanded != nowExpanded && mLogger != null) { 1648 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ; 1649 } 1650 } 1651 1652 @Override 1653 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 1654 super.onInitializeAccessibilityNodeInfoInternal(info); 1655 if (canViewBeDismissed()) { 1656 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); 1657 } 1658 } 1659 1660 @Override 1661 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 1662 if (super.performAccessibilityActionInternal(action, arguments)) { 1663 return true; 1664 } 1665 switch (action) { 1666 case AccessibilityNodeInfo.ACTION_DISMISS: 1667 NotificationStackScrollLayout.performDismiss(this, mGroupManager, 1668 true /* fromAccessibility */); 1669 return true; 1670 } 1671 return false; 1672 } 1673 1674 public boolean shouldRefocusOnDismiss() { 1675 return mRefocusOnDismiss || isAccessibilityFocused(); 1676 } 1677 1678 public interface OnExpandClickListener { 1679 void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded); 1680 } 1681 } 1682