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