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