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 static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.ObjectAnimator; 24 import android.animation.ValueAnimator.AnimatorUpdateListener; 25 import android.annotation.Nullable; 26 import android.content.Context; 27 import android.content.res.Resources; 28 import android.content.res.Configuration; 29 import android.graphics.drawable.AnimatedVectorDrawable; 30 import android.graphics.drawable.AnimationDrawable; 31 import android.graphics.drawable.ColorDrawable; 32 import android.graphics.drawable.Drawable; 33 import android.os.Build; 34 import android.os.Bundle; 35 import android.service.notification.StatusBarNotification; 36 import android.util.AttributeSet; 37 import android.util.FloatProperty; 38 import android.util.Property; 39 import android.view.LayoutInflater; 40 import android.view.MotionEvent; 41 import android.view.NotificationHeaderView; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.view.ViewStub; 45 import android.view.accessibility.AccessibilityEvent; 46 import android.view.accessibility.AccessibilityNodeInfo; 47 import android.widget.Chronometer; 48 import android.widget.FrameLayout; 49 import android.widget.ImageView; 50 import android.widget.RemoteViews; 51 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.logging.MetricsLogger; 54 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 55 import com.android.internal.util.NotificationColorUtil; 56 import com.android.internal.widget.CachingIconView; 57 import com.android.systemui.Dependency; 58 import com.android.systemui.Interpolators; 59 import com.android.systemui.R; 60 import com.android.systemui.classifier.FalsingManager; 61 import com.android.systemui.plugins.PluginListener; 62 import com.android.systemui.plugins.PluginManager; 63 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 64 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; 65 import com.android.systemui.statusbar.NotificationGuts.GutsContent; 66 import com.android.systemui.statusbar.notification.AboveShelfChangedListener; 67 import com.android.systemui.statusbar.notification.AboveShelfObserver; 68 import com.android.systemui.statusbar.notification.HybridNotificationView; 69 import com.android.systemui.statusbar.notification.NotificationInflater; 70 import com.android.systemui.statusbar.notification.NotificationUtils; 71 import com.android.systemui.statusbar.notification.VisualStabilityManager; 72 import com.android.systemui.statusbar.phone.NotificationGroupManager; 73 import com.android.systemui.statusbar.phone.StatusBar; 74 import com.android.systemui.statusbar.policy.HeadsUpManager; 75 import com.android.systemui.statusbar.stack.AnimationProperties; 76 import com.android.systemui.statusbar.stack.ExpandableViewState; 77 import com.android.systemui.statusbar.stack.NotificationChildrenContainer; 78 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 79 import com.android.systemui.statusbar.stack.StackScrollState; 80 81 import java.util.ArrayList; 82 import java.util.List; 83 import java.util.function.BooleanSupplier; 84 85 public class ExpandableNotificationRow extends ActivatableNotificationView 86 implements PluginListener<NotificationMenuRowPlugin> { 87 88 private static final int DEFAULT_DIVIDER_ALPHA = 0x29; 89 private static final int COLORED_DIVIDER_ALPHA = 0x7B; 90 private static final int MENU_VIEW_INDEX = 0; 91 92 public interface LayoutListener { 93 public void onLayout(); 94 } 95 96 private LayoutListener mLayoutListener; 97 private boolean mDark; 98 private boolean mLowPriorityStateUpdated; 99 private final NotificationInflater mNotificationInflater; 100 private int mIconTransformContentShift; 101 private int mIconTransformContentShiftNoIcon; 102 private int mNotificationMinHeightLegacy; 103 private int mMaxHeadsUpHeightLegacy; 104 private int mMaxHeadsUpHeight; 105 private int mMaxHeadsUpHeightIncreased; 106 private int mNotificationMinHeight; 107 private int mNotificationMinHeightLarge; 108 private int mNotificationMaxHeight; 109 private int mNotificationAmbientHeight; 110 private int mIncreasedPaddingBetweenElements; 111 112 /** Does this row contain layouts that can adapt to row expansion */ 113 private boolean mExpandable; 114 /** Has the user actively changed the expansion state of this row */ 115 private boolean mHasUserChangedExpansion; 116 /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ 117 private boolean mUserExpanded; 118 119 /** 120 * Has this notification been expanded while it was pinned 121 */ 122 private boolean mExpandedWhenPinned; 123 /** Is the user touching this row */ 124 private boolean mUserLocked; 125 /** Are we showing the "public" version */ 126 private boolean mShowingPublic; 127 private boolean mSensitive; 128 private boolean mSensitiveHiddenInGeneral; 129 private boolean mShowingPublicInitialized; 130 private boolean mHideSensitiveForIntrinsicHeight; 131 132 /** 133 * Is this notification expanded by the system. The expansion state can be overridden by the 134 * user expansion. 135 */ 136 private boolean mIsSystemExpanded; 137 138 /** 139 * Whether the notification is on the keyguard and the expansion is disabled. 140 */ 141 private boolean mOnKeyguard; 142 143 private Animator mTranslateAnim; 144 private ArrayList<View> mTranslateableViews; 145 private NotificationContentView mPublicLayout; 146 private NotificationContentView mPrivateLayout; 147 private NotificationContentView[] mLayouts; 148 private int mMaxExpandHeight; 149 private int mHeadsUpHeight; 150 private int mNotificationColor; 151 private ExpansionLogger mLogger; 152 private String mLoggingKey; 153 private NotificationGuts mGuts; 154 private NotificationData.Entry mEntry; 155 private StatusBarNotification mStatusBarNotification; 156 private String mAppName; 157 private boolean mIsHeadsUp; 158 private boolean mLastChronometerRunning = true; 159 private ViewStub mChildrenContainerStub; 160 private NotificationGroupManager mGroupManager; 161 private boolean mChildrenExpanded; 162 private boolean mIsSummaryWithChildren; 163 private NotificationChildrenContainer mChildrenContainer; 164 private NotificationMenuRowPlugin mMenuRow; 165 private ViewStub mGutsStub; 166 private boolean mIsSystemChildExpanded; 167 private boolean mIsPinned; 168 private FalsingManager mFalsingManager; 169 private AboveShelfChangedListener mAboveShelfChangedListener; 170 private HeadsUpManager mHeadsUpManager; 171 172 private boolean mJustClicked; 173 private boolean mIconAnimationRunning; 174 private boolean mShowNoBackground; 175 private ExpandableNotificationRow mNotificationParent; 176 private OnExpandClickListener mOnExpandClickListener; 177 private boolean mGroupExpansionChanging; 178 179 /** 180 * A supplier that returns true if keyguard is secure. 181 */ 182 private BooleanSupplier mSecureStateProvider; 183 184 /** 185 * Whether or not a notification that is not part of a group of notifications can be manually 186 * expanded by the user. 187 */ 188 private boolean mEnableNonGroupedNotificationExpand; 189 190 /** 191 * Whether or not to update the background of the header of the notification when its expanded. 192 * If {@code true}, the header background will disappear when expanded. 193 */ 194 private boolean mShowGroupBackgroundWhenExpanded; 195 196 private OnClickListener mExpandClickListener = new OnClickListener() { 197 @Override 198 public void onClick(View v) { 199 if (!mShowingPublic && (!mIsLowPriority || isExpanded()) 200 && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) { 201 mGroupExpansionChanging = true; 202 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); 203 boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification); 204 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded); 205 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, 206 nowExpanded); 207 onExpansionChanged(true /* userAction */, wasExpanded); 208 } else if (mEnableNonGroupedNotificationExpand) { 209 if (v.isAccessibilityFocused()) { 210 mPrivateLayout.setFocusOnVisibilityChange(); 211 } 212 boolean nowExpanded; 213 if (isPinned()) { 214 nowExpanded = !mExpandedWhenPinned; 215 mExpandedWhenPinned = nowExpanded; 216 } else { 217 nowExpanded = !isExpanded(); 218 setUserExpanded(nowExpanded); 219 } 220 notifyHeightChanged(true); 221 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded); 222 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_EXPANDER, 223 nowExpanded); 224 } 225 } 226 }; 227 private boolean mForceUnlocked; 228 private boolean mDismissed; 229 private boolean mKeepInParent; 230 private boolean mRemoved; 231 private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT = 232 new FloatProperty<ExpandableNotificationRow>("translate") { 233 @Override 234 public void setValue(ExpandableNotificationRow object, float value) { 235 object.setTranslation(value); 236 } 237 238 @Override 239 public Float get(ExpandableNotificationRow object) { 240 return object.getTranslation(); 241 } 242 }; 243 private OnClickListener mOnClickListener; 244 private boolean mHeadsupDisappearRunning; 245 private View mChildAfterViewWhenDismissed; 246 private View mGroupParentWhenDismissed; 247 private boolean mRefocusOnDismiss; 248 private float mContentTransformationAmount; 249 private boolean mIconsVisible = true; 250 private boolean mAboveShelf; 251 private boolean mShowAmbient; 252 private boolean mIsLastChild; 253 private Runnable mOnDismissRunnable; 254 private boolean mIsLowPriority; 255 private boolean mIsColorized; 256 private boolean mUseIncreasedCollapsedHeight; 257 private boolean mUseIncreasedHeadsUpHeight; 258 private float mTranslationWhenRemoved; 259 private boolean mWasChildInGroupWhenRemoved; 260 private int mNotificationColorAmbient; 261 262 @Override 263 public boolean isGroupExpansionChanging() { 264 if (isChildInGroup()) { 265 return mNotificationParent.isGroupExpansionChanging(); 266 } 267 return mGroupExpansionChanging; 268 } 269 270 public void setGroupExpansionChanging(boolean changing) { 271 mGroupExpansionChanging = changing; 272 } 273 274 @Override 275 public void setActualHeightAnimating(boolean animating) { 276 if (mPrivateLayout != null) { 277 mPrivateLayout.setContentHeightAnimating(animating); 278 } 279 } 280 281 public NotificationContentView getPrivateLayout() { 282 return mPrivateLayout; 283 } 284 285 public NotificationContentView getPublicLayout() { 286 return mPublicLayout; 287 } 288 289 public void setIconAnimationRunning(boolean running) { 290 for (NotificationContentView l : mLayouts) { 291 setIconAnimationRunning(running, l); 292 } 293 if (mIsSummaryWithChildren) { 294 setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView()); 295 setIconAnimationRunningForChild(running, mChildrenContainer.getLowPriorityHeaderView()); 296 List<ExpandableNotificationRow> notificationChildren = 297 mChildrenContainer.getNotificationChildren(); 298 for (int i = 0; i < notificationChildren.size(); i++) { 299 ExpandableNotificationRow child = notificationChildren.get(i); 300 child.setIconAnimationRunning(running); 301 } 302 } 303 mIconAnimationRunning = running; 304 } 305 306 private void setIconAnimationRunning(boolean running, NotificationContentView layout) { 307 if (layout != null) { 308 View contractedChild = layout.getContractedChild(); 309 View expandedChild = layout.getExpandedChild(); 310 View headsUpChild = layout.getHeadsUpChild(); 311 setIconAnimationRunningForChild(running, contractedChild); 312 setIconAnimationRunningForChild(running, expandedChild); 313 setIconAnimationRunningForChild(running, headsUpChild); 314 } 315 } 316 317 private void setIconAnimationRunningForChild(boolean running, View child) { 318 if (child != null) { 319 ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon); 320 setIconRunning(icon, running); 321 ImageView rightIcon = (ImageView) child.findViewById( 322 com.android.internal.R.id.right_icon); 323 setIconRunning(rightIcon, running); 324 } 325 } 326 327 private void setIconRunning(ImageView imageView, boolean running) { 328 if (imageView != null) { 329 Drawable drawable = imageView.getDrawable(); 330 if (drawable instanceof AnimationDrawable) { 331 AnimationDrawable animationDrawable = (AnimationDrawable) drawable; 332 if (running) { 333 animationDrawable.start(); 334 } else { 335 animationDrawable.stop(); 336 } 337 } else if (drawable instanceof AnimatedVectorDrawable) { 338 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable; 339 if (running) { 340 animationDrawable.start(); 341 } else { 342 animationDrawable.stop(); 343 } 344 } 345 } 346 } 347 348 public void updateNotification(NotificationData.Entry entry) { 349 mEntry = entry; 350 mStatusBarNotification = entry.notification; 351 mNotificationInflater.inflateNotificationViews(); 352 } 353 354 public void onNotificationUpdated() { 355 for (NotificationContentView l : mLayouts) { 356 l.onNotificationUpdated(mEntry); 357 } 358 mIsColorized = mStatusBarNotification.getNotification().isColorized(); 359 mShowingPublicInitialized = false; 360 updateNotificationColor(); 361 if (mMenuRow != null) { 362 mMenuRow.onNotificationUpdated(mStatusBarNotification); 363 } 364 if (mIsSummaryWithChildren) { 365 mChildrenContainer.recreateNotificationHeader(mExpandClickListener); 366 mChildrenContainer.onNotificationUpdated(); 367 } 368 if (mIconAnimationRunning) { 369 setIconAnimationRunning(true); 370 } 371 if (mNotificationParent != null) { 372 mNotificationParent.updateChildrenHeaderAppearance(); 373 } 374 onChildrenCountChanged(); 375 // The public layouts expand button is always visible 376 mPublicLayout.updateExpandButtons(true); 377 updateLimits(); 378 updateIconVisibilities(); 379 updateShelfIconColor(); 380 } 381 382 @VisibleForTesting 383 void updateShelfIconColor() { 384 StatusBarIconView expandedIcon = mEntry.expandedIcon; 385 boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L)); 386 boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon, 387 NotificationColorUtil.getInstance(mContext)); 388 int color = StatusBarIconView.NO_COLOR; 389 if (colorize) { 390 NotificationHeaderView header = getVisibleNotificationHeader(); 391 if (header != null) { 392 color = header.getOriginalIconColor(); 393 } else { 394 color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(), 395 getBackgroundColorWithoutTint()); 396 } 397 } 398 expandedIcon.setStaticDrawableColor(color); 399 } 400 401 public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) { 402 mAboveShelfChangedListener = aboveShelfChangedListener; 403 } 404 405 /** 406 * Sets a supplier that can determine whether the keyguard is secure or not. 407 * @param secureStateProvider A function that returns true if keyguard is secure. 408 */ 409 public void setSecureStateProvider(BooleanSupplier secureStateProvider) { 410 mSecureStateProvider = secureStateProvider; 411 } 412 413 @Override 414 public boolean isDimmable() { 415 if (!getShowingLayout().isDimmable()) { 416 return false; 417 } 418 return super.isDimmable(); 419 } 420 421 private void updateLimits() { 422 for (NotificationContentView l : mLayouts) { 423 updateLimitsForView(l); 424 } 425 } 426 427 private void updateLimitsForView(NotificationContentView layout) { 428 boolean customView = layout.getContractedChild().getId() 429 != com.android.internal.R.id.status_bar_latest_event_content; 430 boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; 431 int minHeight; 432 if (customView && beforeN && !mIsSummaryWithChildren) { 433 minHeight = mNotificationMinHeightLegacy; 434 } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) { 435 minHeight = mNotificationMinHeightLarge; 436 } else { 437 minHeight = mNotificationMinHeight; 438 } 439 boolean headsUpCustom = layout.getHeadsUpChild() != null && 440 layout.getHeadsUpChild().getId() 441 != com.android.internal.R.id.status_bar_latest_event_content; 442 int headsUpheight; 443 if (headsUpCustom && beforeN) { 444 headsUpheight = mMaxHeadsUpHeightLegacy; 445 } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) { 446 headsUpheight = mMaxHeadsUpHeightIncreased; 447 } else { 448 headsUpheight = mMaxHeadsUpHeight; 449 } 450 layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight, 451 mNotificationAmbientHeight); 452 } 453 454 public StatusBarNotification getStatusBarNotification() { 455 return mStatusBarNotification; 456 } 457 458 public NotificationData.Entry getEntry() { 459 return mEntry; 460 } 461 462 public boolean isHeadsUp() { 463 return mIsHeadsUp; 464 } 465 466 public void setHeadsUp(boolean isHeadsUp) { 467 boolean wasAboveShelf = isAboveShelf(); 468 int intrinsicBefore = getIntrinsicHeight(); 469 mIsHeadsUp = isHeadsUp; 470 mPrivateLayout.setHeadsUp(isHeadsUp); 471 if (mIsSummaryWithChildren) { 472 // The overflow might change since we allow more lines as HUN. 473 mChildrenContainer.updateGroupOverflow(); 474 } 475 if (intrinsicBefore != getIntrinsicHeight()) { 476 notifyHeightChanged(false /* needsAnimation */); 477 } 478 if (isHeadsUp) { 479 setAboveShelf(true); 480 } else if (isAboveShelf() != wasAboveShelf) { 481 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 482 } 483 } 484 485 public void setGroupManager(NotificationGroupManager groupManager) { 486 mGroupManager = groupManager; 487 mPrivateLayout.setGroupManager(groupManager); 488 } 489 490 public void setRemoteInputController(RemoteInputController r) { 491 mPrivateLayout.setRemoteInputController(r); 492 } 493 494 public void setAppName(String appName) { 495 mAppName = appName; 496 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 497 mMenuRow.setAppName(mAppName); 498 } 499 } 500 501 public void addChildNotification(ExpandableNotificationRow row) { 502 addChildNotification(row, -1); 503 } 504 505 /** 506 * Add a child notification to this view. 507 * 508 * @param row the row to add 509 * @param childIndex the index to add it at, if -1 it will be added at the end 510 */ 511 public void addChildNotification(ExpandableNotificationRow row, int childIndex) { 512 if (mChildrenContainer == null) { 513 mChildrenContainerStub.inflate(); 514 } 515 mChildrenContainer.addNotification(row, childIndex); 516 onChildrenCountChanged(); 517 row.setIsChildInGroup(true, this); 518 } 519 520 public void removeChildNotification(ExpandableNotificationRow row) { 521 if (mChildrenContainer != null) { 522 mChildrenContainer.removeNotification(row); 523 } 524 onChildrenCountChanged(); 525 row.setIsChildInGroup(false, null); 526 } 527 528 @Override 529 public boolean isChildInGroup() { 530 return mNotificationParent != null; 531 } 532 533 public ExpandableNotificationRow getNotificationParent() { 534 return mNotificationParent; 535 } 536 537 /** 538 * @param isChildInGroup Is this notification now in a group 539 * @param parent the new parent notification 540 */ 541 public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {; 542 boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup; 543 mNotificationParent = childInGroup ? parent : null; 544 mPrivateLayout.setIsChildInGroup(childInGroup); 545 mNotificationInflater.setIsChildInGroup(childInGroup); 546 resetBackgroundAlpha(); 547 updateBackgroundForGroupState(); 548 updateClickAndFocus(); 549 if (mNotificationParent != null) { 550 setOverrideTintColor(NO_COLOR, 0.0f); 551 mNotificationParent.updateBackgroundForGroupState(); 552 } 553 updateIconVisibilities(); 554 } 555 556 @Override 557 public boolean onTouchEvent(MotionEvent event) { 558 if (event.getActionMasked() != MotionEvent.ACTION_DOWN 559 || !isChildInGroup() || isGroupExpanded()) { 560 return super.onTouchEvent(event); 561 } else { 562 return false; 563 } 564 } 565 566 @Override 567 protected boolean handleSlideBack() { 568 if (mMenuRow != null && mMenuRow.isMenuVisible()) { 569 animateTranslateNotification(0 /* targetLeft */); 570 return true; 571 } 572 return false; 573 } 574 575 @Override 576 protected boolean shouldHideBackground() { 577 return super.shouldHideBackground() || mShowNoBackground; 578 } 579 580 @Override 581 public boolean isSummaryWithChildren() { 582 return mIsSummaryWithChildren; 583 } 584 585 @Override 586 public boolean areChildrenExpanded() { 587 return mChildrenExpanded; 588 } 589 590 public List<ExpandableNotificationRow> getNotificationChildren() { 591 return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); 592 } 593 594 public int getNumberOfNotificationChildren() { 595 if (mChildrenContainer == null) { 596 return 0; 597 } 598 return mChildrenContainer.getNotificationChildren().size(); 599 } 600 601 /** 602 * Apply the order given in the list to the children. 603 * 604 * @param childOrder the new list order 605 * @param visualStabilityManager 606 * @param callback the callback to invoked in case it is not allowed 607 * @return whether the list order has changed 608 */ 609 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder, 610 VisualStabilityManager visualStabilityManager, 611 VisualStabilityManager.Callback callback) { 612 return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder, 613 visualStabilityManager, callback); 614 } 615 616 public void getChildrenStates(StackScrollState resultState) { 617 if (mIsSummaryWithChildren) { 618 ExpandableViewState parentState = resultState.getViewStateForView(this); 619 mChildrenContainer.getState(resultState, parentState); 620 } 621 } 622 623 public void applyChildrenState(StackScrollState state) { 624 if (mIsSummaryWithChildren) { 625 mChildrenContainer.applyState(state); 626 } 627 } 628 629 public void prepareExpansionChanged(StackScrollState state) { 630 if (mIsSummaryWithChildren) { 631 mChildrenContainer.prepareExpansionChanged(state); 632 } 633 } 634 635 public void startChildAnimation(StackScrollState finalState, AnimationProperties properties) { 636 if (mIsSummaryWithChildren) { 637 mChildrenContainer.startAnimationToState(finalState, properties); 638 } 639 } 640 641 public ExpandableNotificationRow getViewAtPosition(float y) { 642 if (!mIsSummaryWithChildren || !mChildrenExpanded) { 643 return this; 644 } else { 645 ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); 646 return view == null ? this : view; 647 } 648 } 649 650 public NotificationGuts getGuts() { 651 return mGuts; 652 } 653 654 /** 655 * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this 656 * the notification will be rendered on top of the screen. 657 * 658 * @param pinned whether it is pinned 659 */ 660 public void setPinned(boolean pinned) { 661 int intrinsicHeight = getIntrinsicHeight(); 662 boolean wasAboveShelf = isAboveShelf(); 663 mIsPinned = pinned; 664 if (intrinsicHeight != getIntrinsicHeight()) { 665 notifyHeightChanged(false /* needsAnimation */); 666 } 667 if (pinned) { 668 setIconAnimationRunning(true); 669 mExpandedWhenPinned = false; 670 } else if (mExpandedWhenPinned) { 671 setUserExpanded(true); 672 } 673 setChronometerRunning(mLastChronometerRunning); 674 if (isAboveShelf() != wasAboveShelf) { 675 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 676 } 677 } 678 679 public boolean isPinned() { 680 return mIsPinned; 681 } 682 683 @Override 684 public int getPinnedHeadsUpHeight() { 685 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); 686 } 687 688 /** 689 * @param atLeastMinHeight should the value returned be at least the minimum height. 690 * Used to avoid cyclic calls 691 * @return the height of the heads up notification when pinned 692 */ 693 private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) { 694 if (mIsSummaryWithChildren) { 695 return mChildrenContainer.getIntrinsicHeight(); 696 } 697 if(mExpandedWhenPinned) { 698 return Math.max(getMaxExpandHeight(), mHeadsUpHeight); 699 } else if (atLeastMinHeight) { 700 return Math.max(getCollapsedHeight(), mHeadsUpHeight); 701 } else { 702 return mHeadsUpHeight; 703 } 704 } 705 706 /** 707 * Mark whether this notification was just clicked, i.e. the user has just clicked this 708 * notification in this frame. 709 */ 710 public void setJustClicked(boolean justClicked) { 711 mJustClicked = justClicked; 712 } 713 714 /** 715 * @return true if this notification has been clicked in this frame, false otherwise 716 */ 717 public boolean wasJustClicked() { 718 return mJustClicked; 719 } 720 721 public void setChronometerRunning(boolean running) { 722 mLastChronometerRunning = running; 723 setChronometerRunning(running, mPrivateLayout); 724 setChronometerRunning(running, mPublicLayout); 725 if (mChildrenContainer != null) { 726 List<ExpandableNotificationRow> notificationChildren = 727 mChildrenContainer.getNotificationChildren(); 728 for (int i = 0; i < notificationChildren.size(); i++) { 729 ExpandableNotificationRow child = notificationChildren.get(i); 730 child.setChronometerRunning(running); 731 } 732 } 733 } 734 735 private void setChronometerRunning(boolean running, NotificationContentView layout) { 736 if (layout != null) { 737 running = running || isPinned(); 738 View contractedChild = layout.getContractedChild(); 739 View expandedChild = layout.getExpandedChild(); 740 View headsUpChild = layout.getHeadsUpChild(); 741 setChronometerRunningForChild(running, contractedChild); 742 setChronometerRunningForChild(running, expandedChild); 743 setChronometerRunningForChild(running, headsUpChild); 744 } 745 } 746 747 private void setChronometerRunningForChild(boolean running, View child) { 748 if (child != null) { 749 View chronometer = child.findViewById(com.android.internal.R.id.chronometer); 750 if (chronometer instanceof Chronometer) { 751 ((Chronometer) chronometer).setStarted(running); 752 } 753 } 754 } 755 756 public NotificationHeaderView getNotificationHeader() { 757 if (mIsSummaryWithChildren) { 758 return mChildrenContainer.getHeaderView(); 759 } 760 return mPrivateLayout.getNotificationHeader(); 761 } 762 763 /** 764 * @return the currently visible notification header. This can be different from 765 * {@link #getNotificationHeader()} in case it is a low-priority group. 766 */ 767 public NotificationHeaderView getVisibleNotificationHeader() { 768 if (mIsSummaryWithChildren && !mShowingPublic) { 769 return mChildrenContainer.getVisibleHeader(); 770 } 771 return getShowingLayout().getVisibleNotificationHeader(); 772 } 773 774 775 /** 776 * @return the contracted notification header. This can be different from 777 * {@link #getNotificationHeader()} and also {@link #getVisibleNotificationHeader()} and only 778 * returns the contracted version. 779 */ 780 public NotificationHeaderView getContractedNotificationHeader() { 781 if (mIsSummaryWithChildren) { 782 return mChildrenContainer.getHeaderView(); 783 } 784 return mPrivateLayout.getContractedNotificationHeader(); 785 } 786 787 public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { 788 mOnExpandClickListener = onExpandClickListener; 789 } 790 791 @Override 792 public void setOnClickListener(@Nullable OnClickListener l) { 793 super.setOnClickListener(l); 794 mOnClickListener = l; 795 updateClickAndFocus(); 796 } 797 798 private void updateClickAndFocus() { 799 boolean normalChild = !isChildInGroup() || isGroupExpanded(); 800 boolean clickable = mOnClickListener != null && normalChild; 801 if (isFocusable() != normalChild) { 802 setFocusable(normalChild); 803 } 804 if (isClickable() != clickable) { 805 setClickable(clickable); 806 } 807 } 808 809 public void setHeadsUpManager(HeadsUpManager headsUpManager) { 810 mHeadsUpManager = headsUpManager; 811 } 812 813 public void setGutsView(MenuItem item) { 814 if (mGuts != null && item.getGutsView() instanceof GutsContent) { 815 ((GutsContent) item.getGutsView()).setGutsParent(mGuts); 816 mGuts.setGutsContent((GutsContent) item.getGutsView()); 817 } 818 } 819 820 @Override 821 protected void onAttachedToWindow() { 822 super.onAttachedToWindow(); 823 Dependency.get(PluginManager.class).addPluginListener(this, 824 NotificationMenuRowPlugin.class, false /* Allow multiple */); 825 } 826 827 @Override 828 protected void onDetachedFromWindow() { 829 super.onDetachedFromWindow(); 830 Dependency.get(PluginManager.class).removePluginListener(this); 831 } 832 833 @Override 834 public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { 835 boolean existed = mMenuRow.getMenuView() != null; 836 if (existed) { 837 removeView(mMenuRow.getMenuView()); 838 } 839 mMenuRow = plugin; 840 if (mMenuRow.useDefaultMenuItems()) { 841 ArrayList<MenuItem> items = new ArrayList<>(); 842 items.add(NotificationMenuRow.createInfoItem(mContext)); 843 items.add(NotificationMenuRow.createSnoozeItem(mContext)); 844 mMenuRow.setMenuItems(items); 845 } 846 if (existed) { 847 createMenu(); 848 } 849 } 850 851 @Override 852 public void onPluginDisconnected(NotificationMenuRowPlugin plugin) { 853 boolean existed = mMenuRow.getMenuView() != null; 854 mMenuRow = new NotificationMenuRow(mContext); // Back to default 855 if (existed) { 856 createMenu(); 857 } 858 } 859 860 public NotificationMenuRowPlugin createMenu() { 861 if (mMenuRow.getMenuView() == null) { 862 mMenuRow.createMenu(this, mStatusBarNotification); 863 mMenuRow.setAppName(mAppName); 864 FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 865 LayoutParams.MATCH_PARENT); 866 addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp); 867 } 868 return mMenuRow; 869 } 870 871 public NotificationMenuRowPlugin getProvider() { 872 return mMenuRow; 873 } 874 875 @Override 876 public void onDensityOrFontScaleChanged() { 877 super.onDensityOrFontScaleChanged(); 878 initDimens(); 879 initBackground(); 880 // Let's update our childrencontainer. This is intentionally not guarded with 881 // mIsSummaryWithChildren since we might have had children but not anymore. 882 if (mChildrenContainer != null) { 883 mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.notification); 884 } 885 if (mGuts != null) { 886 View oldGuts = mGuts; 887 int index = indexOfChild(oldGuts); 888 removeView(oldGuts); 889 mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate( 890 R.layout.notification_guts, this, false); 891 mGuts.setVisibility(oldGuts.getVisibility()); 892 addView(mGuts, index); 893 } 894 View oldMenu = mMenuRow.getMenuView(); 895 if (oldMenu != null) { 896 int menuIndex = indexOfChild(oldMenu); 897 removeView(oldMenu); 898 mMenuRow.createMenu(ExpandableNotificationRow.this, mStatusBarNotification); 899 mMenuRow.setAppName(mAppName); 900 addView(mMenuRow.getMenuView(), menuIndex); 901 } 902 for (NotificationContentView l : mLayouts) { 903 l.reInflateViews(); 904 } 905 mNotificationInflater.onDensityOrFontScaleChanged(); 906 onNotificationUpdated(); 907 } 908 909 @Override 910 public void onConfigurationChanged(Configuration newConfig) { 911 if (mMenuRow.getMenuView() != null) { 912 mMenuRow.onConfigurationChanged(); 913 } 914 } 915 916 public void setContentBackground(int customBackgroundColor, boolean animate, 917 NotificationContentView notificationContentView) { 918 if (getShowingLayout() == notificationContentView) { 919 setTintColor(customBackgroundColor, animate); 920 } 921 } 922 923 public void closeRemoteInput() { 924 for (NotificationContentView l : mLayouts) { 925 l.closeRemoteInput(); 926 } 927 } 928 929 /** 930 * Set by how much the single line view should be indented. 931 */ 932 public void setSingleLineWidthIndention(int indention) { 933 mPrivateLayout.setSingleLineWidthIndention(indention); 934 } 935 936 public int getNotificationColor() { 937 return mNotificationColor; 938 } 939 940 private void updateNotificationColor() { 941 mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext, 942 getStatusBarNotification().getNotification().color, 943 getBackgroundColorWithoutTint()); 944 mNotificationColorAmbient = NotificationColorUtil.resolveAmbientColor(mContext, 945 getStatusBarNotification().getNotification().color); 946 } 947 948 public HybridNotificationView getSingleLineView() { 949 return mPrivateLayout.getSingleLineView(); 950 } 951 952 public HybridNotificationView getAmbientSingleLineView() { 953 return getShowingLayout().getAmbientSingleLineChild(); 954 } 955 956 public boolean isOnKeyguard() { 957 return mOnKeyguard; 958 } 959 960 public void removeAllChildren() { 961 List<ExpandableNotificationRow> notificationChildren 962 = mChildrenContainer.getNotificationChildren(); 963 ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren); 964 for (int i = 0; i < clonedList.size(); i++) { 965 ExpandableNotificationRow row = clonedList.get(i); 966 if (row.keepInParent()) { 967 continue; 968 } 969 mChildrenContainer.removeNotification(row); 970 row.setIsChildInGroup(false, null); 971 } 972 onChildrenCountChanged(); 973 } 974 975 public void setForceUnlocked(boolean forceUnlocked) { 976 mForceUnlocked = forceUnlocked; 977 if (mIsSummaryWithChildren) { 978 List<ExpandableNotificationRow> notificationChildren = getNotificationChildren(); 979 for (ExpandableNotificationRow child : notificationChildren) { 980 child.setForceUnlocked(forceUnlocked); 981 } 982 } 983 } 984 985 public void setDismissed(boolean dismissed, boolean fromAccessibility) { 986 mDismissed = dismissed; 987 mGroupParentWhenDismissed = mNotificationParent; 988 mRefocusOnDismiss = fromAccessibility; 989 mChildAfterViewWhenDismissed = null; 990 if (isChildInGroup()) { 991 List<ExpandableNotificationRow> notificationChildren = 992 mNotificationParent.getNotificationChildren(); 993 int i = notificationChildren.indexOf(this); 994 if (i != -1 && i < notificationChildren.size() - 1) { 995 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1); 996 } 997 } 998 } 999 1000 public boolean isDismissed() { 1001 return mDismissed; 1002 } 1003 1004 public boolean keepInParent() { 1005 return mKeepInParent; 1006 } 1007 1008 public void setKeepInParent(boolean keepInParent) { 1009 mKeepInParent = keepInParent; 1010 } 1011 1012 public boolean isRemoved() { 1013 return mRemoved; 1014 } 1015 1016 public void setRemoved() { 1017 mRemoved = true; 1018 mTranslationWhenRemoved = getTranslationY(); 1019 mWasChildInGroupWhenRemoved = isChildInGroup(); 1020 if (isChildInGroup()) { 1021 mTranslationWhenRemoved += getNotificationParent().getTranslationY(); 1022 } 1023 mPrivateLayout.setRemoved(); 1024 } 1025 1026 public boolean wasChildInGroupWhenRemoved() { 1027 return mWasChildInGroupWhenRemoved; 1028 } 1029 1030 public float getTranslationWhenRemoved() { 1031 return mTranslationWhenRemoved; 1032 } 1033 1034 public NotificationChildrenContainer getChildrenContainer() { 1035 return mChildrenContainer; 1036 } 1037 1038 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { 1039 boolean wasAboveShelf = isAboveShelf(); 1040 mHeadsupDisappearRunning = headsUpAnimatingAway; 1041 mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway); 1042 if (isAboveShelf() != wasAboveShelf) { 1043 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 1044 } 1045 } 1046 1047 /** 1048 * @return if the view was just heads upped and is now animating away. During such a time the 1049 * layout needs to be kept consistent 1050 */ 1051 public boolean isHeadsUpAnimatingAway() { 1052 return mHeadsupDisappearRunning; 1053 } 1054 1055 public View getChildAfterViewWhenDismissed() { 1056 return mChildAfterViewWhenDismissed; 1057 } 1058 1059 public View getGroupParentWhenDismissed() { 1060 return mGroupParentWhenDismissed; 1061 } 1062 1063 public void performDismiss() { 1064 if (mOnDismissRunnable != null) { 1065 mOnDismissRunnable.run(); 1066 } 1067 } 1068 1069 public void setOnDismissRunnable(Runnable onDismissRunnable) { 1070 mOnDismissRunnable = onDismissRunnable; 1071 } 1072 1073 public View getNotificationIcon() { 1074 NotificationHeaderView notificationHeader = getVisibleNotificationHeader(); 1075 if (notificationHeader != null) { 1076 return notificationHeader.getIcon(); 1077 } 1078 return null; 1079 } 1080 1081 /** 1082 * @return whether the notification is currently showing a view with an icon. 1083 */ 1084 public boolean isShowingIcon() { 1085 if (areGutsExposed()) { 1086 return false; 1087 } 1088 return getVisibleNotificationHeader() != null; 1089 } 1090 1091 /** 1092 * Set how much this notification is transformed into an icon. 1093 * 1094 * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed 1095 * to the content away 1096 * @param isLastChild is this the last child in the list. If true, then the transformation is 1097 * different since it's content fades out. 1098 */ 1099 public void setContentTransformationAmount(float contentTransformationAmount, 1100 boolean isLastChild) { 1101 boolean changeTransformation = isLastChild != mIsLastChild; 1102 changeTransformation |= mContentTransformationAmount != contentTransformationAmount; 1103 mIsLastChild = isLastChild; 1104 mContentTransformationAmount = contentTransformationAmount; 1105 if (changeTransformation) { 1106 updateContentTransformation(); 1107 } 1108 } 1109 1110 /** 1111 * Set the icons to be visible of this notification. 1112 */ 1113 public void setIconsVisible(boolean iconsVisible) { 1114 if (iconsVisible != mIconsVisible) { 1115 mIconsVisible = iconsVisible; 1116 updateIconVisibilities(); 1117 } 1118 } 1119 1120 @Override 1121 protected void onBelowSpeedBumpChanged() { 1122 updateIconVisibilities(); 1123 } 1124 1125 private void updateContentTransformation() { 1126 float contentAlpha; 1127 float translationY = -mContentTransformationAmount * mIconTransformContentShift; 1128 if (mIsLastChild) { 1129 contentAlpha = 1.0f - mContentTransformationAmount; 1130 contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f); 1131 contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha); 1132 translationY *= 0.4f; 1133 } else { 1134 contentAlpha = 1.0f; 1135 } 1136 for (NotificationContentView l : mLayouts) { 1137 l.setAlpha(contentAlpha); 1138 l.setTranslationY(translationY); 1139 } 1140 if (mChildrenContainer != null) { 1141 mChildrenContainer.setAlpha(contentAlpha); 1142 mChildrenContainer.setTranslationY(translationY); 1143 // TODO: handle children fade out better 1144 } 1145 } 1146 1147 private void updateIconVisibilities() { 1148 boolean visible = isChildInGroup() 1149 || (isBelowSpeedBump() && !NotificationShelf.SHOW_AMBIENT_ICONS) 1150 || mIconsVisible; 1151 for (NotificationContentView l : mLayouts) { 1152 l.setIconsVisible(visible); 1153 } 1154 if (mChildrenContainer != null) { 1155 mChildrenContainer.setIconsVisible(visible); 1156 } 1157 } 1158 1159 /** 1160 * Get the relative top padding of a view relative to this view. This recursively walks up the 1161 * hierarchy and does the corresponding measuring. 1162 * 1163 * @param view the view to the the padding for. The requested view has to be a child of this 1164 * notification. 1165 * @return the toppadding 1166 */ 1167 public int getRelativeTopPadding(View view) { 1168 int topPadding = 0; 1169 while (view.getParent() instanceof ViewGroup) { 1170 topPadding += view.getTop(); 1171 view = (View) view.getParent(); 1172 if (view instanceof ExpandableNotificationRow) { 1173 return topPadding; 1174 } 1175 } 1176 return topPadding; 1177 } 1178 1179 public float getContentTranslation() { 1180 return mPrivateLayout.getTranslationY(); 1181 } 1182 1183 public void setIsLowPriority(boolean isLowPriority) { 1184 mIsLowPriority = isLowPriority; 1185 mPrivateLayout.setIsLowPriority(isLowPriority); 1186 mNotificationInflater.setIsLowPriority(mIsLowPriority); 1187 if (mChildrenContainer != null) { 1188 mChildrenContainer.setIsLowPriority(isLowPriority); 1189 } 1190 } 1191 1192 1193 public void setLowPriorityStateUpdated(boolean lowPriorityStateUpdated) { 1194 mLowPriorityStateUpdated = lowPriorityStateUpdated; 1195 } 1196 1197 public boolean hasLowPriorityStateUpdated() { 1198 return mLowPriorityStateUpdated; 1199 } 1200 1201 public boolean isLowPriority() { 1202 return mIsLowPriority; 1203 } 1204 1205 public void setUseIncreasedCollapsedHeight(boolean use) { 1206 mUseIncreasedCollapsedHeight = use; 1207 mNotificationInflater.setUsesIncreasedHeight(use); 1208 } 1209 1210 public void setUseIncreasedHeadsUpHeight(boolean use) { 1211 mUseIncreasedHeadsUpHeight = use; 1212 mNotificationInflater.setUsesIncreasedHeadsUpHeight(use); 1213 } 1214 1215 public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { 1216 mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler); 1217 } 1218 1219 public void setInflationCallback(InflationCallback callback) { 1220 mNotificationInflater.setInflationCallback(callback); 1221 } 1222 1223 public void setNeedsRedaction(boolean needsRedaction) { 1224 mNotificationInflater.setRedactAmbient(needsRedaction); 1225 } 1226 1227 @VisibleForTesting 1228 public NotificationInflater getNotificationInflater() { 1229 return mNotificationInflater; 1230 } 1231 1232 public int getNotificationColorAmbient() { 1233 return mNotificationColorAmbient; 1234 } 1235 1236 public interface ExpansionLogger { 1237 void logNotificationExpansion(String key, boolean userAction, boolean expanded); 1238 } 1239 1240 public ExpandableNotificationRow(Context context, AttributeSet attrs) { 1241 super(context, attrs); 1242 mFalsingManager = FalsingManager.getInstance(context); 1243 mNotificationInflater = new NotificationInflater(this); 1244 mMenuRow = new NotificationMenuRow(mContext); 1245 initDimens(); 1246 } 1247 1248 private void initDimens() { 1249 mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy); 1250 mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height); 1251 mNotificationMinHeightLarge = getFontScaledHeight( 1252 R.dimen.notification_min_height_increased); 1253 mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height); 1254 mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height); 1255 mMaxHeadsUpHeightLegacy = getFontScaledHeight( 1256 R.dimen.notification_max_heads_up_height_legacy); 1257 mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height); 1258 mMaxHeadsUpHeightIncreased = getFontScaledHeight( 1259 R.dimen.notification_max_heads_up_height_increased); 1260 1261 Resources res = getResources(); 1262 mIncreasedPaddingBetweenElements = res.getDimensionPixelSize( 1263 R.dimen.notification_divider_height_increased); 1264 mIconTransformContentShiftNoIcon = res.getDimensionPixelSize( 1265 R.dimen.notification_icon_transform_content_shift); 1266 mEnableNonGroupedNotificationExpand = 1267 res.getBoolean(R.bool.config_enableNonGroupedNotificationExpand); 1268 mShowGroupBackgroundWhenExpanded = 1269 res.getBoolean(R.bool.config_showGroupNotificationBgWhenExpanded); 1270 } 1271 1272 /** 1273 * @param dimenId the dimen to look up 1274 * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp 1275 */ 1276 private int getFontScaledHeight(int dimenId) { 1277 int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId); 1278 float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity / 1279 getResources().getDisplayMetrics().density); 1280 return (int) (dimensionPixelSize * factor); 1281 } 1282 1283 /** 1284 * Resets this view so it can be re-used for an updated notification. 1285 */ 1286 public void reset() { 1287 mShowingPublicInitialized = false; 1288 onHeightReset(); 1289 requestLayout(); 1290 } 1291 1292 @Override 1293 protected void onFinishInflate() { 1294 super.onFinishInflate(); 1295 mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); 1296 mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); 1297 mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout}; 1298 1299 for (NotificationContentView l : mLayouts) { 1300 l.setExpandClickListener(mExpandClickListener); 1301 l.setContainingNotification(this); 1302 } 1303 mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); 1304 mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { 1305 @Override 1306 public void onInflate(ViewStub stub, View inflated) { 1307 mGuts = (NotificationGuts) inflated; 1308 mGuts.setClipTopAmount(getClipTopAmount()); 1309 mGuts.setActualHeight(getActualHeight()); 1310 mGutsStub = null; 1311 } 1312 }); 1313 mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub); 1314 mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() { 1315 1316 @Override 1317 public void onInflate(ViewStub stub, View inflated) { 1318 mChildrenContainer = (NotificationChildrenContainer) inflated; 1319 mChildrenContainer.setIsLowPriority(mIsLowPriority); 1320 mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); 1321 mChildrenContainer.onNotificationUpdated(); 1322 1323 if (mShouldTranslateContents) { 1324 mTranslateableViews.add(mChildrenContainer); 1325 } 1326 } 1327 }); 1328 1329 if (mShouldTranslateContents) { 1330 // Add the views that we translate to reveal the menu 1331 mTranslateableViews = new ArrayList<>(); 1332 for (int i = 0; i < getChildCount(); i++) { 1333 mTranslateableViews.add(getChildAt(i)); 1334 } 1335 // Remove views that don't translate 1336 mTranslateableViews.remove(mChildrenContainerStub); 1337 mTranslateableViews.remove(mGutsStub); 1338 } 1339 } 1340 1341 public void resetTranslation() { 1342 if (mTranslateAnim != null) { 1343 mTranslateAnim.cancel(); 1344 } 1345 1346 if (!mShouldTranslateContents) { 1347 setTranslationX(0); 1348 } else if (mTranslateableViews != null) { 1349 for (int i = 0; i < mTranslateableViews.size(); i++) { 1350 mTranslateableViews.get(i).setTranslationX(0); 1351 } 1352 invalidateOutline(); 1353 } 1354 1355 mMenuRow.resetMenu(); 1356 } 1357 1358 public void animateTranslateNotification(final float leftTarget) { 1359 if (mTranslateAnim != null) { 1360 mTranslateAnim.cancel(); 1361 } 1362 mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */); 1363 if (mTranslateAnim != null) { 1364 mTranslateAnim.start(); 1365 } 1366 } 1367 1368 @Override 1369 public void setTranslation(float translationX) { 1370 if (areGutsExposed()) { 1371 // Don't translate if guts are showing. 1372 return; 1373 } 1374 if (!mShouldTranslateContents) { 1375 setTranslationX(translationX); 1376 } else if (mTranslateableViews != null) { 1377 // Translate the group of views 1378 for (int i = 0; i < mTranslateableViews.size(); i++) { 1379 if (mTranslateableViews.get(i) != null) { 1380 mTranslateableViews.get(i).setTranslationX(translationX); 1381 } 1382 } 1383 invalidateOutline(); 1384 } 1385 if (mMenuRow.getMenuView() != null) { 1386 mMenuRow.onTranslationUpdate(translationX); 1387 } 1388 } 1389 1390 @Override 1391 public float getTranslation() { 1392 if (!mShouldTranslateContents) { 1393 return getTranslationX(); 1394 } 1395 1396 if (mTranslateableViews != null && mTranslateableViews.size() > 0) { 1397 // All of the views in the list should have same translation, just use first one. 1398 return mTranslateableViews.get(0).getTranslationX(); 1399 } 1400 1401 return 0; 1402 } 1403 1404 public Animator getTranslateViewAnimator(final float leftTarget, 1405 AnimatorUpdateListener listener) { 1406 if (mTranslateAnim != null) { 1407 mTranslateAnim.cancel(); 1408 } 1409 if (areGutsExposed()) { 1410 // No translation if guts are exposed. 1411 return null; 1412 } 1413 final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT, 1414 leftTarget); 1415 if (listener != null) { 1416 translateAnim.addUpdateListener(listener); 1417 } 1418 translateAnim.addListener(new AnimatorListenerAdapter() { 1419 boolean cancelled = false; 1420 1421 @Override 1422 public void onAnimationCancel(Animator anim) { 1423 cancelled = true; 1424 } 1425 1426 @Override 1427 public void onAnimationEnd(Animator anim) { 1428 if (!cancelled && leftTarget == 0) { 1429 mMenuRow.resetMenu(); 1430 mTranslateAnim = null; 1431 } 1432 } 1433 }); 1434 mTranslateAnim = translateAnim; 1435 return translateAnim; 1436 } 1437 1438 public void inflateGuts() { 1439 if (mGuts == null) { 1440 mGutsStub.inflate(); 1441 } 1442 } 1443 1444 private void updateChildrenVisibility() { 1445 mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE 1446 : INVISIBLE); 1447 if (mChildrenContainer != null) { 1448 mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE 1449 : INVISIBLE); 1450 } 1451 // The limits might have changed if the view suddenly became a group or vice versa 1452 updateLimits(); 1453 } 1454 1455 @Override 1456 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 1457 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 1458 // Add a record for the entire layout since its content is somehow small. 1459 // The event comes from a leaf view that is interacted with. 1460 AccessibilityEvent record = AccessibilityEvent.obtain(); 1461 onInitializeAccessibilityEvent(record); 1462 dispatchPopulateAccessibilityEvent(record); 1463 event.appendRecord(record); 1464 return true; 1465 } 1466 return false; 1467 } 1468 1469 @Override 1470 public void setDark(boolean dark, boolean fade, long delay) { 1471 super.setDark(dark, fade, delay); 1472 mDark = dark; 1473 if (!mIsHeadsUp) { 1474 // Only fade the showing view of the pulsing notification. 1475 fade = false; 1476 } 1477 final NotificationContentView showing = getShowingLayout(); 1478 if (showing != null) { 1479 showing.setDark(dark, fade, delay); 1480 } 1481 if (mIsSummaryWithChildren) { 1482 mChildrenContainer.setDark(dark, fade, delay); 1483 } 1484 updateShelfIconColor(); 1485 } 1486 1487 /** 1488 * Tap sounds should not be played when we're unlocking. 1489 * Doing so would cause audio collision and the system would feel unpolished. 1490 */ 1491 @Override 1492 public boolean isSoundEffectsEnabled() { 1493 final boolean mute = mDark && mSecureStateProvider != null && 1494 !mSecureStateProvider.getAsBoolean(); 1495 return !mute && super.isSoundEffectsEnabled(); 1496 } 1497 1498 public boolean isExpandable() { 1499 if (mIsSummaryWithChildren && !mShowingPublic) { 1500 return !mChildrenExpanded; 1501 } 1502 return mEnableNonGroupedNotificationExpand && mExpandable; 1503 } 1504 1505 public void setExpandable(boolean expandable) { 1506 mExpandable = expandable; 1507 mPrivateLayout.updateExpandButtons(isExpandable()); 1508 } 1509 1510 @Override 1511 public void setClipToActualHeight(boolean clipToActualHeight) { 1512 super.setClipToActualHeight(clipToActualHeight || isUserLocked()); 1513 getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked()); 1514 } 1515 1516 /** 1517 * @return whether the user has changed the expansion state 1518 */ 1519 public boolean hasUserChangedExpansion() { 1520 return mHasUserChangedExpansion; 1521 } 1522 1523 public boolean isUserExpanded() { 1524 return mUserExpanded; 1525 } 1526 1527 /** 1528 * Set this notification to be expanded by the user 1529 * 1530 * @param userExpanded whether the user wants this notification to be expanded 1531 */ 1532 public void setUserExpanded(boolean userExpanded) { 1533 setUserExpanded(userExpanded, false /* allowChildExpansion */); 1534 } 1535 1536 /** 1537 * Set this notification to be expanded by the user 1538 * 1539 * @param userExpanded whether the user wants this notification to be expanded 1540 * @param allowChildExpansion whether a call to this method allows expanding children 1541 */ 1542 public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { 1543 mFalsingManager.setNotificationExpanded(); 1544 if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion 1545 && !mChildrenContainer.showingAsLowPriority()) { 1546 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); 1547 mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded); 1548 onExpansionChanged(true /* userAction */, wasExpanded); 1549 return; 1550 } 1551 if (userExpanded && !mExpandable) return; 1552 final boolean wasExpanded = isExpanded(); 1553 mHasUserChangedExpansion = true; 1554 mUserExpanded = userExpanded; 1555 onExpansionChanged(true /* userAction */, wasExpanded); 1556 if (!wasExpanded && isExpanded() 1557 && getActualHeight() != getIntrinsicHeight()) { 1558 notifyHeightChanged(true /* needsAnimation */); 1559 } 1560 } 1561 1562 public void resetUserExpansion() { 1563 boolean changed = mUserExpanded; 1564 mHasUserChangedExpansion = false; 1565 mUserExpanded = false; 1566 if (changed && mIsSummaryWithChildren) { 1567 mChildrenContainer.onExpansionChanged(); 1568 } 1569 updateShelfIconColor(); 1570 } 1571 1572 public boolean isUserLocked() { 1573 return mUserLocked && !mForceUnlocked; 1574 } 1575 1576 public void setUserLocked(boolean userLocked) { 1577 mUserLocked = userLocked; 1578 mPrivateLayout.setUserExpanding(userLocked); 1579 // This is intentionally not guarded with mIsSummaryWithChildren since we might have had 1580 // children but not anymore. 1581 if (mChildrenContainer != null) { 1582 mChildrenContainer.setUserLocked(userLocked); 1583 if (mIsSummaryWithChildren && (userLocked || !isGroupExpanded())) { 1584 updateBackgroundForGroupState(); 1585 } 1586 } 1587 } 1588 1589 /** 1590 * @return has the system set this notification to be expanded 1591 */ 1592 public boolean isSystemExpanded() { 1593 return mIsSystemExpanded; 1594 } 1595 1596 /** 1597 * Set this notification to be expanded by the system. 1598 * 1599 * @param expand whether the system wants this notification to be expanded. 1600 */ 1601 public void setSystemExpanded(boolean expand) { 1602 if (expand != mIsSystemExpanded) { 1603 final boolean wasExpanded = isExpanded(); 1604 mIsSystemExpanded = expand; 1605 notifyHeightChanged(false /* needsAnimation */); 1606 onExpansionChanged(false /* userAction */, wasExpanded); 1607 if (mIsSummaryWithChildren) { 1608 mChildrenContainer.updateGroupOverflow(); 1609 } 1610 } 1611 } 1612 1613 /** 1614 * @param onKeyguard whether to prevent notification expansion 1615 */ 1616 public void setOnKeyguard(boolean onKeyguard) { 1617 if (onKeyguard != mOnKeyguard) { 1618 boolean wasAboveShelf = isAboveShelf(); 1619 final boolean wasExpanded = isExpanded(); 1620 mOnKeyguard = onKeyguard; 1621 onExpansionChanged(false /* userAction */, wasExpanded); 1622 if (wasExpanded != isExpanded()) { 1623 if (mIsSummaryWithChildren) { 1624 mChildrenContainer.updateGroupOverflow(); 1625 } 1626 notifyHeightChanged(false /* needsAnimation */); 1627 } 1628 if (isAboveShelf() != wasAboveShelf) { 1629 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 1630 } 1631 } 1632 } 1633 1634 /** 1635 * @return Can the underlying notification be cleared? This can be different from whether the 1636 * notification can be dismissed in case notifications are sensitive on the lockscreen. 1637 * @see #canViewBeDismissed() 1638 */ 1639 public boolean isClearable() { 1640 if (mStatusBarNotification == null || !mStatusBarNotification.isClearable()) { 1641 return false; 1642 } 1643 if (mIsSummaryWithChildren) { 1644 List<ExpandableNotificationRow> notificationChildren = 1645 mChildrenContainer.getNotificationChildren(); 1646 for (int i = 0; i < notificationChildren.size(); i++) { 1647 ExpandableNotificationRow child = notificationChildren.get(i); 1648 if (!child.isClearable()) { 1649 return false; 1650 } 1651 } 1652 } 1653 return true; 1654 } 1655 1656 @Override 1657 public int getIntrinsicHeight() { 1658 if (isUserLocked()) { 1659 return getActualHeight(); 1660 } 1661 if (mGuts != null && mGuts.isExposed()) { 1662 return mGuts.getIntrinsicHeight(); 1663 } else if ((isChildInGroup() && !isGroupExpanded())) { 1664 return mPrivateLayout.getMinHeight(); 1665 } else if (mSensitive && mHideSensitiveForIntrinsicHeight) { 1666 return getMinHeight(); 1667 } else if (mIsSummaryWithChildren && (!mOnKeyguard || mShowAmbient)) { 1668 return mChildrenContainer.getIntrinsicHeight(); 1669 } else if (isHeadsUpAllowed() && (mIsHeadsUp || mHeadsupDisappearRunning)) { 1670 if (isPinned() || mHeadsupDisappearRunning) { 1671 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); 1672 } else if (isExpanded()) { 1673 return Math.max(getMaxExpandHeight(), mHeadsUpHeight); 1674 } else { 1675 return Math.max(getCollapsedHeight(), mHeadsUpHeight); 1676 } 1677 } else if (isExpanded()) { 1678 return getMaxExpandHeight(); 1679 } else { 1680 return getCollapsedHeight(); 1681 } 1682 } 1683 1684 private boolean isHeadsUpAllowed() { 1685 return !mOnKeyguard && !mShowAmbient; 1686 } 1687 1688 @Override 1689 public boolean isGroupExpanded() { 1690 return mGroupManager.isGroupExpanded(mStatusBarNotification); 1691 } 1692 1693 private void onChildrenCountChanged() { 1694 mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS 1695 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0; 1696 if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) { 1697 mChildrenContainer.recreateNotificationHeader(mExpandClickListener 1698 ); 1699 } 1700 getShowingLayout().updateBackgroundColor(false /* animate */); 1701 mPrivateLayout.updateExpandButtons(isExpandable()); 1702 updateChildrenHeaderAppearance(); 1703 updateChildrenVisibility(); 1704 } 1705 1706 public void updateChildrenHeaderAppearance() { 1707 if (mIsSummaryWithChildren) { 1708 mChildrenContainer.updateChildrenHeaderAppearance(); 1709 } 1710 } 1711 1712 /** 1713 * Check whether the view state is currently expanded. This is given by the system in {@link 1714 * #setSystemExpanded(boolean)} and can be overridden by user expansion or 1715 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this 1716 * view can differ from this state, if layout params are modified from outside. 1717 * 1718 * @return whether the view state is currently expanded. 1719 */ 1720 public boolean isExpanded() { 1721 return isExpanded(false /* allowOnKeyguard */); 1722 } 1723 1724 public boolean isExpanded(boolean allowOnKeyguard) { 1725 return (!mOnKeyguard || allowOnKeyguard) 1726 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) 1727 || isUserExpanded()); 1728 } 1729 1730 private boolean isSystemChildExpanded() { 1731 return mIsSystemChildExpanded; 1732 } 1733 1734 public void setSystemChildExpanded(boolean expanded) { 1735 mIsSystemChildExpanded = expanded; 1736 } 1737 1738 public void setLayoutListener(LayoutListener listener) { 1739 mLayoutListener = listener; 1740 } 1741 1742 public void removeListener() { 1743 mLayoutListener = null; 1744 } 1745 1746 @Override 1747 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1748 super.onLayout(changed, left, top, right, bottom); 1749 updateMaxHeights(); 1750 if (mMenuRow.getMenuView() != null) { 1751 mMenuRow.onHeightUpdate(); 1752 } 1753 updateContentShiftHeight(); 1754 if (mLayoutListener != null) { 1755 mLayoutListener.onLayout(); 1756 } 1757 } 1758 1759 /** 1760 * Updates the content shift height such that the header is completely hidden when coming from 1761 * the top. 1762 */ 1763 private void updateContentShiftHeight() { 1764 NotificationHeaderView notificationHeader = getVisibleNotificationHeader(); 1765 if (notificationHeader != null) { 1766 CachingIconView icon = notificationHeader.getIcon(); 1767 mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight(); 1768 } else { 1769 mIconTransformContentShift = mIconTransformContentShiftNoIcon; 1770 } 1771 } 1772 1773 private void updateMaxHeights() { 1774 int intrinsicBefore = getIntrinsicHeight(); 1775 View expandedChild = mPrivateLayout.getExpandedChild(); 1776 if (expandedChild == null) { 1777 expandedChild = mPrivateLayout.getContractedChild(); 1778 } 1779 mMaxExpandHeight = expandedChild.getHeight(); 1780 View headsUpChild = mPrivateLayout.getHeadsUpChild(); 1781 if (headsUpChild == null) { 1782 headsUpChild = mPrivateLayout.getContractedChild(); 1783 } 1784 mHeadsUpHeight = headsUpChild.getHeight(); 1785 if (intrinsicBefore != getIntrinsicHeight()) { 1786 notifyHeightChanged(true /* needsAnimation */); 1787 } 1788 } 1789 1790 @Override 1791 public void notifyHeightChanged(boolean needsAnimation) { 1792 super.notifyHeightChanged(needsAnimation); 1793 getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked()); 1794 } 1795 1796 public void setSensitive(boolean sensitive, boolean hideSensitive) { 1797 mSensitive = sensitive; 1798 mSensitiveHiddenInGeneral = hideSensitive; 1799 } 1800 1801 @Override 1802 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { 1803 mHideSensitiveForIntrinsicHeight = hideSensitive; 1804 if (mIsSummaryWithChildren) { 1805 List<ExpandableNotificationRow> notificationChildren = 1806 mChildrenContainer.getNotificationChildren(); 1807 for (int i = 0; i < notificationChildren.size(); i++) { 1808 ExpandableNotificationRow child = notificationChildren.get(i); 1809 child.setHideSensitiveForIntrinsicHeight(hideSensitive); 1810 } 1811 } 1812 } 1813 1814 @Override 1815 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, 1816 long duration) { 1817 boolean oldShowingPublic = mShowingPublic; 1818 mShowingPublic = mSensitive && hideSensitive; 1819 if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { 1820 return; 1821 } 1822 1823 // bail out if no public version 1824 if (mPublicLayout.getChildCount() == 0) return; 1825 1826 if (!animated) { 1827 mPublicLayout.animate().cancel(); 1828 mPrivateLayout.animate().cancel(); 1829 if (mChildrenContainer != null) { 1830 mChildrenContainer.animate().cancel(); 1831 mChildrenContainer.setAlpha(1f); 1832 } 1833 mPublicLayout.setAlpha(1f); 1834 mPrivateLayout.setAlpha(1f); 1835 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); 1836 updateChildrenVisibility(); 1837 } else { 1838 animateShowingPublic(delay, duration); 1839 } 1840 NotificationContentView showingLayout = getShowingLayout(); 1841 showingLayout.updateBackgroundColor(animated); 1842 mPrivateLayout.updateExpandButtons(isExpandable()); 1843 updateShelfIconColor(); 1844 showingLayout.setDark(isDark(), false /* animate */, 0 /* delay */); 1845 mShowingPublicInitialized = true; 1846 } 1847 1848 private void animateShowingPublic(long delay, long duration) { 1849 View[] privateViews = mIsSummaryWithChildren 1850 ? new View[] {mChildrenContainer} 1851 : new View[] {mPrivateLayout}; 1852 View[] publicViews = new View[] {mPublicLayout}; 1853 View[] hiddenChildren = mShowingPublic ? privateViews : publicViews; 1854 View[] shownChildren = mShowingPublic ? publicViews : privateViews; 1855 for (final View hiddenView : hiddenChildren) { 1856 hiddenView.setVisibility(View.VISIBLE); 1857 hiddenView.animate().cancel(); 1858 hiddenView.animate() 1859 .alpha(0f) 1860 .setStartDelay(delay) 1861 .setDuration(duration) 1862 .withEndAction(new Runnable() { 1863 @Override 1864 public void run() { 1865 hiddenView.setVisibility(View.INVISIBLE); 1866 } 1867 }); 1868 } 1869 for (View showView : shownChildren) { 1870 showView.setVisibility(View.VISIBLE); 1871 showView.setAlpha(0f); 1872 showView.animate().cancel(); 1873 showView.animate() 1874 .alpha(1f) 1875 .setStartDelay(delay) 1876 .setDuration(duration); 1877 } 1878 } 1879 1880 @Override 1881 public boolean mustStayOnScreen() { 1882 return mIsHeadsUp; 1883 } 1884 1885 /** 1886 * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as 1887 * otherwise some state might not be updated. To request about the general clearability 1888 * see {@link #isClearable()}. 1889 */ 1890 public boolean canViewBeDismissed() { 1891 return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral); 1892 } 1893 1894 public void makeActionsVisibile() { 1895 setUserExpanded(true, true); 1896 if (isChildInGroup()) { 1897 mGroupManager.setGroupExpanded(mStatusBarNotification, true); 1898 } 1899 notifyHeightChanged(false /* needsAnimation */); 1900 } 1901 1902 public void setChildrenExpanded(boolean expanded, boolean animate) { 1903 mChildrenExpanded = expanded; 1904 if (mChildrenContainer != null) { 1905 mChildrenContainer.setChildrenExpanded(expanded); 1906 } 1907 updateBackgroundForGroupState(); 1908 updateClickAndFocus(); 1909 } 1910 1911 public static void applyTint(View v, int color) { 1912 int alpha; 1913 if (color != 0) { 1914 alpha = COLORED_DIVIDER_ALPHA; 1915 } else { 1916 color = 0xff000000; 1917 alpha = DEFAULT_DIVIDER_ALPHA; 1918 } 1919 if (v.getBackground() instanceof ColorDrawable) { 1920 ColorDrawable background = (ColorDrawable) v.getBackground(); 1921 background.mutate(); 1922 background.setColor(color); 1923 background.setAlpha(alpha); 1924 } 1925 } 1926 1927 public int getMaxExpandHeight() { 1928 return mMaxExpandHeight; 1929 } 1930 1931 public boolean areGutsExposed() { 1932 return (mGuts != null && mGuts.isExposed()); 1933 } 1934 1935 @Override 1936 public boolean isContentExpandable() { 1937 if (mIsSummaryWithChildren && !mShowingPublic) { 1938 return true; 1939 } 1940 NotificationContentView showingLayout = getShowingLayout(); 1941 return showingLayout.isContentExpandable(); 1942 } 1943 1944 @Override 1945 protected View getContentView() { 1946 if (mIsSummaryWithChildren && !mShowingPublic) { 1947 return mChildrenContainer; 1948 } 1949 return getShowingLayout(); 1950 } 1951 1952 @Override 1953 protected void onAppearAnimationFinished(boolean wasAppearing) { 1954 super.onAppearAnimationFinished(wasAppearing); 1955 if (wasAppearing) { 1956 // During the animation the visible view might have changed, so let's make sure all 1957 // alphas are reset 1958 if (mChildrenContainer != null) { 1959 mChildrenContainer.setAlpha(1.0f); 1960 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null); 1961 } 1962 for (NotificationContentView l : mLayouts) { 1963 l.setAlpha(1.0f); 1964 l.setLayerType(LAYER_TYPE_NONE, null); 1965 } 1966 } 1967 } 1968 1969 @Override 1970 public int getExtraBottomPadding() { 1971 if (mIsSummaryWithChildren && isGroupExpanded()) { 1972 return mIncreasedPaddingBetweenElements; 1973 } 1974 return 0; 1975 } 1976 1977 @Override 1978 public void setActualHeight(int height, boolean notifyListeners) { 1979 boolean changed = height != getActualHeight(); 1980 super.setActualHeight(height, notifyListeners); 1981 if (changed && isRemoved()) { 1982 // TODO: remove this once we found the gfx bug for this. 1983 // This is a hack since a removed view sometimes would just stay blank. it occured 1984 // when sending yourself a message and then clicking on it. 1985 ViewGroup parent = (ViewGroup) getParent(); 1986 if (parent != null) { 1987 parent.invalidate(); 1988 } 1989 } 1990 if (mGuts != null && mGuts.isExposed()) { 1991 mGuts.setActualHeight(height); 1992 return; 1993 } 1994 int contentHeight = Math.max(getMinHeight(), height); 1995 for (NotificationContentView l : mLayouts) { 1996 l.setContentHeight(contentHeight); 1997 } 1998 if (mIsSummaryWithChildren) { 1999 mChildrenContainer.setActualHeight(height); 2000 } 2001 if (mGuts != null) { 2002 mGuts.setActualHeight(height); 2003 } 2004 if (mMenuRow.getMenuView() != null) { 2005 mMenuRow.onHeightUpdate(); 2006 } 2007 } 2008 2009 @Override 2010 public int getMaxContentHeight() { 2011 if (mIsSummaryWithChildren && !mShowingPublic) { 2012 return mChildrenContainer.getMaxContentHeight(); 2013 } 2014 NotificationContentView showingLayout = getShowingLayout(); 2015 return showingLayout.getMaxHeight(); 2016 } 2017 2018 @Override 2019 public int getMinHeight(boolean ignoreTemporaryStates) { 2020 if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) { 2021 return mGuts.getIntrinsicHeight(); 2022 } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp 2023 && mHeadsUpManager.isTrackingHeadsUp()) { 2024 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); 2025 } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) { 2026 return mChildrenContainer.getMinHeight(); 2027 } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) { 2028 return mHeadsUpHeight; 2029 } 2030 NotificationContentView showingLayout = getShowingLayout(); 2031 return showingLayout.getMinHeight(); 2032 } 2033 2034 @Override 2035 public int getCollapsedHeight() { 2036 if (mIsSummaryWithChildren && !mShowingPublic) { 2037 return mChildrenContainer.getCollapsedHeight(); 2038 } 2039 return getMinHeight(); 2040 } 2041 2042 @Override 2043 public void setClipTopAmount(int clipTopAmount) { 2044 super.setClipTopAmount(clipTopAmount); 2045 for (NotificationContentView l : mLayouts) { 2046 l.setClipTopAmount(clipTopAmount); 2047 } 2048 if (mGuts != null) { 2049 mGuts.setClipTopAmount(clipTopAmount); 2050 } 2051 } 2052 2053 @Override 2054 public void setClipBottomAmount(int clipBottomAmount) { 2055 if (clipBottomAmount != mClipBottomAmount) { 2056 super.setClipBottomAmount(clipBottomAmount); 2057 for (NotificationContentView l : mLayouts) { 2058 l.setClipBottomAmount(clipBottomAmount); 2059 } 2060 if (mGuts != null) { 2061 mGuts.setClipBottomAmount(clipBottomAmount); 2062 } 2063 } 2064 if (mChildrenContainer != null) { 2065 // We have to update this even if it hasn't changed, since the children locations can 2066 // have changed 2067 mChildrenContainer.setClipBottomAmount(clipBottomAmount); 2068 } 2069 } 2070 2071 public boolean isMaxExpandHeightInitialized() { 2072 return mMaxExpandHeight != 0; 2073 } 2074 2075 public NotificationContentView getShowingLayout() { 2076 return mShowingPublic ? mPublicLayout : mPrivateLayout; 2077 } 2078 2079 public void setLegacy(boolean legacy) { 2080 for (NotificationContentView l : mLayouts) { 2081 l.setLegacy(legacy); 2082 } 2083 } 2084 2085 @Override 2086 protected void updateBackgroundTint() { 2087 super.updateBackgroundTint(); 2088 updateBackgroundForGroupState(); 2089 if (mIsSummaryWithChildren) { 2090 List<ExpandableNotificationRow> notificationChildren = 2091 mChildrenContainer.getNotificationChildren(); 2092 for (int i = 0; i < notificationChildren.size(); i++) { 2093 ExpandableNotificationRow child = notificationChildren.get(i); 2094 child.updateBackgroundForGroupState(); 2095 } 2096 } 2097 } 2098 2099 /** 2100 * Called when a group has finished animating from collapsed or expanded state. 2101 */ 2102 public void onFinishedExpansionChange() { 2103 mGroupExpansionChanging = false; 2104 updateBackgroundForGroupState(); 2105 } 2106 2107 /** 2108 * Updates the parent and children backgrounds in a group based on the expansion state. 2109 */ 2110 public void updateBackgroundForGroupState() { 2111 if (mIsSummaryWithChildren) { 2112 // Only when the group has finished expanding do we hide its background. 2113 mShowNoBackground = !mShowGroupBackgroundWhenExpanded && isGroupExpanded() 2114 && !isGroupExpansionChanging() && !isUserLocked(); 2115 mChildrenContainer.updateHeaderForExpansion(mShowNoBackground); 2116 List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren(); 2117 for (int i = 0; i < children.size(); i++) { 2118 children.get(i).updateBackgroundForGroupState(); 2119 } 2120 } else if (isChildInGroup()) { 2121 final int childColor = getShowingLayout().getBackgroundColorForExpansionState(); 2122 // Only show a background if the group is expanded OR if it is expanding / collapsing 2123 // and has a custom background color. 2124 final boolean showBackground = isGroupExpanded() 2125 || ((mNotificationParent.isGroupExpansionChanging() 2126 || mNotificationParent.isUserLocked()) && childColor != 0); 2127 mShowNoBackground = !showBackground; 2128 } else { 2129 // Only children or parents ever need no background. 2130 mShowNoBackground = false; 2131 } 2132 updateOutline(); 2133 updateBackground(); 2134 } 2135 2136 public int getPositionOfChild(ExpandableNotificationRow childRow) { 2137 if (mIsSummaryWithChildren) { 2138 return mChildrenContainer.getPositionInLinearLayout(childRow); 2139 } 2140 return 0; 2141 } 2142 2143 public void setExpansionLogger(ExpansionLogger logger, String key) { 2144 mLogger = logger; 2145 mLoggingKey = key; 2146 } 2147 2148 public void onExpandedByGesture(boolean userExpanded) { 2149 int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; 2150 if (mGroupManager.isSummaryOfGroup(getStatusBarNotification())) { 2151 event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER; 2152 } 2153 MetricsLogger.action(mContext, event, userExpanded); 2154 } 2155 2156 @Override 2157 public float getIncreasedPaddingAmount() { 2158 if (mIsSummaryWithChildren) { 2159 if (isGroupExpanded()) { 2160 return 1.0f; 2161 } else if (isUserLocked()) { 2162 return mChildrenContainer.getIncreasedPaddingAmount(); 2163 } 2164 } else if (isColorized() && (!mIsLowPriority || isExpanded())) { 2165 return -1.0f; 2166 } 2167 return 0.0f; 2168 } 2169 2170 private boolean isColorized() { 2171 return mIsColorized && mBgTint != NO_COLOR; 2172 } 2173 2174 @Override 2175 protected boolean disallowSingleClick(MotionEvent event) { 2176 float x = event.getX(); 2177 float y = event.getY(); 2178 NotificationHeaderView header = getVisibleNotificationHeader(); 2179 if (header != null && header.isInTouchRect(x - getTranslation(), y)) { 2180 return true; 2181 } 2182 if ((!mIsSummaryWithChildren || mShowingPublic) 2183 && getShowingLayout().disallowSingleClick(x, y)) { 2184 return true; 2185 } 2186 return super.disallowSingleClick(event); 2187 } 2188 2189 private void onExpansionChanged(boolean userAction, boolean wasExpanded) { 2190 boolean nowExpanded = isExpanded(); 2191 if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) { 2192 nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); 2193 } 2194 if (nowExpanded != wasExpanded) { 2195 updateShelfIconColor(); 2196 if (mLogger != null) { 2197 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded); 2198 } 2199 if (mIsSummaryWithChildren) { 2200 mChildrenContainer.onExpansionChanged(); 2201 } 2202 } 2203 } 2204 2205 @Override 2206 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 2207 super.onInitializeAccessibilityNodeInfoInternal(info); 2208 if (canViewBeDismissed()) { 2209 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); 2210 } 2211 boolean expandable = mShowingPublic; 2212 boolean isExpanded = false; 2213 if (!expandable) { 2214 if (mIsSummaryWithChildren) { 2215 expandable = true; 2216 if (!mIsLowPriority || isExpanded()) { 2217 isExpanded = isGroupExpanded(); 2218 } 2219 } else { 2220 expandable = mPrivateLayout.isContentExpandable(); 2221 isExpanded = isExpanded(); 2222 } 2223 } 2224 if (expandable) { 2225 if (isExpanded) { 2226 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); 2227 } else { 2228 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); 2229 } 2230 } 2231 } 2232 2233 @Override 2234 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 2235 if (super.performAccessibilityActionInternal(action, arguments)) { 2236 return true; 2237 } 2238 switch (action) { 2239 case AccessibilityNodeInfo.ACTION_DISMISS: 2240 NotificationStackScrollLayout.performDismiss(this, mGroupManager, 2241 true /* fromAccessibility */); 2242 return true; 2243 case AccessibilityNodeInfo.ACTION_COLLAPSE: 2244 case AccessibilityNodeInfo.ACTION_EXPAND: 2245 mExpandClickListener.onClick(this); 2246 return true; 2247 } 2248 return false; 2249 } 2250 2251 public boolean shouldRefocusOnDismiss() { 2252 return mRefocusOnDismiss || isAccessibilityFocused(); 2253 } 2254 2255 public interface OnExpandClickListener { 2256 void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded); 2257 } 2258 2259 @Override 2260 public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { 2261 return new NotificationViewState(stackScrollState); 2262 } 2263 2264 @Override 2265 public boolean isAboveShelf() { 2266 return !isOnKeyguard() 2267 && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)); 2268 } 2269 2270 public void setShowAmbient(boolean showAmbient) { 2271 if (showAmbient != mShowAmbient) { 2272 mShowAmbient = showAmbient; 2273 if (mChildrenContainer != null) { 2274 mChildrenContainer.notifyShowAmbientChanged(); 2275 } 2276 notifyHeightChanged(false /* needsAnimation */); 2277 } 2278 } 2279 2280 public boolean isShowingAmbient() { 2281 return mShowAmbient; 2282 } 2283 2284 public void setAboveShelf(boolean aboveShelf) { 2285 boolean wasAboveShelf = isAboveShelf(); 2286 mAboveShelf = aboveShelf; 2287 if (isAboveShelf() != wasAboveShelf) { 2288 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 2289 } 2290 } 2291 2292 public static class NotificationViewState extends ExpandableViewState { 2293 2294 private final StackScrollState mOverallState; 2295 2296 2297 private NotificationViewState(StackScrollState stackScrollState) { 2298 mOverallState = stackScrollState; 2299 } 2300 2301 @Override 2302 public void applyToView(View view) { 2303 super.applyToView(view); 2304 if (view instanceof ExpandableNotificationRow) { 2305 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 2306 row.applyChildrenState(mOverallState); 2307 } 2308 } 2309 2310 @Override 2311 protected void onYTranslationAnimationFinished(View view) { 2312 super.onYTranslationAnimationFinished(view); 2313 if (view instanceof ExpandableNotificationRow) { 2314 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 2315 if (row.isHeadsUpAnimatingAway()) { 2316 row.setHeadsUpAnimatingAway(false); 2317 } 2318 } 2319 } 2320 2321 @Override 2322 public void animateTo(View child, AnimationProperties properties) { 2323 super.animateTo(child, properties); 2324 if (child instanceof ExpandableNotificationRow) { 2325 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 2326 row.startChildAnimation(mOverallState, properties); 2327 } 2328 } 2329 } 2330 2331 @VisibleForTesting 2332 protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) { 2333 mChildrenContainer = childrenContainer; 2334 } 2335 } 2336