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.notification.row; 18 19 import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY; 20 import static android.service.notification.NotificationListenerService.REASON_CANCEL; 21 22 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; 23 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; 24 25 import android.animation.Animator; 26 import android.animation.AnimatorListenerAdapter; 27 import android.animation.ObjectAnimator; 28 import android.animation.ValueAnimator.AnimatorUpdateListener; 29 import android.app.Notification; 30 import android.app.NotificationChannel; 31 import android.content.Context; 32 import android.content.res.Configuration; 33 import android.content.res.Resources; 34 import android.graphics.Canvas; 35 import android.graphics.Path; 36 import android.graphics.Point; 37 import android.graphics.drawable.AnimatedVectorDrawable; 38 import android.graphics.drawable.AnimationDrawable; 39 import android.graphics.drawable.Drawable; 40 import android.os.Build; 41 import android.os.Bundle; 42 import android.os.Trace; 43 import android.service.notification.StatusBarNotification; 44 import android.util.ArraySet; 45 import android.util.AttributeSet; 46 import android.util.FloatProperty; 47 import android.util.IndentingPrintWriter; 48 import android.util.Log; 49 import android.util.MathUtils; 50 import android.view.KeyEvent; 51 import android.view.LayoutInflater; 52 import android.view.MotionEvent; 53 import android.view.NotificationHeaderView; 54 import android.view.View; 55 import android.view.ViewGroup; 56 import android.view.ViewParent; 57 import android.view.ViewStub; 58 import android.view.accessibility.AccessibilityEvent; 59 import android.view.accessibility.AccessibilityNodeInfo; 60 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 61 import android.widget.Chronometer; 62 import android.widget.FrameLayout; 63 import android.widget.ImageView; 64 65 import androidx.annotation.NonNull; 66 import androidx.annotation.Nullable; 67 68 import com.android.app.animation.Interpolators; 69 import com.android.internal.annotations.VisibleForTesting; 70 import com.android.internal.logging.MetricsLogger; 71 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 72 import com.android.internal.statusbar.IStatusBarService; 73 import com.android.internal.util.ContrastColorUtil; 74 import com.android.internal.widget.CachingIconView; 75 import com.android.internal.widget.CallLayout; 76 import com.android.systemui.R; 77 import com.android.systemui.classifier.FalsingCollector; 78 import com.android.systemui.flags.FeatureFlags; 79 import com.android.systemui.flags.Flags; 80 import com.android.systemui.flags.ViewRefactorFlag; 81 import com.android.systemui.plugins.FalsingManager; 82 import com.android.systemui.plugins.PluginListener; 83 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 84 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; 85 import com.android.systemui.plugins.statusbar.StatusBarStateController; 86 import com.android.systemui.statusbar.RemoteInputController; 87 import com.android.systemui.statusbar.SmartReplyController; 88 import com.android.systemui.statusbar.StatusBarIconView; 89 import com.android.systemui.statusbar.notification.AboveShelfChangedListener; 90 import com.android.systemui.statusbar.notification.FeedbackIcon; 91 import com.android.systemui.statusbar.notification.LaunchAnimationParameters; 92 import com.android.systemui.statusbar.notification.NotificationFadeAware; 93 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController; 94 import com.android.systemui.statusbar.notification.NotificationUtils; 95 import com.android.systemui.statusbar.notification.SourceType; 96 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 97 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; 98 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; 99 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; 100 import com.android.systemui.statusbar.notification.logging.NotificationCounters; 101 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; 102 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; 103 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; 104 import com.android.systemui.statusbar.notification.stack.AmbientState; 105 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 106 import com.android.systemui.statusbar.notification.stack.ExpandableViewState; 107 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; 108 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger; 109 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; 110 import com.android.systemui.statusbar.notification.stack.SwipeableView; 111 import com.android.systemui.statusbar.phone.KeyguardBypassController; 112 import com.android.systemui.statusbar.policy.HeadsUpManager; 113 import com.android.systemui.statusbar.policy.InflatedSmartReplyState; 114 import com.android.systemui.statusbar.policy.SmartReplyConstants; 115 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; 116 import com.android.systemui.util.Compile; 117 import com.android.systemui.util.DumpUtilsKt; 118 import com.android.systemui.wmshell.BubblesManager; 119 120 import java.io.PrintWriter; 121 import java.util.ArrayList; 122 import java.util.Arrays; 123 import java.util.List; 124 import java.util.Optional; 125 import java.util.concurrent.TimeUnit; 126 import java.util.function.BooleanSupplier; 127 import java.util.function.Consumer; 128 129 /** 130 * View representing a notification item - this can be either the individual child notification or 131 * the group summary (which contains 1 or more child notifications). 132 */ 133 public class ExpandableNotificationRow extends ActivatableNotificationView 134 implements PluginListener<NotificationMenuRowPlugin>, SwipeableView, 135 NotificationFadeAware.FadeOptimizedNotification { 136 137 private static final String TAG = "ExpandableNotifRow"; 138 private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); 139 private static final boolean DEBUG_ONMEASURE = 140 Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); 141 private static final int MENU_VIEW_INDEX = 0; 142 public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; 143 private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); 144 private static final SourceType BASE_VALUE = SourceType.from("BaseValue"); 145 private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)"); 146 private static final SourceType PINNED = SourceType.from("Pinned"); 147 148 // We don't correctly track dark mode until the content views are inflated, so always update 149 // the background on first content update just in case it happens to be during a theme change. 150 private boolean mUpdateSelfBackgroundOnUpdate = true; 151 private boolean mIsSnoozed; 152 private boolean mShowSnooze = false; 153 private boolean mIsFaded; 154 private boolean mAnimatePinnedRoundness = false; 155 156 /** 157 * Listener for when {@link ExpandableNotificationRow} is laid out. 158 */ 159 public interface LayoutListener { onLayout()160 void onLayout(); 161 } 162 163 /** 164 * Listens for changes to the expansion state of this row. 165 */ 166 public interface OnExpansionChangedListener { onExpansionChanged(boolean isExpanded)167 void onExpansionChanged(boolean isExpanded); 168 } 169 170 private StatusBarStateController mStatusBarStateController; 171 private KeyguardBypassController mBypassController; 172 private LayoutListener mLayoutListener; 173 private RowContentBindStage mRowContentBindStage; 174 private PeopleNotificationIdentifier mPeopleNotificationIdentifier; 175 private Optional<BubblesManager> mBubblesManagerOptional; 176 private MetricsLogger mMetricsLogger; 177 private NotificationChildrenContainerLogger mChildrenContainerLogger; 178 private NotificationDismissibilityProvider mDismissibilityProvider; 179 private FeatureFlags mFeatureFlags; 180 private int mIconTransformContentShift; 181 private int mMaxHeadsUpHeightBeforeN; 182 private int mMaxHeadsUpHeightBeforeP; 183 private int mMaxHeadsUpHeightBeforeS; 184 private int mMaxHeadsUpHeight; 185 private int mMaxHeadsUpHeightIncreased; 186 private int mMaxSmallHeightBeforeN; 187 private int mMaxSmallHeightBeforeP; 188 private int mMaxSmallHeightBeforeS; 189 private int mMaxSmallHeight; 190 private int mMaxSmallHeightLarge; 191 private int mMaxExpandedHeight; 192 private int mNotificationLaunchHeight; 193 private boolean mMustStayOnScreen; 194 195 /** 196 * Does this row contain layouts that can adapt to row expansion 197 */ 198 private boolean mExpandable; 199 /** 200 * Has the user actively changed the expansion state of this row 201 */ 202 private boolean mHasUserChangedExpansion; 203 /** 204 * If {@link #mHasUserChangedExpansion}, has the user expanded this row 205 */ 206 private boolean mUserExpanded; 207 /** 208 * Has this notification been expanded while it was pinned 209 */ 210 private boolean mExpandedWhenPinned; 211 /** 212 * Is the user touching this row 213 */ 214 private boolean mUserLocked; 215 /** 216 * Are we showing the "public" version 217 */ 218 private boolean mShowingPublic; 219 private boolean mSensitive; 220 private boolean mSensitiveHiddenInGeneral; 221 private boolean mShowingPublicInitialized; 222 private boolean mHideSensitiveForIntrinsicHeight; 223 private float mHeaderVisibleAmount = DEFAULT_HEADER_VISIBLE_AMOUNT; 224 225 /** 226 * Is this notification expanded by the system. The expansion state can be overridden by the 227 * user expansion. 228 */ 229 private boolean mIsSystemExpanded; 230 231 /** 232 * Whether the notification is on the keyguard and the expansion is disabled. 233 */ 234 private boolean mOnKeyguard; 235 236 private Animator mTranslateAnim; 237 private ArrayList<View> mTranslateableViews; 238 private NotificationContentView mPublicLayout; 239 private NotificationContentView mPrivateLayout; 240 private NotificationContentView[] mLayouts; 241 private int mNotificationColor; 242 private ExpandableNotificationRowLogger mLogger; 243 private String mLoggingKey; 244 private NotificationGuts mGuts; 245 private NotificationEntry mEntry; 246 private String mAppName; 247 private FalsingManager mFalsingManager; 248 private FalsingCollector mFalsingCollector; 249 250 /** 251 * Whether or not the notification is using the heads up view and should peek from the top. 252 */ 253 private boolean mIsHeadsUp; 254 255 private boolean mLastChronometerRunning = true; 256 private ViewStub mChildrenContainerStub; 257 private GroupMembershipManager mGroupMembershipManager; 258 private GroupExpansionManager mGroupExpansionManager; 259 private boolean mChildrenExpanded; 260 private boolean mIsSummaryWithChildren; 261 private NotificationChildrenContainer mChildrenContainer; 262 private NotificationMenuRowPlugin mMenuRow; 263 private ViewStub mGutsStub; 264 private boolean mIsSystemChildExpanded; 265 private boolean mIsPinned; 266 private boolean mExpandAnimationRunning; 267 private AboveShelfChangedListener mAboveShelfChangedListener; 268 private HeadsUpManager mHeadsUpManager; 269 private Consumer<Boolean> mHeadsUpAnimatingAwayListener; 270 private boolean mChildIsExpanding; 271 272 private boolean mJustClicked; 273 private boolean mAnimationRunning; 274 private boolean mShowNoBackground; 275 private ExpandableNotificationRow mNotificationParent; 276 private OnExpandClickListener mOnExpandClickListener; 277 private View.OnClickListener mOnFeedbackClickListener; 278 private Path mExpandingClipPath; 279 private final ViewRefactorFlag mInlineReplyAnimation = 280 new ViewRefactorFlag(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); 281 282 // Listener will be called when receiving a long click event. 283 // Use #setLongPressPosition to optionally assign positional data with the long press. 284 private LongPressListener mLongPressListener; 285 286 private ExpandableNotificationRowDragController mDragController; 287 288 private boolean mGroupExpansionChanging; 289 290 /** 291 * A supplier that returns true if keyguard is secure. 292 */ 293 private BooleanSupplier mSecureStateProvider; 294 295 /** 296 * Whether or not a notification that is not part of a group of notifications can be manually 297 * expanded by the user. 298 */ 299 private boolean mEnableNonGroupedNotificationExpand; 300 301 /** 302 * Whether or not to update the background of the header of the notification when its expanded. 303 * If {@code true}, the header background will disappear when expanded. 304 */ 305 private boolean mShowGroupBackgroundWhenExpanded; 306 307 /** 308 * True if we always show the collapsed layout on lockscreen because vertical space is low. 309 */ 310 private boolean mSaveSpaceOnLockscreen; 311 312 /** 313 * True if we use intrinsic height regardless of vertical space available on lockscreen. 314 */ 315 private boolean mIgnoreLockscreenConstraints; 316 317 private OnClickListener mExpandClickListener = new OnClickListener() { 318 @Override 319 public void onClick(View v) { 320 if (!shouldShowPublic() && (!mIsLowPriority || isExpanded()) 321 && mGroupMembershipManager.isGroupSummary(mEntry)) { 322 mGroupExpansionChanging = true; 323 final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry); 324 boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(mEntry); 325 mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded); 326 mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, nowExpanded); 327 onExpansionChanged(true /* userAction */, wasExpanded); 328 } else if (mEnableNonGroupedNotificationExpand) { 329 if (v.isAccessibilityFocused()) { 330 mPrivateLayout.setFocusOnVisibilityChange(); 331 } 332 boolean nowExpanded; 333 if (isPinned()) { 334 nowExpanded = !mExpandedWhenPinned; 335 mExpandedWhenPinned = nowExpanded; 336 // Also notify any expansion changed listeners. This is necessary since the 337 // expansion doesn't actually change (it's already system expanded) but it 338 // changes visually 339 if (mExpansionChangedListener != null) { 340 mExpansionChangedListener.onExpansionChanged(nowExpanded); 341 } 342 } else { 343 nowExpanded = !isExpanded(); 344 setUserExpanded(nowExpanded); 345 } 346 notifyHeightChanged(true); 347 mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded); 348 mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded); 349 } 350 } 351 }; 352 private boolean mKeepInParentForDismissAnimation; 353 private boolean mRemoved; 354 public static final FloatProperty<ExpandableNotificationRow> TRANSLATE_CONTENT = 355 new FloatProperty<>("translate") { 356 @Override 357 public void setValue(ExpandableNotificationRow object, float value) { 358 object.setTranslation(value); 359 } 360 361 @Override 362 public Float get(ExpandableNotificationRow object) { 363 return object.getTranslation(); 364 } 365 }; 366 367 private OnClickListener mOnClickListener; 368 private OnDragSuccessListener mOnDragSuccessListener; 369 private boolean mHeadsupDisappearRunning; 370 private View mChildAfterViewWhenDismissed; 371 private View mGroupParentWhenDismissed; 372 private boolean mAboveShelf; 373 private OnUserInteractionCallback mOnUserInteractionCallback; 374 private NotificationGutsManager mNotificationGutsManager; 375 private boolean mIsLowPriority; 376 private boolean mUseIncreasedCollapsedHeight; 377 private boolean mUseIncreasedHeadsUpHeight; 378 private float mTranslationWhenRemoved; 379 private boolean mWasChildInGroupWhenRemoved; 380 private NotificationInlineImageResolver mImageResolver; 381 @Nullable 382 private OnExpansionChangedListener mExpansionChangedListener; 383 @Nullable 384 private Runnable mOnIntrinsicHeightReachedRunnable; 385 386 private float mTopRoundnessDuringLaunchAnimation; 387 private float mBottomRoundnessDuringLaunchAnimation; 388 private float mSmallRoundness; 389 getLayouts()390 public NotificationContentView[] getLayouts() { 391 return Arrays.copyOf(mLayouts, mLayouts.length); 392 } 393 394 /** 395 * Is this entry pinned and was expanded while doing so 396 */ isPinnedAndExpanded()397 public boolean isPinnedAndExpanded() { 398 if (!isPinned()) { 399 return false; 400 } 401 return mExpandedWhenPinned; 402 } 403 404 @Override isGroupExpansionChanging()405 public boolean isGroupExpansionChanging() { 406 if (isChildInGroup()) { 407 return mNotificationParent.isGroupExpansionChanging(); 408 } 409 return mGroupExpansionChanging; 410 } 411 setSaveSpaceOnLockscreen(boolean saveSpace)412 public void setSaveSpaceOnLockscreen(boolean saveSpace) { 413 mSaveSpaceOnLockscreen = saveSpace; 414 } 415 getSaveSpaceOnLockscreen()416 public boolean getSaveSpaceOnLockscreen() { 417 return mSaveSpaceOnLockscreen; 418 } 419 setGroupExpansionChanging(boolean changing)420 public void setGroupExpansionChanging(boolean changing) { 421 mGroupExpansionChanging = changing; 422 } 423 424 @Override setActualHeightAnimating(boolean animating)425 public void setActualHeightAnimating(boolean animating) { 426 if (mPrivateLayout != null) { 427 mPrivateLayout.setContentHeightAnimating(animating); 428 } 429 } 430 getPrivateLayout()431 public NotificationContentView getPrivateLayout() { 432 return mPrivateLayout; 433 } 434 getPublicLayout()435 public NotificationContentView getPublicLayout() { 436 return mPublicLayout; 437 } 438 439 /** 440 * Sets animations running in the layouts of this row, including public, private, and children. 441 * @param running whether the animations should be started running or stopped. 442 */ setAnimationRunning(boolean running)443 public void setAnimationRunning(boolean running) { 444 // Sets animations running in the private/public layouts. 445 for (NotificationContentView l : mLayouts) { 446 if (l != null) { 447 l.setContentAnimationRunning(running); 448 setIconAnimationRunning(running, l); 449 } 450 } 451 // For groups summaries with children, we want to set the children containers 452 // animating as well. 453 if (mIsSummaryWithChildren) { 454 NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper(); 455 if (viewWrapper != null) { 456 setIconAnimationRunningForChild(running, viewWrapper.getIcon()); 457 } 458 NotificationViewWrapper lowPriWrapper = mChildrenContainer.getLowPriorityViewWrapper(); 459 if (lowPriWrapper != null) { 460 setIconAnimationRunningForChild(running, lowPriWrapper.getIcon()); 461 } 462 List<ExpandableNotificationRow> notificationChildren = 463 mChildrenContainer.getAttachedChildren(); 464 for (int i = 0; i < notificationChildren.size(); i++) { 465 ExpandableNotificationRow child = notificationChildren.get(i); 466 child.setAnimationRunning(running); 467 } 468 } 469 mAnimationRunning = running; 470 } 471 472 /** 473 * Starts or stops animations of the icons in all potential content views (regardless of 474 * whether they're contracted, expanded, etc). 475 * 476 * @param running whether to start or stop the icon's animation. 477 */ setIconAnimationRunning(boolean running, NotificationContentView layout)478 private void setIconAnimationRunning(boolean running, NotificationContentView layout) { 479 if (layout != null) { 480 View contractedChild = layout.getContractedChild(); 481 View expandedChild = layout.getExpandedChild(); 482 View headsUpChild = layout.getHeadsUpChild(); 483 setIconAnimationRunningForChild(running, contractedChild); 484 setIconAnimationRunningForChild(running, expandedChild); 485 setIconAnimationRunningForChild(running, headsUpChild); 486 } 487 } 488 489 /** 490 * Starts or stops animations of the icon in the provided view's icon and right icon. 491 * 492 * @param running whether to start or stop the icon's animation. 493 * @param child the view with the icon to start or stop. 494 */ setIconAnimationRunningForChild(boolean running, View child)495 private void setIconAnimationRunningForChild(boolean running, View child) { 496 if (child != null) { 497 ImageView icon = child.findViewById(com.android.internal.R.id.icon); 498 setImageViewAnimationRunning(icon, running); 499 ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon); 500 setImageViewAnimationRunning(rightIcon, running); 501 } 502 } 503 504 /** 505 * Starts or stops the animation of a provided image view if it's an AnimationDrawable or an 506 * AnimatedVectorDrawable. 507 * 508 * @param imageView the image view on which to start/stop animation. 509 * @param running whether to start or stop the view's animation. 510 */ setImageViewAnimationRunning(ImageView imageView, boolean running)511 private void setImageViewAnimationRunning(ImageView imageView, boolean running) { 512 if (imageView != null) { 513 Drawable drawable = imageView.getDrawable(); 514 if (drawable instanceof AnimationDrawable) { 515 AnimationDrawable animationDrawable = (AnimationDrawable) drawable; 516 if (running) { 517 animationDrawable.start(); 518 } else { 519 animationDrawable.stop(); 520 } 521 } else if (drawable instanceof AnimatedVectorDrawable) { 522 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable; 523 if (running) { 524 animationDrawable.start(); 525 } else { 526 animationDrawable.stop(); 527 } 528 } 529 } 530 } 531 532 /** 533 * Marks a content view as freeable, setting it so that future inflations do not reinflate 534 * and ensuring that the view is freed when it is safe to remove. 535 * 536 * @param inflationFlag flag corresponding to the content view to be freed 537 * @deprecated By default, {@link NotificationContentInflater#unbindContent} will tell the 538 * view hierarchy to only free when the view is safe to remove so this method is no longer 539 * needed. Will remove when all uses are gone. 540 */ 541 @Deprecated freeContentViewWhenSafe(@nflationFlag int inflationFlag)542 public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { 543 RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); 544 params.markContentViewsFreeable(inflationFlag); 545 mRowContentBindStage.requestRebind(mEntry, null /* callback */); 546 } 547 548 /** 549 * Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif 550 * or is in an allowList). 551 */ getIsNonblockable()552 public boolean getIsNonblockable() { 553 if (mEntry == null) { 554 return true; 555 } 556 return !mEntry.isBlockable(); 557 } 558 isConversation()559 private boolean isConversation() { 560 return mPeopleNotificationIdentifier.getPeopleNotificationType(mEntry) 561 != PeopleNotificationIdentifier.TYPE_NON_PERSON; 562 } 563 onNotificationUpdated()564 public void onNotificationUpdated() { 565 if (mIsSummaryWithChildren) { 566 Trace.beginSection("ExpNotRow#onNotifUpdated (summary)"); 567 } else { 568 Trace.beginSection("ExpNotRow#onNotifUpdated (leaf)"); 569 } 570 for (NotificationContentView l : mLayouts) { 571 l.onNotificationUpdated(mEntry); 572 } 573 mShowingPublicInitialized = false; 574 updateNotificationColor(); 575 if (mMenuRow != null) { 576 mMenuRow.onNotificationUpdated(mEntry.getSbn()); 577 mMenuRow.setAppName(mAppName); 578 } 579 if (mIsSummaryWithChildren) { 580 mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation()); 581 mChildrenContainer.onNotificationUpdated(); 582 } 583 if (mAnimationRunning) { 584 setAnimationRunning(true); 585 } 586 if (mLastChronometerRunning) { 587 setChronometerRunning(true); 588 } 589 if (mNotificationParent != null) { 590 mNotificationParent.updateChildrenAppearance(); 591 } 592 onAttachedChildrenCountChanged(); 593 // The public layouts expand button is always visible 594 mPublicLayout.updateExpandButtons(true); 595 updateLimits(); 596 updateShelfIconColor(); 597 updateRippleAllowed(); 598 if (mUpdateSelfBackgroundOnUpdate) { 599 // Because this is triggered by UiMode change which we already propagated to children, 600 // we know that child rows will receive the same event, and will update their own 601 // backgrounds when they finish inflating, so propagating again would be redundant. 602 mUpdateSelfBackgroundOnUpdate = false; 603 updateBackgroundColorsOfSelf(); 604 } 605 Trace.endSection(); 606 } 607 updateBackgroundColorsOfSelf()608 private void updateBackgroundColorsOfSelf() { 609 super.updateBackgroundColors(); 610 } 611 612 @Override updateBackgroundColors()613 public void updateBackgroundColors() { 614 // Because this call is made by the NSSL only on attached rows at the moment of the 615 // UiMode or Theme change, we have to propagate to our child views. 616 updateBackgroundColorsOfSelf(); 617 if (mIsSummaryWithChildren) { 618 for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) { 619 child.updateBackgroundColors(); 620 } 621 } 622 } 623 624 /** 625 * Called when the notification's ranking was changed (but nothing else changed). 626 */ onNotificationRankingUpdated()627 public void onNotificationRankingUpdated() { 628 if (mMenuRow != null) { 629 mMenuRow.onNotificationUpdated(mEntry.getSbn()); 630 } 631 } 632 633 /** 634 * Call when bubble state has changed and the button on the notification should be updated. 635 */ updateBubbleButton()636 public void updateBubbleButton() { 637 for (NotificationContentView l : mLayouts) { 638 l.updateBubbleButton(mEntry); 639 } 640 } 641 642 @VisibleForTesting updateShelfIconColor()643 void updateShelfIconColor() { 644 StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon(); 645 boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L)); 646 boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon, 647 ContrastColorUtil.getInstance(mContext)); 648 int color = StatusBarIconView.NO_COLOR; 649 if (colorize) { 650 color = getOriginalIconColor(); 651 } 652 expandedIcon.setStaticDrawableColor(color); 653 } 654 getOriginalIconColor()655 public int getOriginalIconColor() { 656 if (mIsSummaryWithChildren && !shouldShowPublic()) { 657 return mChildrenContainer.getVisibleWrapper().getOriginalIconColor(); 658 } 659 int color = getShowingLayout().getOriginalIconColor(); 660 if (color != Notification.COLOR_INVALID) { 661 return color; 662 } else { 663 return mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(), 664 getBackgroundColorWithoutTint()); 665 } 666 } 667 setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener)668 public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) { 669 mAboveShelfChangedListener = aboveShelfChangedListener; 670 } 671 672 /** 673 * Sets a supplier that can determine whether the keyguard is secure or not. 674 * 675 * @param secureStateProvider A function that returns true if keyguard is secure. 676 */ setSecureStateProvider(BooleanSupplier secureStateProvider)677 public void setSecureStateProvider(BooleanSupplier secureStateProvider) { 678 mSecureStateProvider = secureStateProvider; 679 } 680 updateLimits()681 private void updateLimits() { 682 for (NotificationContentView l : mLayouts) { 683 updateLimitsForView(l); 684 } 685 } 686 updateLimitsForView(NotificationContentView layout)687 private void updateLimitsForView(NotificationContentView layout) { 688 View contractedView = layout.getContractedChild(); 689 boolean customView = contractedView != null 690 && contractedView.getId() 691 != com.android.internal.R.id.status_bar_latest_event_content; 692 boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; 693 boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P; 694 boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S; 695 int smallHeight; 696 697 boolean isCallLayout = contractedView instanceof CallLayout; 698 699 if (customView && beforeS && !mIsSummaryWithChildren) { 700 if (beforeN) { 701 smallHeight = mMaxSmallHeightBeforeN; 702 } else if (beforeP) { 703 smallHeight = mMaxSmallHeightBeforeP; 704 } else { 705 smallHeight = mMaxSmallHeightBeforeS; 706 } 707 } else if (isCallLayout) { 708 smallHeight = mMaxExpandedHeight; 709 } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) { 710 smallHeight = mMaxSmallHeightLarge; 711 } else { 712 smallHeight = mMaxSmallHeight; 713 } 714 boolean headsUpCustom = layout.getHeadsUpChild() != null && 715 layout.getHeadsUpChild().getId() 716 != com.android.internal.R.id.status_bar_latest_event_content; 717 int headsUpHeight; 718 if (headsUpCustom && beforeS) { 719 if (beforeN) { 720 headsUpHeight = mMaxHeadsUpHeightBeforeN; 721 } else if (beforeP) { 722 headsUpHeight = mMaxHeadsUpHeightBeforeP; 723 } else { 724 headsUpHeight = mMaxHeadsUpHeightBeforeS; 725 } 726 } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) { 727 headsUpHeight = mMaxHeadsUpHeightIncreased; 728 } else { 729 headsUpHeight = mMaxHeadsUpHeight; 730 } 731 NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper( 732 VISIBLE_TYPE_HEADSUP); 733 if (headsUpWrapper != null) { 734 headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight()); 735 } 736 layout.setHeights(smallHeight, headsUpHeight, mMaxExpandedHeight); 737 } 738 739 @NonNull 740 public NotificationEntry getEntry() { 741 return mEntry; 742 } 743 744 @Override 745 public boolean isHeadsUp() { 746 return mIsHeadsUp; 747 } 748 749 public void setHeadsUp(boolean isHeadsUp) { 750 boolean wasAboveShelf = isAboveShelf(); 751 int intrinsicBefore = getIntrinsicHeight(); 752 mIsHeadsUp = isHeadsUp; 753 mPrivateLayout.setHeadsUp(isHeadsUp); 754 if (mIsSummaryWithChildren) { 755 // The overflow might change since we allow more lines as HUN. 756 mChildrenContainer.updateGroupOverflow(); 757 } 758 if (intrinsicBefore != getIntrinsicHeight()) { 759 notifyHeightChanged(false /* needsAnimation */); 760 } 761 if (isHeadsUp) { 762 mMustStayOnScreen = true; 763 setAboveShelf(true); 764 } else if (isAboveShelf() != wasAboveShelf) { 765 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 766 } 767 } 768 769 @Override 770 public boolean showingPulsing() { 771 return isHeadsUpState() && (isDozing() || (mOnKeyguard && isBypassEnabled())); 772 } 773 774 /** 775 * @return if the view is in heads up state, i.e either still heads upped or it's disappearing. 776 */ 777 public boolean isHeadsUpState() { 778 return mIsHeadsUp || mHeadsupDisappearRunning; 779 } 780 781 public void setRemoteInputController(RemoteInputController r) { 782 mPrivateLayout.setRemoteInputController(r); 783 } 784 785 public void addChildNotification(ExpandableNotificationRow row) { 786 addChildNotification(row, -1); 787 } 788 789 /** 790 * Set the how much the header should be visible. A value of 0 will make the header fully gone 791 * and a value of 1 will make the notification look just like normal. 792 * This is being used for heads up notifications, when they are pinned to the top of the screen 793 * and the header content is extracted to the statusbar. 794 * 795 * @param headerVisibleAmount the amount the header should be visible. 796 */ 797 public void setHeaderVisibleAmount(float headerVisibleAmount) { 798 if (mHeaderVisibleAmount != headerVisibleAmount) { 799 mHeaderVisibleAmount = headerVisibleAmount; 800 for (NotificationContentView l : mLayouts) { 801 l.setHeaderVisibleAmount(headerVisibleAmount); 802 } 803 if (mChildrenContainer != null) { 804 mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount); 805 } 806 notifyHeightChanged(false /* needsAnimation */); 807 } 808 } 809 810 @Override 811 public float getHeaderVisibleAmount() { 812 return mHeaderVisibleAmount; 813 } 814 815 @Override 816 public void setHeadsUpIsVisible() { 817 super.setHeadsUpIsVisible(); 818 mMustStayOnScreen = false; 819 } 820 821 /** 822 * @see NotificationChildrenContainer#setUntruncatedChildCount(int) 823 */ 824 public void setUntruncatedChildCount(int childCount) { 825 if (mChildrenContainer == null) { 826 mChildrenContainerStub.inflate(); 827 } 828 mChildrenContainer.setUntruncatedChildCount(childCount); 829 } 830 831 /** 832 * @see NotificationChildrenContainer#setNotificationGroupWhen(long) 833 */ 834 public void setNotificationGroupWhen(long whenMillis) { 835 if (mIsSummaryWithChildren) { 836 mChildrenContainer.setNotificationGroupWhen(whenMillis); 837 } else { 838 Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")" 839 + " mIsSummaryWithChildren: false" 840 + " mChildrenContainer has not been inflated yet."); 841 } 842 } 843 844 /** 845 * Called after children have been attached to set the expansion states 846 */ 847 public void resetChildSystemExpandedStates() { 848 if (isSummaryWithChildren()) { 849 mChildrenContainer.updateExpansionStates(); 850 } 851 } 852 853 /** 854 * Add a child notification to this view. 855 * 856 * @param row the row to add 857 * @param childIndex the index to add it at, if -1 it will be added at the end 858 */ 859 public void addChildNotification(ExpandableNotificationRow row, int childIndex) { 860 if (mChildrenContainer == null) { 861 mChildrenContainerStub.inflate(); 862 } 863 864 if (row.keepInParentForDismissAnimation()) { 865 logSkipAttachingKeepInParentChild(row); 866 return; 867 } 868 869 mChildrenContainer.addNotification(row, childIndex); 870 onAttachedChildrenCountChanged(); 871 row.setIsChildInGroup(true, this); 872 } 873 874 public void removeChildNotification(ExpandableNotificationRow row) { 875 if (mChildrenContainer != null) { 876 mChildrenContainer.removeNotification(row); 877 row.setKeepInParentForDismissAnimation(false); 878 } 879 onAttachedChildrenCountChanged(); 880 row.setIsChildInGroup(false, null); 881 } 882 883 /** 884 * Removes the children notifications which were marked to keep for the dismissal animation. 885 */ 886 public void removeChildrenWithKeepInParent() { 887 if (mChildrenContainer == null) return; 888 889 List<ExpandableNotificationRow> clonedList = new ArrayList<>( 890 mChildrenContainer.getAttachedChildren()); 891 boolean childCountChanged = false; 892 for (ExpandableNotificationRow child : clonedList) { 893 if (child.keepInParentForDismissAnimation()) { 894 mChildrenContainer.removeNotification(child); 895 child.setIsChildInGroup(false, null); 896 child.setKeepInParentForDismissAnimation(false); 897 logKeepInParentChildDetached(child); 898 childCountChanged = true; 899 } 900 } 901 902 if (childCountChanged) { 903 onAttachedChildrenCountChanged(); 904 } 905 } 906 907 /** 908 * Returns the child notification at [index], or null if no such child. 909 */ 910 @Nullable 911 public ExpandableNotificationRow getChildNotificationAt(int index) { 912 if (mChildrenContainer == null 913 || mChildrenContainer.getAttachedChildren().size() <= index) { 914 return null; 915 } else { 916 return mChildrenContainer.getAttachedChildren().get(index); 917 } 918 } 919 920 @Override 921 public boolean isChildInGroup() { 922 return mNotificationParent != null; 923 } 924 925 public ExpandableNotificationRow getNotificationParent() { 926 return mNotificationParent; 927 } 928 929 /** 930 * @param isChildInGroup Is this notification now in a group 931 * @param parent the new parent notification 932 */ 933 public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) { 934 if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) { 935 mNotificationParent.setChildIsExpanding(false); 936 mNotificationParent.setExpandingClipPath(null); 937 mNotificationParent.setExtraWidthForClipping(0.0f); 938 mNotificationParent.setMinimumHeightForClipping(0); 939 } 940 mNotificationParent = isChildInGroup ? parent : null; 941 mPrivateLayout.setIsChildInGroup(isChildInGroup); 942 943 updateBackgroundForGroupState(); 944 updateClickAndFocus(); 945 if (mNotificationParent != null) { 946 setOverrideTintColor(NO_COLOR, 0.0f); 947 mNotificationParent.updateBackgroundForGroupState(); 948 } 949 updateBackgroundClipping(); 950 updateBaseRoundness(); 951 } 952 953 @Override 954 public boolean onInterceptTouchEvent(MotionEvent ev) { 955 // Other parts of the system may intercept and handle all the falsing. 956 // Otherwise, if we see motion and follow-on events, try to classify them as a tap. 957 if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) { 958 mFalsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY); 959 } 960 return super.onInterceptTouchEvent(ev); 961 } 962 963 @Override 964 public boolean onTouchEvent(MotionEvent event) { 965 if (event.getActionMasked() != MotionEvent.ACTION_DOWN 966 || !isChildInGroup() || isGroupExpanded()) { 967 return super.onTouchEvent(event); 968 } else { 969 return false; 970 } 971 } 972 973 @Override 974 public boolean isSummaryWithChildren() { 975 return mIsSummaryWithChildren; 976 } 977 978 @Override 979 public boolean areChildrenExpanded() { 980 return mChildrenExpanded; 981 } 982 983 public List<ExpandableNotificationRow> getAttachedChildren() { 984 return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren(); 985 } 986 987 /** 988 * Updates states of all children. 989 */ 990 public void updateChildrenStates() { 991 if (mIsSummaryWithChildren) { 992 ExpandableViewState parentState = getViewState(); 993 mChildrenContainer.updateState(parentState); 994 } 995 } 996 997 /** 998 * Applies children states. 999 */ 1000 public void applyChildrenState() { 1001 if (mIsSummaryWithChildren) { 1002 mChildrenContainer.applyState(); 1003 } 1004 } 1005 1006 /** 1007 * Prepares expansion changed. 1008 */ 1009 public void prepareExpansionChanged() { 1010 if (mIsSummaryWithChildren) { 1011 mChildrenContainer.prepareExpansionChanged(); 1012 } 1013 } 1014 1015 /** 1016 * Starts child animations. 1017 */ 1018 public void startChildAnimation(AnimationProperties properties) { 1019 if (mIsSummaryWithChildren) { 1020 mChildrenContainer.startAnimationToState(properties); 1021 } 1022 } 1023 1024 public ExpandableNotificationRow getViewAtPosition(float y) { 1025 if (!mIsSummaryWithChildren || !mChildrenExpanded) { 1026 return this; 1027 } else { 1028 ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); 1029 return view == null ? this : view; 1030 } 1031 } 1032 1033 public NotificationGuts getGuts() { 1034 return mGuts; 1035 } 1036 1037 /** 1038 * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this 1039 * the notification will be rendered on top of the screen. 1040 * 1041 * @param pinned whether it is pinned 1042 */ 1043 public void setPinned(boolean pinned) { 1044 int intrinsicHeight = getIntrinsicHeight(); 1045 boolean wasAboveShelf = isAboveShelf(); 1046 mIsPinned = pinned; 1047 if (intrinsicHeight != getIntrinsicHeight()) { 1048 notifyHeightChanged(false /* needsAnimation */); 1049 } 1050 if (pinned) { 1051 setAnimationRunning(true); 1052 mExpandedWhenPinned = false; 1053 } else if (mExpandedWhenPinned) { 1054 setUserExpanded(true); 1055 } 1056 setChronometerRunning(mLastChronometerRunning); 1057 if (isAboveShelf() != wasAboveShelf) { 1058 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 1059 } 1060 if (pinned) { 1061 // Should be animated if someone explicitly set it to 0 and the row is shown. 1062 boolean animated = mAnimatePinnedRoundness && isShown(); 1063 requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PINNED, animated); 1064 } else { 1065 requestRoundnessReset(PINNED); 1066 mAnimatePinnedRoundness = true; 1067 } 1068 } 1069 1070 @Override 1071 public boolean isPinned() { 1072 return mIsPinned; 1073 } 1074 1075 @Override 1076 public int getPinnedHeadsUpHeight() { 1077 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); 1078 } 1079 1080 /** 1081 * @param atLeastMinHeight should the value returned be at least the minimum height. 1082 * Used to avoid cyclic calls 1083 * @return the height of the heads up notification when pinned 1084 */ 1085 private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) { 1086 if (mIsSummaryWithChildren) { 1087 return mChildrenContainer.getIntrinsicHeight(); 1088 } 1089 if (mExpandedWhenPinned) { 1090 return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); 1091 } else if (atLeastMinHeight) { 1092 return Math.max(getCollapsedHeight(), getHeadsUpHeight()); 1093 } else { 1094 return getHeadsUpHeight(); 1095 } 1096 } 1097 1098 /** 1099 * Mark whether this notification was just clicked, i.e. the user has just clicked this 1100 * notification in this frame. 1101 */ 1102 public void setJustClicked(boolean justClicked) { 1103 mJustClicked = justClicked; 1104 } 1105 1106 /** 1107 * @return true if this notification has been clicked in this frame, false otherwise 1108 */ 1109 public boolean wasJustClicked() { 1110 return mJustClicked; 1111 } 1112 1113 public void setChronometerRunning(boolean running) { 1114 mLastChronometerRunning = running; 1115 setChronometerRunning(running, mPrivateLayout); 1116 setChronometerRunning(running, mPublicLayout); 1117 if (mChildrenContainer != null) { 1118 List<ExpandableNotificationRow> notificationChildren = 1119 mChildrenContainer.getAttachedChildren(); 1120 for (int i = 0; i < notificationChildren.size(); i++) { 1121 ExpandableNotificationRow child = notificationChildren.get(i); 1122 child.setChronometerRunning(running); 1123 } 1124 } 1125 } 1126 1127 private void setChronometerRunning(boolean running, NotificationContentView layout) { 1128 if (layout != null) { 1129 running = running || isPinned(); 1130 View contractedChild = layout.getContractedChild(); 1131 View expandedChild = layout.getExpandedChild(); 1132 View headsUpChild = layout.getHeadsUpChild(); 1133 setChronometerRunningForChild(running, contractedChild); 1134 setChronometerRunningForChild(running, expandedChild); 1135 setChronometerRunningForChild(running, headsUpChild); 1136 } 1137 } 1138 1139 private void setChronometerRunningForChild(boolean running, View child) { 1140 if (child != null) { 1141 View chronometer = child.findViewById(com.android.internal.R.id.chronometer); 1142 if (chronometer instanceof Chronometer) { 1143 ((Chronometer) chronometer).setStarted(running); 1144 } 1145 } 1146 } 1147 1148 /** 1149 * @return the main notification view wrapper. 1150 */ 1151 public NotificationViewWrapper getNotificationViewWrapper() { 1152 if (mIsSummaryWithChildren) { 1153 return mChildrenContainer.getNotificationViewWrapper(); 1154 } 1155 return mPrivateLayout.getNotificationViewWrapper(); 1156 } 1157 1158 /** 1159 * @return the currently visible notification view wrapper. This can be different from 1160 * {@link #getNotificationViewWrapper()} in case it is a low-priority group. 1161 */ 1162 public NotificationViewWrapper getVisibleNotificationViewWrapper() { 1163 if (mIsSummaryWithChildren && !shouldShowPublic()) { 1164 return mChildrenContainer.getVisibleWrapper(); 1165 } 1166 return getShowingLayout().getVisibleWrapper(); 1167 } 1168 1169 /** 1170 * @return whether the notification row is long clickable or not. 1171 */ 1172 public boolean isNotificationRowLongClickable() { 1173 if (mLongPressListener == null) { 1174 return false; 1175 } 1176 1177 if (!areGutsExposed()) { // guts is not opened 1178 return true; 1179 } 1180 1181 // if it is leave behind, it shouldn't be long clickable. 1182 return !isGutsLeaveBehind(); 1183 } 1184 1185 public void setLongPressListener(LongPressListener longPressListener) { 1186 mLongPressListener = longPressListener; 1187 } 1188 1189 public void setDragController(ExpandableNotificationRowDragController dragController) { 1190 mDragController = dragController; 1191 } 1192 1193 @Override 1194 public void setOnClickListener(@Nullable OnClickListener l) { 1195 super.setOnClickListener(l); 1196 mOnClickListener = l; 1197 updateClickAndFocus(); 1198 } 1199 1200 /** 1201 * The click listener for the bubble button. 1202 */ 1203 public View.OnClickListener getBubbleClickListener() { 1204 return v -> { 1205 if (mBubblesManagerOptional.isPresent()) { 1206 mBubblesManagerOptional.get() 1207 .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */); 1208 } 1209 mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */); 1210 }; 1211 } 1212 1213 /** 1214 * The click listener for the snooze button. 1215 */ 1216 public View.OnClickListener getSnoozeClickListener(MenuItem item) { 1217 return v -> { 1218 // Dismiss a snoozed notification if one is still left behind 1219 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 1220 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 1221 false /* resetMenu */); 1222 mNotificationGutsManager.openGuts(this, 0, 0, item); 1223 mIsSnoozed = true; 1224 }; 1225 } 1226 1227 private void updateClickAndFocus() { 1228 boolean normalChild = !isChildInGroup() || isGroupExpanded(); 1229 boolean clickable = mOnClickListener != null && normalChild; 1230 if (isFocusable() != normalChild) { 1231 setFocusable(normalChild); 1232 } 1233 if (isClickable() != clickable) { 1234 setClickable(clickable); 1235 } 1236 } 1237 1238 public void setGutsView(MenuItem item) { 1239 if (getGuts() != null && item.getGutsView() instanceof NotificationGuts.GutsContent) { 1240 getGuts().setGutsContent((NotificationGuts.GutsContent) item.getGutsView()); 1241 } 1242 } 1243 1244 @Override 1245 public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { 1246 boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null; 1247 if (existed) { 1248 removeView(mMenuRow.getMenuView()); 1249 } 1250 if (plugin == null) { 1251 return; 1252 } 1253 mMenuRow = plugin; 1254 if (mMenuRow.shouldUseDefaultMenuItems()) { 1255 ArrayList<MenuItem> items = new ArrayList<>(); 1256 items.add(NotificationMenuRow.createConversationItem(mContext)); 1257 items.add(NotificationMenuRow.createPartialConversationItem(mContext)); 1258 items.add(NotificationMenuRow.createInfoItem(mContext)); 1259 items.add(NotificationMenuRow.createSnoozeItem(mContext)); 1260 mMenuRow.setMenuItems(items); 1261 } 1262 if (existed) { 1263 createMenu(); 1264 } 1265 } 1266 1267 @Override 1268 public void onPluginDisconnected(NotificationMenuRowPlugin plugin) { 1269 boolean existed = mMenuRow.getMenuView() != null; 1270 mMenuRow = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); 1271 if (existed) { 1272 createMenu(); 1273 } 1274 } 1275 1276 @Override 1277 public boolean hasFinishedInitialization() { 1278 return getEntry().hasFinishedInitialization(); 1279 } 1280 1281 /** 1282 * Get a handle to a NotificationMenuRowPlugin whose menu view has been added to our hierarchy, 1283 * or null if there is no menu row 1284 * 1285 * @return a {@link NotificationMenuRowPlugin}, or null 1286 */ 1287 @Nullable 1288 public NotificationMenuRowPlugin createMenu() { 1289 if (mMenuRow == null) { 1290 return null; 1291 } 1292 if (mMenuRow.getMenuView() == null) { 1293 mMenuRow.createMenu(this, mEntry.getSbn()); 1294 mMenuRow.setAppName(mAppName); 1295 FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 1296 LayoutParams.MATCH_PARENT); 1297 addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp); 1298 } 1299 return mMenuRow; 1300 } 1301 1302 @Nullable 1303 public NotificationMenuRowPlugin getProvider() { 1304 return mMenuRow; 1305 } 1306 1307 @Override 1308 public void onDensityOrFontScaleChanged() { 1309 super.onDensityOrFontScaleChanged(); 1310 initDimens(); 1311 initBackground(); 1312 reInflateViews(); 1313 } 1314 1315 private void reInflateViews() { 1316 Trace.beginSection("ExpandableNotificationRow#reInflateViews"); 1317 // Let's update our childrencontainer. This is intentionally not guarded with 1318 // mIsSummaryWithChildren since we might have had children but not anymore. 1319 if (mChildrenContainer != null) { 1320 mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.getSbn()); 1321 } 1322 if (mGuts != null) { 1323 NotificationGuts oldGuts = mGuts; 1324 int index = indexOfChild(oldGuts); 1325 removeView(oldGuts); 1326 mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate( 1327 R.layout.notification_guts, this, false); 1328 mGuts.setVisibility(oldGuts.isExposed() ? VISIBLE : GONE); 1329 addView(mGuts, index); 1330 } 1331 View oldMenu = mMenuRow == null ? null : mMenuRow.getMenuView(); 1332 if (oldMenu != null) { 1333 int menuIndex = indexOfChild(oldMenu); 1334 removeView(oldMenu); 1335 mMenuRow.createMenu(ExpandableNotificationRow.this, mEntry.getSbn()); 1336 mMenuRow.setAppName(mAppName); 1337 addView(mMenuRow.getMenuView(), menuIndex); 1338 } 1339 for (NotificationContentView l : mLayouts) { 1340 l.reinflate(); 1341 l.reInflateViews(); 1342 } 1343 mEntry.getSbn().clearPackageContext(); 1344 // TODO: Move content inflation logic out of this call 1345 RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); 1346 params.setNeedsReinflation(true); 1347 mRowContentBindStage.requestRebind(mEntry, null /* callback */); 1348 Trace.endSection(); 1349 } 1350 1351 @Override 1352 public void onConfigurationChanged(Configuration newConfig) { 1353 super.onConfigurationChanged(newConfig); 1354 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 1355 mMenuRow.onConfigurationChanged(); 1356 } 1357 if (mImageResolver != null) { 1358 mImageResolver.updateMaxImageSizes(); 1359 } 1360 } 1361 1362 public void onUiModeChanged() { 1363 mUpdateSelfBackgroundOnUpdate = true; 1364 reInflateViews(); 1365 if (mChildrenContainer != null) { 1366 for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) { 1367 child.onUiModeChanged(); 1368 } 1369 } 1370 } 1371 1372 public void setContentBackground(int customBackgroundColor, boolean animate, 1373 NotificationContentView notificationContentView) { 1374 if (getShowingLayout() == notificationContentView) { 1375 setTintColor(customBackgroundColor, animate); 1376 } 1377 } 1378 1379 @Override 1380 protected void setBackgroundTintColor(int color) { 1381 super.setBackgroundTintColor(color); 1382 NotificationContentView view = getShowingLayout(); 1383 if (view != null) { 1384 view.setBackgroundTintColor(color); 1385 } 1386 } 1387 1388 public void closeRemoteInput() { 1389 for (NotificationContentView l : mLayouts) { 1390 l.closeRemoteInput(); 1391 } 1392 } 1393 1394 /** 1395 * Set by how much the single line view should be indented. 1396 */ 1397 public void setSingleLineWidthIndention(int indention) { 1398 mPrivateLayout.setSingleLineWidthIndention(indention); 1399 } 1400 1401 public int getNotificationColor() { 1402 return mNotificationColor; 1403 } 1404 1405 public void updateNotificationColor() { 1406 Configuration currentConfig = getResources().getConfiguration(); 1407 boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 1408 == Configuration.UI_MODE_NIGHT_YES; 1409 1410 mNotificationColor = ContrastColorUtil.resolveContrastColor(mContext, 1411 mEntry.getSbn().getNotification().color, 1412 getBackgroundColorWithoutTint(), nightMode); 1413 } 1414 1415 public HybridNotificationView getSingleLineView() { 1416 return mPrivateLayout.getSingleLineView(); 1417 } 1418 1419 public boolean isOnKeyguard() { 1420 return mOnKeyguard; 1421 } 1422 1423 @Override 1424 public void dismiss(boolean refocusOnDismiss) { 1425 super.dismiss(refocusOnDismiss); 1426 setLongPressListener(null); 1427 setDragController(null); 1428 mGroupParentWhenDismissed = mNotificationParent; 1429 mChildAfterViewWhenDismissed = null; 1430 mEntry.getIcons().getStatusBarIcon().setDismissed(); 1431 if (isChildInGroup()) { 1432 List<ExpandableNotificationRow> notificationChildren = 1433 mNotificationParent.getAttachedChildren(); 1434 int i = notificationChildren.indexOf(this); 1435 if (i != -1 && i < notificationChildren.size() - 1) { 1436 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1); 1437 } 1438 } 1439 } 1440 1441 /** 1442 * @return if this entry should be kept in its parent during removal. 1443 */ 1444 public boolean keepInParentForDismissAnimation() { 1445 return mKeepInParentForDismissAnimation; 1446 } 1447 1448 public void setKeepInParentForDismissAnimation(boolean keepInParent) { 1449 mKeepInParentForDismissAnimation = keepInParent; 1450 } 1451 1452 /** @return true if the User has dismissed this notif's parent */ 1453 public boolean isParentDismissed() { 1454 return getEntry().getDismissState() == PARENT_DISMISSED; 1455 } 1456 1457 @Override 1458 public boolean isRemoved() { 1459 return mRemoved; 1460 } 1461 1462 public void setRemoved() { 1463 mRemoved = true; 1464 mTranslationWhenRemoved = getTranslationY(); 1465 mWasChildInGroupWhenRemoved = isChildInGroup(); 1466 if (isChildInGroup()) { 1467 mTranslationWhenRemoved += getNotificationParent().getTranslationY(); 1468 } 1469 for (NotificationContentView l : mLayouts) { 1470 l.setRemoved(); 1471 } 1472 } 1473 1474 public boolean wasChildInGroupWhenRemoved() { 1475 return mWasChildInGroupWhenRemoved; 1476 } 1477 1478 public float getTranslationWhenRemoved() { 1479 return mTranslationWhenRemoved; 1480 } 1481 1482 public NotificationChildrenContainer getChildrenContainer() { 1483 return mChildrenContainer; 1484 } 1485 1486 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { 1487 boolean wasAboveShelf = isAboveShelf(); 1488 boolean changed = headsUpAnimatingAway != mHeadsupDisappearRunning; 1489 mHeadsupDisappearRunning = headsUpAnimatingAway; 1490 mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway); 1491 if (changed && mHeadsUpAnimatingAwayListener != null) { 1492 mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway); 1493 } 1494 if (isAboveShelf() != wasAboveShelf) { 1495 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 1496 } 1497 } 1498 1499 public void setHeadsUpAnimatingAwayListener(Consumer<Boolean> listener) { 1500 mHeadsUpAnimatingAwayListener = listener; 1501 } 1502 1503 /** 1504 * @return if the view was just heads upped and is now animating away. During such a time the 1505 * layout needs to be kept consistent 1506 */ 1507 @Override 1508 public boolean isHeadsUpAnimatingAway() { 1509 return mHeadsupDisappearRunning; 1510 } 1511 1512 public View getChildAfterViewWhenDismissed() { 1513 return mChildAfterViewWhenDismissed; 1514 } 1515 1516 public View getGroupParentWhenDismissed() { 1517 return mGroupParentWhenDismissed; 1518 } 1519 1520 /** 1521 * Dismisses the notification. 1522 * 1523 * @param fromAccessibility whether this dismiss is coming from an accessibility action 1524 */ 1525 public void performDismiss(boolean fromAccessibility) { 1526 mMetricsLogger.count(NotificationCounters.NOTIFICATION_DISMISSED, 1); 1527 dismiss(fromAccessibility); 1528 if (canEntryBeDismissed()) { 1529 if (mOnUserInteractionCallback != null) { 1530 mOnUserInteractionCallback.registerFutureDismissal(mEntry, REASON_CANCEL).run(); 1531 } 1532 } 1533 } 1534 1535 @Override 1536 public View getShelfTransformationTarget() { 1537 if (mIsSummaryWithChildren && !shouldShowPublic()) { 1538 return mChildrenContainer.getVisibleWrapper().getShelfTransformationTarget(); 1539 } 1540 return getShowingLayout().getShelfTransformationTarget(); 1541 } 1542 1543 /** 1544 * @return whether the notification is currently showing a view with an icon. 1545 */ 1546 public boolean isShowingIcon() { 1547 if (areGutsExposed()) { 1548 return false; 1549 } 1550 return getShelfTransformationTarget() != null; 1551 } 1552 1553 @Override 1554 protected void updateContentTransformation() { 1555 if (mExpandAnimationRunning) { 1556 return; 1557 } 1558 super.updateContentTransformation(); 1559 } 1560 1561 @Override 1562 protected void applyContentTransformation(float contentAlpha, float translationY) { 1563 super.applyContentTransformation(contentAlpha, translationY); 1564 if (!mIsLastChild) { 1565 // Don't fade views unless we're last 1566 contentAlpha = 1.0f; 1567 } 1568 for (NotificationContentView l : mLayouts) { 1569 l.setAlpha(contentAlpha); 1570 l.setTranslationY(translationY); 1571 } 1572 if (mChildrenContainer != null) { 1573 mChildrenContainer.setAlpha(contentAlpha); 1574 mChildrenContainer.setTranslationY(translationY); 1575 // TODO: handle children fade out better 1576 } 1577 } 1578 1579 /** 1580 * Sets the alpha on the content, while leaving the background of the row itself as is. 1581 * 1582 * @param alpha alpha value to apply to the notification content 1583 */ 1584 public void setContentAlpha(float alpha) { 1585 for (NotificationContentView l : mLayouts) { 1586 l.setAlpha(alpha); 1587 } 1588 if (mChildrenContainer != null) { 1589 mChildrenContainer.setContentAlpha(alpha); 1590 } 1591 } 1592 1593 public void setIsLowPriority(boolean isLowPriority) { 1594 mIsLowPriority = isLowPriority; 1595 mPrivateLayout.setIsLowPriority(isLowPriority); 1596 if (mChildrenContainer != null) { 1597 mChildrenContainer.setIsLowPriority(isLowPriority); 1598 } 1599 } 1600 1601 public boolean isLowPriority() { 1602 return mIsLowPriority; 1603 } 1604 1605 public void setUsesIncreasedCollapsedHeight(boolean use) { 1606 mUseIncreasedCollapsedHeight = use; 1607 } 1608 1609 public void setUsesIncreasedHeadsUpHeight(boolean use) { 1610 mUseIncreasedHeadsUpHeight = use; 1611 } 1612 1613 /** 1614 * Interface for logging {{@link ExpandableNotificationRow} events.} 1615 */ 1616 public interface ExpandableNotificationRowLogger { 1617 /** 1618 * Called when the notification is expanded / collapsed. 1619 */ 1620 void logNotificationExpansion(String key, boolean userAction, boolean expanded); 1621 1622 /** 1623 * Called when a notification which was previously kept in its parent for the 1624 * dismiss animation is finally detached from its parent. 1625 */ 1626 void logKeepInParentChildDetached(NotificationEntry child, NotificationEntry oldParent); 1627 1628 /** 1629 * Called when we want to attach a notification to a new parent, 1630 * but it still has the keepInParent flag set, so we skip it. 1631 */ 1632 void logSkipAttachingKeepInParentChild( 1633 NotificationEntry child, 1634 NotificationEntry newParent 1635 ); 1636 1637 /** 1638 * Called when an ExpandableNotificationRow transient view is removed from the 1639 * NotificationChildrenContainer 1640 */ 1641 void logRemoveTransientFromContainer( 1642 NotificationEntry childEntry, 1643 NotificationEntry containerEntry 1644 ); 1645 1646 /** 1647 * Called when an ExpandableNotificationRow transient view is removed from the 1648 * NotificationStackScrollLayout 1649 */ 1650 void logRemoveTransientFromNssl( 1651 NotificationEntry childEntry 1652 ); 1653 1654 /** 1655 * Called when an ExpandableNotificationRow transient view is removed from a ViewGroup that 1656 * is not NotificationChildrenContainer or NotificationStackScrollLayout 1657 */ 1658 void logRemoveTransientFromViewGroup( 1659 NotificationEntry childEntry, 1660 ViewGroup containerView 1661 ); 1662 1663 /** 1664 * Called when an ExpandableNotificationRow transient view is added to this 1665 * ExpandableNotificationRow 1666 */ 1667 void logAddTransientRow( 1668 NotificationEntry childEntry, 1669 NotificationEntry containerEntry, 1670 int index 1671 ); 1672 1673 /** 1674 * Called when an ExpandableNotificationRow transient view is removed from this 1675 * ExpandableNotificationRow 1676 */ 1677 void logRemoveTransientRow( 1678 NotificationEntry childEntry, 1679 NotificationEntry containerEntry 1680 ); 1681 } 1682 1683 /** 1684 * Constructs an ExpandableNotificationRow. 1685 * @param context context passed to image resolver 1686 * @param attrs attributes used to initialize parent view 1687 */ 1688 public ExpandableNotificationRow(Context context, AttributeSet attrs) { 1689 super(context, attrs); 1690 mImageResolver = new NotificationInlineImageResolver(context, 1691 new NotificationInlineImageCache()); 1692 float radius = getResources().getDimension(R.dimen.notification_corner_radius_small); 1693 mSmallRoundness = radius / getMaxRadius(); 1694 initDimens(); 1695 } 1696 1697 /** 1698 * Initialize row. 1699 */ 1700 public void initialize( 1701 NotificationEntry entry, 1702 RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, 1703 String appName, 1704 String notificationKey, 1705 ExpandableNotificationRowLogger logger, 1706 KeyguardBypassController bypassController, 1707 GroupMembershipManager groupMembershipManager, 1708 GroupExpansionManager groupExpansionManager, 1709 HeadsUpManager headsUpManager, 1710 RowContentBindStage rowContentBindStage, 1711 OnExpandClickListener onExpandClickListener, 1712 CoordinateOnClickListener onFeedbackClickListener, 1713 FalsingManager falsingManager, 1714 FalsingCollector falsingCollector, 1715 StatusBarStateController statusBarStateController, 1716 PeopleNotificationIdentifier peopleNotificationIdentifier, 1717 OnUserInteractionCallback onUserInteractionCallback, 1718 Optional<BubblesManager> bubblesManagerOptional, 1719 NotificationGutsManager gutsManager, 1720 NotificationDismissibilityProvider dismissibilityProvider, 1721 MetricsLogger metricsLogger, 1722 NotificationChildrenContainerLogger childrenContainerLogger, 1723 SmartReplyConstants smartReplyConstants, 1724 SmartReplyController smartReplyController, 1725 FeatureFlags featureFlags, 1726 IStatusBarService statusBarService) { 1727 mEntry = entry; 1728 mAppName = appName; 1729 if (mMenuRow == null) { 1730 mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier); 1731 } 1732 if (mMenuRow.getMenuView() != null) { 1733 mMenuRow.setAppName(mAppName); 1734 } 1735 mLogger = logger; 1736 mLoggingKey = notificationKey; 1737 mBypassController = bypassController; 1738 mGroupMembershipManager = groupMembershipManager; 1739 mGroupExpansionManager = groupExpansionManager; 1740 mPrivateLayout.setGroupMembershipManager(groupMembershipManager); 1741 mHeadsUpManager = headsUpManager; 1742 mRowContentBindStage = rowContentBindStage; 1743 mOnExpandClickListener = onExpandClickListener; 1744 setOnFeedbackClickListener(onFeedbackClickListener); 1745 mFalsingManager = falsingManager; 1746 mFalsingCollector = falsingCollector; 1747 mStatusBarStateController = statusBarStateController; 1748 mPeopleNotificationIdentifier = peopleNotificationIdentifier; 1749 for (NotificationContentView l : mLayouts) { 1750 l.initialize( 1751 mPeopleNotificationIdentifier, 1752 rivSubcomponentFactory, 1753 smartReplyConstants, 1754 smartReplyController, 1755 statusBarService); 1756 } 1757 mOnUserInteractionCallback = onUserInteractionCallback; 1758 mBubblesManagerOptional = bubblesManagerOptional; 1759 mNotificationGutsManager = gutsManager; 1760 mMetricsLogger = metricsLogger; 1761 mChildrenContainerLogger = childrenContainerLogger; 1762 mDismissibilityProvider = dismissibilityProvider; 1763 mFeatureFlags = featureFlags; 1764 } 1765 1766 private void initDimens() { 1767 mMaxSmallHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, 1768 R.dimen.notification_min_height_legacy); 1769 mMaxSmallHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, 1770 R.dimen.notification_min_height_before_p); 1771 mMaxSmallHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext, 1772 R.dimen.notification_min_height_before_s); 1773 mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext, 1774 R.dimen.notification_min_height); 1775 mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext, 1776 R.dimen.notification_min_height_increased); 1777 mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext, 1778 R.dimen.notification_max_height); 1779 mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, 1780 R.dimen.notification_max_heads_up_height_legacy); 1781 mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, 1782 R.dimen.notification_max_heads_up_height_before_p); 1783 mMaxHeadsUpHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext, 1784 R.dimen.notification_max_heads_up_height_before_s); 1785 mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext, 1786 R.dimen.notification_max_heads_up_height); 1787 mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext, 1788 R.dimen.notification_max_heads_up_height_increased); 1789 1790 Resources res = getResources(); 1791 mEnableNonGroupedNotificationExpand = 1792 res.getBoolean(R.bool.config_enableNonGroupedNotificationExpand); 1793 mShowGroupBackgroundWhenExpanded = 1794 res.getBoolean(R.bool.config_showGroupNotificationBgWhenExpanded); 1795 } 1796 1797 NotificationInlineImageResolver getImageResolver() { 1798 return mImageResolver; 1799 } 1800 1801 /** 1802 * Resets this view so it can be re-used for an updated notification. 1803 */ 1804 public void reset() { 1805 mShowingPublicInitialized = false; 1806 unDismiss(); 1807 if (mMenuRow == null || !mMenuRow.isMenuVisible()) { 1808 resetTranslation(); 1809 } 1810 onHeightReset(); 1811 requestLayout(); 1812 1813 setTargetPoint(null); 1814 } 1815 1816 /** 1817 * Shows the given feedback icon, or hides the icon if null. 1818 */ 1819 public void setFeedbackIcon(@Nullable FeedbackIcon icon) { 1820 if (mIsSummaryWithChildren) { 1821 mChildrenContainer.setFeedbackIcon(icon); 1822 } 1823 mPrivateLayout.setFeedbackIcon(icon); 1824 mPublicLayout.setFeedbackIcon(icon); 1825 } 1826 1827 /** 1828 * Sets the last time the notification being displayed audibly alerted the user. 1829 */ 1830 public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) { 1831 long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs; 1832 boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS; 1833 1834 applyAudiblyAlertedRecently(alertedRecently); 1835 1836 removeCallbacks(mExpireRecentlyAlertedFlag); 1837 if (alertedRecently) { 1838 long timeUntilNoLongerRecent = RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly; 1839 postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent); 1840 } 1841 } 1842 1843 @VisibleForTesting 1844 protected void setEntry(NotificationEntry entry) { 1845 mEntry = entry; 1846 } 1847 1848 private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false); 1849 1850 private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) { 1851 if (mIsSummaryWithChildren) { 1852 mChildrenContainer.setRecentlyAudiblyAlerted(audiblyAlertedRecently); 1853 } 1854 mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); 1855 mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); 1856 } 1857 1858 public View.OnClickListener getFeedbackOnClickListener() { 1859 return mOnFeedbackClickListener; 1860 } 1861 1862 void setOnFeedbackClickListener(CoordinateOnClickListener l) { 1863 mOnFeedbackClickListener = v -> { 1864 createMenu(); 1865 NotificationMenuRowPlugin provider = getProvider(); 1866 if (provider == null) { 1867 return; 1868 } 1869 MenuItem menuItem = provider.getFeedbackMenuItem(mContext); 1870 if (menuItem != null) { 1871 l.onClick(this, v.getWidth() / 2, v.getHeight() / 2, menuItem); 1872 } 1873 }; 1874 } 1875 1876 @Override 1877 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1878 Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure")); 1879 if (DEBUG_ONMEASURE) { 1880 Log.d(TAG, "onMeasure(" 1881 + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", " 1882 + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")"); 1883 } 1884 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1885 Trace.endSection(); 1886 } 1887 1888 /** 1889 * Generates and appends "(MessagingStyle)" type tag to passed string for tracing. 1890 */ 1891 @NonNull 1892 private String appendTraceStyleTag(@NonNull String traceTag) { 1893 if (!Trace.isEnabled()) { 1894 return traceTag; 1895 } 1896 1897 if (isSummaryWithChildren()) { 1898 return traceTag + "(summary)"; 1899 } 1900 Class<? extends Notification.Style> style = 1901 getEntry().getSbn().getNotification().getNotificationStyle(); 1902 if (style == null) { 1903 return traceTag + "(nostyle)"; 1904 } else { 1905 return traceTag + "(" + style.getSimpleName() + ")"; 1906 } 1907 } 1908 1909 @Override 1910 protected void onFinishInflate() { 1911 super.onFinishInflate(); 1912 mPublicLayout = findViewById(R.id.expandedPublic); 1913 mPrivateLayout = findViewById(R.id.expanded); 1914 mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; 1915 1916 for (NotificationContentView l : mLayouts) { 1917 l.setExpandClickListener(mExpandClickListener); 1918 l.setContainingNotification(this); 1919 } 1920 mGutsStub = findViewById(R.id.notification_guts_stub); 1921 mGutsStub.setOnInflateListener((stub, inflated) -> { 1922 mGuts = (NotificationGuts) inflated; 1923 mGuts.setClipTopAmount(getClipTopAmount()); 1924 mGuts.setActualHeight(getActualHeight()); 1925 mGutsStub = null; 1926 }); 1927 mChildrenContainerStub = findViewById(R.id.child_container_stub); 1928 mChildrenContainerStub.setOnInflateListener((stub, inflated) -> { 1929 mChildrenContainer = (NotificationChildrenContainer) inflated; 1930 mChildrenContainer.setIsLowPriority(mIsLowPriority); 1931 mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); 1932 mChildrenContainer.onNotificationUpdated(); 1933 mChildrenContainer.setLogger(mChildrenContainerLogger); 1934 1935 mTranslateableViews.add(mChildrenContainer); 1936 }); 1937 1938 // Add the views that we translate to reveal the menu 1939 mTranslateableViews = new ArrayList<>(); 1940 for (int i = 0; i < getChildCount(); i++) { 1941 mTranslateableViews.add(getChildAt(i)); 1942 } 1943 // Remove views that don't translate 1944 mTranslateableViews.remove(mChildrenContainerStub); 1945 mTranslateableViews.remove(mGutsStub); 1946 } 1947 1948 /** 1949 * Called once when starting drag motion after opening notification guts, 1950 * in case of notification that has {@link android.app.Notification#contentIntent} 1951 * and it is to start an activity. 1952 */ 1953 public void doDragCallback(float x, float y) { 1954 if (mDragController != null) { 1955 setTargetPoint(new Point((int) x, (int) y)); 1956 mDragController.startDragAndDrop(this); 1957 } 1958 } 1959 1960 public void setOnDragSuccessListener(OnDragSuccessListener listener) { 1961 mOnDragSuccessListener = listener; 1962 } 1963 1964 /** 1965 * Called when a notification is dropped on proper target window. 1966 */ 1967 public void dragAndDropSuccess() { 1968 if (mOnDragSuccessListener != null) { 1969 mOnDragSuccessListener.onDragSuccess(getEntry()); 1970 } 1971 } 1972 1973 private void doLongClickCallback() { 1974 doLongClickCallback(getWidth() / 2, getHeight() / 2); 1975 } 1976 1977 public void doLongClickCallback(int x, int y) { 1978 createMenu(); 1979 NotificationMenuRowPlugin provider = getProvider(); 1980 MenuItem menuItem = null; 1981 if (provider != null) { 1982 menuItem = provider.getLongpressMenuItem(mContext); 1983 } 1984 doLongClickCallback(x, y, menuItem); 1985 } 1986 1987 /** 1988 * Perform a smart action which triggers a longpress (expose guts). 1989 * Based on the semanticAction passed, may update the state of the guts view. 1990 * 1991 * @param semanticAction associated with this smart action click 1992 */ 1993 public void doSmartActionClick(int x, int y, int semanticAction) { 1994 createMenu(); 1995 NotificationMenuRowPlugin provider = getProvider(); 1996 MenuItem menuItem = null; 1997 if (provider != null) { 1998 menuItem = provider.getLongpressMenuItem(mContext); 1999 } 2000 if (SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == semanticAction 2001 && menuItem.getGutsView() instanceof NotificationConversationInfo) { 2002 NotificationConversationInfo info = 2003 (NotificationConversationInfo) menuItem.getGutsView(); 2004 info.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE); 2005 } 2006 doLongClickCallback(x, y, menuItem); 2007 } 2008 2009 private void doLongClickCallback(int x, int y, MenuItem menuItem) { 2010 if (mLongPressListener != null && menuItem != null) { 2011 mLongPressListener.onLongPress(this, x, y, menuItem); 2012 } 2013 } 2014 2015 @Override 2016 public boolean onKeyDown(int keyCode, KeyEvent event) { 2017 if (KeyEvent.isConfirmKey(keyCode)) { 2018 event.startTracking(); 2019 return true; 2020 } 2021 return super.onKeyDown(keyCode, event); 2022 } 2023 2024 @Override 2025 public boolean onKeyUp(int keyCode, KeyEvent event) { 2026 if (KeyEvent.isConfirmKey(keyCode)) { 2027 if (!event.isCanceled()) { 2028 performClick(); 2029 } 2030 return true; 2031 } 2032 return super.onKeyUp(keyCode, event); 2033 } 2034 2035 @Override 2036 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 2037 if (KeyEvent.isConfirmKey(keyCode)) { 2038 doLongClickCallback(); 2039 return true; 2040 } 2041 return false; 2042 } 2043 2044 public void resetTranslation() { 2045 if (mTranslateAnim != null) { 2046 mTranslateAnim.cancel(); 2047 } 2048 2049 if (mDismissUsingRowTranslationX) { 2050 setTranslationX(0); 2051 } else if (mTranslateableViews != null) { 2052 for (int i = 0; i < mTranslateableViews.size(); i++) { 2053 mTranslateableViews.get(i).setTranslationX(0); 2054 } 2055 invalidateOutline(); 2056 getEntry().getIcons().getShelfIcon().setScrollX(0); 2057 } 2058 2059 if (mMenuRow != null) { 2060 mMenuRow.resetMenu(); 2061 } 2062 } 2063 2064 void onGutsOpened() { 2065 resetTranslation(); 2066 updateContentAccessibilityImportanceForGuts(false /* isEnabled */); 2067 } 2068 2069 void onGutsClosed() { 2070 updateContentAccessibilityImportanceForGuts(true /* isEnabled */); 2071 mIsSnoozed = false; 2072 } 2073 2074 /** 2075 * Updates whether all the non-guts content inside this row is important for accessibility. 2076 * 2077 * @param isEnabled whether the content views should be enabled for accessibility 2078 */ 2079 private void updateContentAccessibilityImportanceForGuts(boolean isEnabled) { 2080 updateAccessibilityImportance(isEnabled); 2081 2082 if (mChildrenContainer != null) { 2083 updateChildAccessibilityImportance(mChildrenContainer, isEnabled); 2084 } 2085 if (mLayouts != null) { 2086 for (View view : mLayouts) { 2087 updateChildAccessibilityImportance(view, isEnabled); 2088 } 2089 } 2090 2091 if (isEnabled) { 2092 this.requestAccessibilityFocus(); 2093 } 2094 } 2095 2096 /** 2097 * Updates whether this view is important for accessibility based on {@code isEnabled}. 2098 */ 2099 private void updateAccessibilityImportance(boolean isEnabled) { 2100 setImportantForAccessibility(isEnabled 2101 ? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO 2102 : View.IMPORTANT_FOR_ACCESSIBILITY_NO); 2103 } 2104 2105 /** 2106 * Updates whether the given childView is important for accessibility based on 2107 * {@code isEnabled}. 2108 */ 2109 private void updateChildAccessibilityImportance(View childView, boolean isEnabled) { 2110 childView.setImportantForAccessibility(isEnabled 2111 ? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO 2112 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 2113 } 2114 2115 public CharSequence getActiveRemoteInputText() { 2116 return mPrivateLayout.getActiveRemoteInputText(); 2117 } 2118 2119 /** 2120 * Reset the translation with an animation. 2121 */ 2122 public void animateResetTranslation() { 2123 if (mTranslateAnim != null) { 2124 mTranslateAnim.cancel(); 2125 } 2126 mTranslateAnim = getTranslateViewAnimator(0, null /* updateListener */); 2127 if (mTranslateAnim != null) { 2128 mTranslateAnim.start(); 2129 } 2130 } 2131 2132 /** 2133 * Set the dismiss behavior of the view. 2134 * 2135 * @param usingRowTranslationX {@code true} if the view should translate using regular 2136 * translationX, otherwise the contents will be 2137 * translated. 2138 */ 2139 @Override 2140 public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) { 2141 if (usingRowTranslationX != mDismissUsingRowTranslationX) { 2142 // In case we were already transitioning, let's switch over! 2143 float previousTranslation = getTranslation(); 2144 if (previousTranslation != 0) { 2145 setTranslation(0); 2146 } 2147 super.setDismissUsingRowTranslationX(usingRowTranslationX); 2148 if (previousTranslation != 0) { 2149 setTranslation(previousTranslation); 2150 } 2151 if (mChildrenContainer != null) { 2152 List<ExpandableNotificationRow> notificationChildren = 2153 mChildrenContainer.getAttachedChildren(); 2154 for (int i = 0; i < notificationChildren.size(); i++) { 2155 ExpandableNotificationRow child = notificationChildren.get(i); 2156 child.setDismissUsingRowTranslationX(usingRowTranslationX); 2157 } 2158 } 2159 } 2160 } 2161 2162 @Override 2163 public void setTranslation(float translationX) { 2164 invalidate(); 2165 if (mDismissUsingRowTranslationX) { 2166 setTranslationX(translationX); 2167 } else if (mTranslateableViews != null) { 2168 // Translate the group of views 2169 for (int i = 0; i < mTranslateableViews.size(); i++) { 2170 if (mTranslateableViews.get(i) != null) { 2171 mTranslateableViews.get(i).setTranslationX(translationX); 2172 } 2173 } 2174 invalidateOutline(); 2175 2176 // In order to keep the shelf in sync with this swiping, we're simply translating 2177 // it's icon by the same amount. The translation is already being used for the normal 2178 // positioning, so we can use the scrollX instead. 2179 getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX); 2180 } 2181 2182 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 2183 mMenuRow.onParentTranslationUpdate(translationX); 2184 } 2185 } 2186 2187 @Override 2188 public float getTranslation() { 2189 if (mDismissUsingRowTranslationX) { 2190 return getTranslationX(); 2191 } 2192 2193 if (mTranslateableViews != null && mTranslateableViews.size() > 0) { 2194 // All of the views in the list should have same translation, just use first one. 2195 return mTranslateableViews.get(0).getTranslationX(); 2196 } 2197 2198 return 0; 2199 } 2200 2201 public Animator getTranslateViewAnimator(final float leftTarget, 2202 AnimatorUpdateListener listener) { 2203 if (mTranslateAnim != null) { 2204 mTranslateAnim.cancel(); 2205 } 2206 2207 final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT, 2208 leftTarget); 2209 if (listener != null) { 2210 translateAnim.addUpdateListener(listener); 2211 } 2212 translateAnim.addListener(new AnimatorListenerAdapter() { 2213 boolean cancelled = false; 2214 2215 @Override 2216 public void onAnimationCancel(Animator anim) { 2217 cancelled = true; 2218 } 2219 2220 @Override 2221 public void onAnimationEnd(Animator anim) { 2222 if (!cancelled && leftTarget == 0) { 2223 if (mMenuRow != null) { 2224 mMenuRow.resetMenu(); 2225 } 2226 mTranslateAnim = null; 2227 } 2228 } 2229 }); 2230 mTranslateAnim = translateAnim; 2231 return translateAnim; 2232 } 2233 2234 /** Cancels the ongoing translate animation if there is any. */ 2235 public void cancelTranslateAnimation() { 2236 if (mTranslateAnim != null) { 2237 mTranslateAnim.cancel(); 2238 } 2239 } 2240 2241 void ensureGutsInflated() { 2242 if (mGuts == null) { 2243 mGutsStub.inflate(); 2244 } 2245 } 2246 2247 private void updateChildrenVisibility() { 2248 boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null 2249 && mGuts.isExposed(); 2250 mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren 2251 && !hideContentWhileLaunching ? VISIBLE : INVISIBLE); 2252 if (mChildrenContainer != null) { 2253 mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren 2254 && !hideContentWhileLaunching ? VISIBLE 2255 : INVISIBLE); 2256 } 2257 // The limits might have changed if the view suddenly became a group or vice versa 2258 updateLimits(); 2259 } 2260 2261 @Override 2262 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 2263 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 2264 // Add a record for the entire layout since its content is somehow small. 2265 // The event comes from a leaf view that is interacted with. 2266 AccessibilityEvent record = AccessibilityEvent.obtain(); 2267 onInitializeAccessibilityEvent(record); 2268 dispatchPopulateAccessibilityEvent(record); 2269 event.appendRecord(record); 2270 return true; 2271 } 2272 return false; 2273 } 2274 2275 2276 public void applyLaunchAnimationParams(LaunchAnimationParameters params) { 2277 if (params == null) { 2278 // `null` params indicates the animation is over, which means we can't access 2279 // params.getParentStartClipTopAmount() which has the value we want to restore. 2280 // Fortunately, only NotificationShelf actually uses these values for anything other 2281 // than this launch animation, so we can restore the value to 0 and it's right for now. 2282 if (mNotificationParent != null) { 2283 mNotificationParent.setClipTopAmount(0); 2284 } 2285 setTranslationX(0); 2286 return; 2287 } 2288 2289 if (!params.getVisible()) { 2290 if (getVisibility() == View.VISIBLE) { 2291 setVisibility(View.INVISIBLE); 2292 } 2293 return; 2294 } 2295 2296 float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( 2297 params.getProgress(0, 50)); 2298 float translationZ = MathUtils.lerp(params.getStartTranslationZ(), 2299 mNotificationLaunchHeight, 2300 zProgress); 2301 setTranslationZ(translationZ); 2302 float extraWidthForClipping = params.getWidth() - getWidth(); 2303 setExtraWidthForClipping(extraWidthForClipping); 2304 2305 int top; 2306 if (params.getStartRoundedTopClipping() > 0) { 2307 // If we were clipping initially, let's interpolate from the start position to the 2308 // top. Otherwise, we just take the top directly. 2309 float expandProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( 2310 params.getProgress(0, 2311 NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING)); 2312 int startTop = params.getStartNotificationTop(); 2313 top = (int) Math.min(MathUtils.lerp(startTop, params.getTop(), expandProgress), 2314 startTop); 2315 } else { 2316 top = params.getTop(); 2317 } 2318 int actualHeight = params.getBottom() - top; 2319 setActualHeight(actualHeight); 2320 2321 int notificationStackTop = params.getNotificationParentTop(); 2322 top -= notificationStackTop; 2323 int startClipTopAmount = params.getStartClipTopAmount(); 2324 int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, params.getProgress()); 2325 if (mNotificationParent != null) { 2326 float parentTranslationY = mNotificationParent.getTranslationY(); 2327 top -= parentTranslationY; 2328 mNotificationParent.setTranslationZ(translationZ); 2329 2330 // When the expanding notification is below its parent, the parent must be clipped 2331 // exactly how it was clipped before the animation. When the expanding notification is 2332 // on or above its parent (top <= 0), then the parent must be clipped exactly 'top' 2333 // pixels to show the expanding notification, while still taking the decreasing 2334 // notification clipTopAmount into consideration, so 'top + clipTopAmount'. 2335 int parentStartClipTopAmount = params.getParentStartClipTopAmount(); 2336 int parentClipTopAmount = Math.min(parentStartClipTopAmount, top + clipTopAmount); 2337 mNotificationParent.setClipTopAmount(parentClipTopAmount); 2338 2339 mNotificationParent.setExtraWidthForClipping(extraWidthForClipping); 2340 float clipBottom = Math.max(params.getBottom() - notificationStackTop, 2341 parentTranslationY + mNotificationParent.getActualHeight() 2342 - mNotificationParent.getClipBottomAmount()); 2343 float clipTop = Math.min(params.getTop() - notificationStackTop, parentTranslationY); 2344 int minimumHeightForClipping = (int) (clipBottom - clipTop); 2345 mNotificationParent.setMinimumHeightForClipping(minimumHeightForClipping); 2346 } else if (startClipTopAmount != 0) { 2347 setClipTopAmount(clipTopAmount); 2348 } 2349 setTranslationY(top); 2350 2351 float absoluteCenterX = getLocationOnScreen()[0] + getWidth() / 2f - getTranslationX(); 2352 setTranslationX(params.getCenterX() - absoluteCenterX); 2353 2354 final float maxRadius = getMaxRadius(); 2355 mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / maxRadius; 2356 mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / maxRadius; 2357 invalidateOutline(); 2358 2359 mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight); 2360 } 2361 2362 public void setExpandAnimationRunning(boolean expandAnimationRunning) { 2363 if (expandAnimationRunning) { 2364 setAboveShelf(true); 2365 mExpandAnimationRunning = true; 2366 getViewState().cancelAnimations(this); 2367 mNotificationLaunchHeight = AmbientState.getNotificationLaunchHeight(getContext()); 2368 } else { 2369 mExpandAnimationRunning = false; 2370 setAboveShelf(isAboveShelf()); 2371 setVisibility(View.VISIBLE); 2372 if (mGuts != null) { 2373 mGuts.setAlpha(1.0f); 2374 } 2375 resetAllContentAlphas(); 2376 setExtraWidthForClipping(0.0f); 2377 if (mNotificationParent != null) { 2378 mNotificationParent.setExtraWidthForClipping(0.0f); 2379 mNotificationParent.setMinimumHeightForClipping(0); 2380 } 2381 } 2382 if (mNotificationParent != null) { 2383 mNotificationParent.setChildIsExpanding(mExpandAnimationRunning); 2384 } 2385 updateChildrenVisibility(); 2386 updateClipping(); 2387 mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning); 2388 } 2389 2390 private void setChildIsExpanding(boolean isExpanding) { 2391 mChildIsExpanding = isExpanding; 2392 updateClipping(); 2393 invalidate(); 2394 } 2395 2396 @Override 2397 public boolean hasExpandingChild() { 2398 return mChildIsExpanding; 2399 } 2400 2401 @Override 2402 public @NonNull StatusBarIconView getShelfIcon() { 2403 return getEntry().getIcons().getShelfIcon(); 2404 } 2405 2406 @Override 2407 protected boolean shouldClipToActualHeight() { 2408 return super.shouldClipToActualHeight() && !mExpandAnimationRunning; 2409 } 2410 2411 @Override 2412 public boolean isExpandAnimationRunning() { 2413 return mExpandAnimationRunning; 2414 } 2415 2416 /** 2417 * Tap sounds should not be played when we're unlocking. 2418 * Doing so would cause audio collision and the system would feel unpolished. 2419 */ 2420 @Override 2421 public boolean isSoundEffectsEnabled() { 2422 final boolean mute = mStatusBarStateController != null 2423 && mStatusBarStateController.isDozing() 2424 && mSecureStateProvider != null && 2425 !mSecureStateProvider.getAsBoolean(); 2426 return !mute && super.isSoundEffectsEnabled(); 2427 } 2428 2429 public boolean isExpandable() { 2430 if (mIsSummaryWithChildren && !shouldShowPublic()) { 2431 return !mChildrenExpanded; 2432 } 2433 return mEnableNonGroupedNotificationExpand && mExpandable; 2434 } 2435 2436 public void setExpandable(boolean expandable) { 2437 mExpandable = expandable; 2438 mPrivateLayout.updateExpandButtons(isExpandable()); 2439 } 2440 2441 @Override 2442 public void setClipToActualHeight(boolean clipToActualHeight) { 2443 super.setClipToActualHeight(clipToActualHeight || isUserLocked()); 2444 getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked()); 2445 } 2446 2447 /** 2448 * @return whether the user has changed the expansion state 2449 */ 2450 public boolean hasUserChangedExpansion() { 2451 return mHasUserChangedExpansion; 2452 } 2453 2454 public boolean isUserExpanded() { 2455 return mUserExpanded; 2456 } 2457 2458 /** 2459 * Set this notification to be expanded by the user 2460 * 2461 * @param userExpanded whether the user wants this notification to be expanded 2462 */ 2463 public void setUserExpanded(boolean userExpanded) { 2464 setUserExpanded(userExpanded, false /* allowChildExpansion */); 2465 } 2466 2467 /** 2468 * Set this notification to be expanded by the user 2469 * 2470 * @param userExpanded whether the user wants this notification to be expanded 2471 * @param allowChildExpansion whether a call to this method allows expanding children 2472 */ 2473 public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { 2474 mFalsingCollector.setNotificationExpanded(); 2475 if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion 2476 && !mChildrenContainer.showingAsLowPriority()) { 2477 final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry); 2478 mGroupExpansionManager.setGroupExpanded(mEntry, userExpanded); 2479 onExpansionChanged(true /* userAction */, wasExpanded); 2480 return; 2481 } 2482 if (userExpanded && !mExpandable) return; 2483 final boolean wasExpanded = isExpanded(); 2484 mHasUserChangedExpansion = true; 2485 mUserExpanded = userExpanded; 2486 onExpansionChanged(true /* userAction */, wasExpanded); 2487 if (!wasExpanded && isExpanded() 2488 && getActualHeight() != getIntrinsicHeight()) { 2489 notifyHeightChanged(true /* needsAnimation */); 2490 } 2491 } 2492 2493 public void resetUserExpansion() { 2494 boolean wasExpanded = isExpanded(); 2495 mHasUserChangedExpansion = false; 2496 mUserExpanded = false; 2497 if (wasExpanded != isExpanded()) { 2498 if (mIsSummaryWithChildren) { 2499 mChildrenContainer.onExpansionChanged(); 2500 } 2501 notifyHeightChanged(false /* needsAnimation */); 2502 } 2503 updateShelfIconColor(); 2504 } 2505 2506 public boolean isUserLocked() { 2507 return mUserLocked; 2508 } 2509 2510 public void setUserLocked(boolean userLocked) { 2511 mUserLocked = userLocked; 2512 mPrivateLayout.setUserExpanding(userLocked); 2513 // This is intentionally not guarded with mIsSummaryWithChildren since we might have had 2514 // children but not anymore. 2515 if (mChildrenContainer != null) { 2516 mChildrenContainer.setUserLocked(userLocked); 2517 if (mIsSummaryWithChildren && (userLocked || !isGroupExpanded())) { 2518 updateBackgroundForGroupState(); 2519 } 2520 } 2521 } 2522 2523 /** 2524 * @return has the system set this notification to be expanded 2525 */ 2526 public boolean isSystemExpanded() { 2527 return mIsSystemExpanded; 2528 } 2529 2530 /** 2531 * Set this notification to be expanded by the system. 2532 * 2533 * @param expand whether the system wants this notification to be expanded. 2534 */ 2535 public void setSystemExpanded(boolean expand) { 2536 if (expand != mIsSystemExpanded) { 2537 final boolean wasExpanded = isExpanded(); 2538 mIsSystemExpanded = expand; 2539 notifyHeightChanged(false /* needsAnimation */); 2540 onExpansionChanged(false /* userAction */, wasExpanded); 2541 if (mIsSummaryWithChildren) { 2542 mChildrenContainer.updateGroupOverflow(); 2543 resetChildSystemExpandedStates(); 2544 } 2545 } 2546 } 2547 2548 void setOnKeyguard(boolean onKeyguard) { 2549 if (onKeyguard != mOnKeyguard) { 2550 boolean wasAboveShelf = isAboveShelf(); 2551 final boolean wasExpanded = isExpanded(); 2552 mOnKeyguard = onKeyguard; 2553 onExpansionChanged(false /* userAction */, wasExpanded); 2554 if (wasExpanded != isExpanded()) { 2555 if (mIsSummaryWithChildren) { 2556 mChildrenContainer.updateGroupOverflow(); 2557 } 2558 notifyHeightChanged(false /* needsAnimation */); 2559 } 2560 if (isAboveShelf() != wasAboveShelf) { 2561 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 2562 } 2563 } 2564 updateRippleAllowed(); 2565 } 2566 2567 private void updateRippleAllowed() { 2568 boolean allowed = isOnKeyguard() 2569 || mEntry.getSbn().getNotification().contentIntent == null; 2570 setRippleAllowed(allowed); 2571 } 2572 2573 @Override 2574 public void onTap() { 2575 // This notification will expand and animates into the content activity, so we disable the 2576 // ripple. We will restore its value once the tap/click is actually performed. 2577 if (mEntry.getSbn().getNotification().contentIntent != null) { 2578 setRippleAllowed(false); 2579 } 2580 } 2581 2582 @Override 2583 public boolean performClick() { 2584 // We force-disabled the ripple in onTap. When this method is called, the code drawing the 2585 // ripple will already have been called so we can restore its value now. 2586 updateRippleAllowed(); 2587 return super.performClick(); 2588 } 2589 2590 @Override 2591 public int getHeightWithoutLockscreenConstraints() { 2592 mIgnoreLockscreenConstraints = true; 2593 final int height = getIntrinsicHeight(); 2594 mIgnoreLockscreenConstraints = false; 2595 return height; 2596 } 2597 2598 @Override 2599 public int getIntrinsicHeight() { 2600 if (isUserLocked()) { 2601 return getActualHeight(); 2602 } else if (mGuts != null && mGuts.isExposed()) { 2603 return mGuts.getIntrinsicHeight(); 2604 } else if ((isChildInGroup() && !isGroupExpanded())) { 2605 return mPrivateLayout.getMinHeight(); 2606 } else if (mSensitive && mHideSensitiveForIntrinsicHeight) { 2607 return getMinHeight(); 2608 } else if (mIsSummaryWithChildren) { 2609 return mChildrenContainer.getIntrinsicHeight(); 2610 } else if (canShowHeadsUp() && isHeadsUpState()) { 2611 if (isPinned() || mHeadsupDisappearRunning) { 2612 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); 2613 } else if (isExpanded()) { 2614 return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); 2615 } else { 2616 return Math.max(getCollapsedHeight(), getHeadsUpHeight()); 2617 } 2618 } else if (isExpanded()) { 2619 return getMaxExpandHeight(); 2620 } else { 2621 return getCollapsedHeight(); 2622 } 2623 } 2624 /** 2625 * @return {@code true} if the notification can show it's heads up layout. This is mostly true 2626 * except for legacy use cases. 2627 */ 2628 public boolean canShowHeadsUp() { 2629 if (mOnKeyguard && !isDozing() && !isBypassEnabled() && 2630 (!mEntry.isStickyAndNotDemoted() 2631 || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) { 2632 return false; 2633 } 2634 return true; 2635 } 2636 2637 private boolean isBypassEnabled() { 2638 return mBypassController == null || mBypassController.getBypassEnabled(); 2639 } 2640 2641 private boolean isDozing() { 2642 return mStatusBarStateController != null && mStatusBarStateController.isDozing(); 2643 } 2644 2645 @Override 2646 public boolean isGroupExpanded() { 2647 return mGroupExpansionManager.isGroupExpanded(mEntry); 2648 } 2649 2650 private void onAttachedChildrenCountChanged() { 2651 mIsSummaryWithChildren = mChildrenContainer != null 2652 && mChildrenContainer.getNotificationChildCount() > 0; 2653 if (mIsSummaryWithChildren) { 2654 Trace.beginSection("ExpNotRow#onChildCountChanged (summary)"); 2655 NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper(); 2656 if (wrapper == null || wrapper.getNotificationHeader() == null) { 2657 mChildrenContainer.recreateNotificationHeader(mExpandClickListener, 2658 isConversation()); 2659 } 2660 } 2661 getShowingLayout().updateBackgroundColor(false /* animate */); 2662 mPrivateLayout.updateExpandButtons(isExpandable()); 2663 updateChildrenAppearance(); 2664 updateChildrenVisibility(); 2665 applyChildrenRoundness(); 2666 if (mIsSummaryWithChildren) { 2667 Trace.endSection(); 2668 } 2669 } 2670 2671 protected void expandNotification() { 2672 mExpandClickListener.onClick(this); 2673 } 2674 2675 /** 2676 * Returns the number of channels covered by the notification row (including its children if 2677 * it's a summary notification). 2678 */ 2679 public int getNumUniqueChannels() { 2680 return getUniqueChannels().size(); 2681 } 2682 2683 /** 2684 * Returns the channels covered by the notification row (including its children if 2685 * it's a summary notification). 2686 */ 2687 public ArraySet<NotificationChannel> getUniqueChannels() { 2688 ArraySet<NotificationChannel> channels = new ArraySet<>(); 2689 2690 channels.add(mEntry.getChannel()); 2691 2692 // If this is a summary, then add in the children notification channels for the 2693 // same user and pkg. 2694 if (mIsSummaryWithChildren) { 2695 final List<ExpandableNotificationRow> childrenRows = getAttachedChildren(); 2696 final int numChildren = childrenRows.size(); 2697 for (int i = 0; i < numChildren; i++) { 2698 final ExpandableNotificationRow childRow = childrenRows.get(i); 2699 final NotificationChannel childChannel = childRow.getEntry().getChannel(); 2700 final StatusBarNotification childSbn = childRow.getEntry().getSbn(); 2701 if (childSbn.getUser().equals(mEntry.getSbn().getUser()) 2702 && childSbn.getPackageName().equals(mEntry.getSbn().getPackageName())) { 2703 channels.add(childChannel); 2704 } 2705 } 2706 } 2707 2708 return channels; 2709 } 2710 2711 /** 2712 * If this is a group, update the appearance of the children. 2713 */ 2714 public void updateChildrenAppearance() { 2715 if (mIsSummaryWithChildren) { 2716 mChildrenContainer.updateChildrenAppearance(); 2717 } 2718 } 2719 2720 /** 2721 * Check whether the view state is currently expanded. This is given by the system in {@link 2722 * #setSystemExpanded(boolean)} and can be overridden by user expansion or 2723 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this 2724 * view can differ from this state, if layout params are modified from outside. 2725 * 2726 * @return whether the view state is currently expanded. 2727 */ 2728 public boolean isExpanded() { 2729 return isExpanded(false /* allowOnKeyguard */); 2730 } 2731 2732 public boolean isExpanded(boolean allowOnKeyguard) { 2733 if (DEBUG) { 2734 if (!mShowingPublicInitialized && !allowOnKeyguard) { 2735 Log.d(TAG, "mShowingPublic is not initialized."); 2736 } 2737 } 2738 return !mShowingPublic && (!mOnKeyguard || allowOnKeyguard) 2739 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) 2740 || isUserExpanded()); 2741 } 2742 2743 private boolean isSystemChildExpanded() { 2744 return mIsSystemChildExpanded; 2745 } 2746 2747 public void setSystemChildExpanded(boolean expanded) { 2748 mIsSystemChildExpanded = expanded; 2749 } 2750 2751 public void setLayoutListener(LayoutListener listener) { 2752 mLayoutListener = listener; 2753 } 2754 2755 public void removeListener() { 2756 mLayoutListener = null; 2757 } 2758 2759 @Override 2760 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 2761 Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout")); 2762 int intrinsicBefore = getIntrinsicHeight(); 2763 super.onLayout(changed, left, top, right, bottom); 2764 if (intrinsicBefore != getIntrinsicHeight() 2765 && (intrinsicBefore != 0 || getActualHeight() > 0)) { 2766 notifyHeightChanged(true /* needsAnimation */); 2767 } 2768 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 2769 mMenuRow.onParentHeightUpdate(); 2770 } 2771 updateContentShiftHeight(); 2772 if (mLayoutListener != null) { 2773 mLayoutListener.onLayout(); 2774 } 2775 Trace.endSection(); 2776 } 2777 2778 /** 2779 * Updates the content shift height such that the header is completely hidden when coming from 2780 * the top. 2781 */ 2782 private void updateContentShiftHeight() { 2783 NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper(); 2784 CachingIconView icon = wrapper == null ? null : wrapper.getIcon(); 2785 if (icon != null) { 2786 mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight(); 2787 } else { 2788 mIconTransformContentShift = mContentShift; 2789 } 2790 } 2791 2792 @Override 2793 protected float getContentTransformationShift() { 2794 return mIconTransformContentShift; 2795 } 2796 2797 @Override 2798 public void notifyHeightChanged(boolean needsAnimation) { 2799 super.notifyHeightChanged(needsAnimation); 2800 getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked()); 2801 } 2802 2803 public void setSensitive(boolean sensitive, boolean hideSensitive) { 2804 int intrinsicBefore = getIntrinsicHeight(); 2805 mSensitive = sensitive; 2806 mSensitiveHiddenInGeneral = hideSensitive; 2807 int intrinsicAfter = getIntrinsicHeight(); 2808 if (intrinsicBefore != intrinsicAfter) { 2809 boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); 2810 notifyHeightChanged(needsAnimation); 2811 } 2812 } 2813 2814 @Override 2815 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { 2816 mHideSensitiveForIntrinsicHeight = hideSensitive; 2817 if (mIsSummaryWithChildren) { 2818 List<ExpandableNotificationRow> notificationChildren = 2819 mChildrenContainer.getAttachedChildren(); 2820 for (int i = 0; i < notificationChildren.size(); i++) { 2821 ExpandableNotificationRow child = notificationChildren.get(i); 2822 child.setHideSensitiveForIntrinsicHeight(hideSensitive); 2823 } 2824 } 2825 } 2826 2827 @Override 2828 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, 2829 long duration) { 2830 if (getVisibility() == GONE) { 2831 // If we are GONE, the hideSensitive parameter will not be calculated and always be 2832 // false, which is incorrect, let's wait until a real call comes in later. 2833 return; 2834 } 2835 boolean oldShowingPublic = mShowingPublic; 2836 mShowingPublic = mSensitive && hideSensitive; 2837 if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { 2838 return; 2839 } 2840 2841 if (!animated) { 2842 mPublicLayout.animate().cancel(); 2843 mPrivateLayout.animate().cancel(); 2844 if (mChildrenContainer != null) { 2845 mChildrenContainer.animate().cancel(); 2846 } 2847 resetAllContentAlphas(); 2848 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); 2849 updateChildrenVisibility(); 2850 } else { 2851 animateShowingPublic(delay, duration, mShowingPublic); 2852 } 2853 NotificationContentView showingLayout = getShowingLayout(); 2854 showingLayout.updateBackgroundColor(animated); 2855 mPrivateLayout.updateExpandButtons(isExpandable()); 2856 updateShelfIconColor(); 2857 mShowingPublicInitialized = true; 2858 } 2859 2860 private void animateShowingPublic(long delay, long duration, boolean showingPublic) { 2861 View[] privateViews = mIsSummaryWithChildren 2862 ? new View[]{mChildrenContainer} 2863 : new View[]{mPrivateLayout}; 2864 View[] publicViews = new View[]{mPublicLayout}; 2865 View[] hiddenChildren = showingPublic ? privateViews : publicViews; 2866 View[] shownChildren = showingPublic ? publicViews : privateViews; 2867 // disappear/appear overlap: 10 percent of duration 2868 long overlap = duration / 10; 2869 // disappear duration: 1/3 of duration + half of overlap 2870 long disappearDuration = duration / 3 + overlap / 2; 2871 // appear duration: 2/3 of duration + half of overlap 2872 long appearDuration = (duration - disappearDuration) + overlap / 2; 2873 for (final View hiddenView : hiddenChildren) { 2874 hiddenView.setVisibility(View.VISIBLE); 2875 hiddenView.animate().cancel(); 2876 hiddenView.animate() 2877 .alpha(0f) 2878 .setStartDelay(delay) 2879 .setDuration(disappearDuration) 2880 .withEndAction(() -> { 2881 hiddenView.setVisibility(View.INVISIBLE); 2882 resetAllContentAlphas(); 2883 }); 2884 } 2885 for (View showView : shownChildren) { 2886 showView.setVisibility(View.VISIBLE); 2887 showView.setAlpha(0f); 2888 showView.animate().cancel(); 2889 showView.animate() 2890 .alpha(1f) 2891 .setStartDelay(delay + duration - appearDuration) 2892 .setDuration(appearDuration); 2893 } 2894 } 2895 2896 @Override 2897 public boolean mustStayOnScreen() { 2898 return mIsHeadsUp && mMustStayOnScreen; 2899 } 2900 2901 /** 2902 * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as 2903 * otherwise some state might not be updated. 2904 */ 2905 public boolean canViewBeDismissed() { 2906 return canEntryBeDismissed() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); 2907 } 2908 2909 private boolean canEntryBeDismissed() { 2910 return mDismissibilityProvider.isDismissable(mEntry); 2911 } 2912 2913 /** 2914 * @return Whether this view is allowed to be cleared with clear all. Only valid for visible 2915 * notifications as otherwise some state might not be updated. To request about the general 2916 * clearability see {@link NotificationEntry#isClearable()}. 2917 */ 2918 public boolean canViewBeCleared() { 2919 return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); 2920 } 2921 2922 private boolean shouldShowPublic() { 2923 return mSensitive && mHideSensitiveForIntrinsicHeight; 2924 } 2925 2926 public void makeActionsVisibile() { 2927 setUserExpanded(true, true); 2928 if (isChildInGroup()) { 2929 mGroupExpansionManager.setGroupExpanded(mEntry, true); 2930 } 2931 notifyHeightChanged(false /* needsAnimation */); 2932 } 2933 2934 public void setChildrenExpanded(boolean expanded, boolean animate) { 2935 mChildrenExpanded = expanded; 2936 if (mChildrenContainer != null) { 2937 mChildrenContainer.setChildrenExpanded(expanded); 2938 } 2939 updateBackgroundForGroupState(); 2940 updateClickAndFocus(); 2941 } 2942 2943 public int getMaxExpandHeight() { 2944 return mPrivateLayout.getExpandHeight(); 2945 } 2946 2947 2948 private int getHeadsUpHeight() { 2949 return getShowingLayout().getHeadsUpHeight(false /* forceNoHeader */); 2950 } 2951 2952 public boolean areGutsExposed() { 2953 return (mGuts != null && mGuts.isExposed()); 2954 } 2955 2956 private boolean isGutsLeaveBehind() { 2957 return (mGuts != null && mGuts.isLeavebehind()); 2958 } 2959 2960 @Override 2961 public boolean isContentExpandable() { 2962 if (mIsSummaryWithChildren && !shouldShowPublic()) { 2963 return true; 2964 } 2965 NotificationContentView showingLayout = getShowingLayout(); 2966 return showingLayout.isContentExpandable(); 2967 } 2968 2969 @Override 2970 protected View getContentView() { 2971 if (mIsSummaryWithChildren && !shouldShowPublic()) { 2972 return mChildrenContainer; 2973 } 2974 return getShowingLayout(); 2975 } 2976 2977 @Override 2978 public long performRemoveAnimation( 2979 long duration, 2980 long delay, 2981 float translationDirection, 2982 boolean isHeadsUpAnimation, 2983 Runnable onFinishedRunnable, 2984 AnimatorListenerAdapter animationListener) { 2985 if (mMenuRow != null && mMenuRow.isMenuVisible()) { 2986 Animator anim = getTranslateViewAnimator(0f, null /* listener */); 2987 if (anim != null) { 2988 anim.addListener(new AnimatorListenerAdapter() { 2989 @Override 2990 public void onAnimationEnd(Animator animation) { 2991 ExpandableNotificationRow.super.performRemoveAnimation( 2992 duration, delay, translationDirection, isHeadsUpAnimation, 2993 onFinishedRunnable, animationListener); 2994 } 2995 }); 2996 anim.start(); 2997 return anim.getDuration(); 2998 } 2999 } 3000 return super.performRemoveAnimation(duration, delay, translationDirection, 3001 isHeadsUpAnimation, onFinishedRunnable, animationListener); 3002 } 3003 3004 @Override 3005 protected void onAppearAnimationFinished(boolean wasAppearing) { 3006 super.onAppearAnimationFinished(wasAppearing); 3007 if (wasAppearing) { 3008 // During the animation the visible view might have changed, so let's make sure all 3009 // alphas are reset 3010 resetAllContentAlphas(); 3011 if (FADE_LAYER_OPTIMIZATION_ENABLED) { 3012 setNotificationFaded(false); 3013 } else { 3014 setNotificationFadedOnChildren(false); 3015 } 3016 } else { 3017 setHeadsUpAnimatingAway(false); 3018 } 3019 } 3020 3021 @Override 3022 protected void resetAllContentAlphas() { 3023 mPrivateLayout.setAlpha(1f); 3024 mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null); 3025 mPublicLayout.setAlpha(1f); 3026 mPublicLayout.setLayerType(LAYER_TYPE_NONE, null); 3027 if (mChildrenContainer != null) { 3028 mChildrenContainer.setAlpha(1f); 3029 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null); 3030 } 3031 } 3032 3033 /** 3034 * Gets the last value set with {@link #setNotificationFaded(boolean)} 3035 */ 3036 @Override 3037 public boolean isNotificationFaded() { 3038 return mIsFaded; 3039 } 3040 3041 /** 3042 * This class needs to delegate the faded state set on it by 3043 * {@link com.android.systemui.statusbar.notification.stack.ViewState} to its children. 3044 * Having each notification use layerType of HARDWARE anytime it fades in/out can result in 3045 * extremely large layers (in the case of groups, it can even exceed the device height). 3046 * Because these large renders can cause serious jank when rendering, we instead have 3047 * notifications return false from {@link #hasOverlappingRendering()} and delegate the 3048 * layerType to child views which really need it in order to render correctly, such as icon 3049 * views or the conversation face pile. 3050 * <p> 3051 * Another compounding factor for notifications is that we change clipping on each frame of the 3052 * animation, so the hardware layer isn't able to do any caching at the top level, but the 3053 * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we 3054 * never invalidate them. 3055 */ 3056 @Override 3057 public void setNotificationFaded(boolean faded) { 3058 mIsFaded = faded; 3059 if (childrenRequireOverlappingRendering()) { 3060 // == Simple Scenario == 3061 // If a child (like remote input) needs this to have overlapping rendering, then set 3062 // the layerType of this view and reset the children to render as if the notification is 3063 // not fading. 3064 NotificationFadeAware.setLayerTypeForFaded(this, faded); 3065 setNotificationFadedOnChildren(false); 3066 } else { 3067 // == Delegating Scenario == 3068 // This is the new normal for alpha: Explicitly reset this view's layer type to NONE, 3069 // and require that all children use their own hardware layer if they have bad 3070 // overlapping rendering. 3071 NotificationFadeAware.setLayerTypeForFaded(this, false); 3072 setNotificationFadedOnChildren(faded); 3073 } 3074 } 3075 3076 /** 3077 * Private helper for iterating over the layouts and children containers to set faded state 3078 */ 3079 private void setNotificationFadedOnChildren(boolean faded) { 3080 delegateNotificationFaded(mChildrenContainer, faded); 3081 for (NotificationContentView layout : mLayouts) { 3082 delegateNotificationFaded(layout, faded); 3083 } 3084 } 3085 3086 private static void delegateNotificationFaded(@Nullable View view, boolean faded) { 3087 if (FADE_LAYER_OPTIMIZATION_ENABLED && view instanceof NotificationFadeAware) { 3088 ((NotificationFadeAware) view).setNotificationFaded(faded); 3089 } else { 3090 NotificationFadeAware.setLayerTypeForFaded(view, faded); 3091 } 3092 } 3093 3094 /** 3095 * Only declare overlapping rendering if independent children of the view require it. 3096 */ 3097 @Override 3098 public boolean hasOverlappingRendering() { 3099 return super.hasOverlappingRendering() && childrenRequireOverlappingRendering(); 3100 } 3101 3102 /** 3103 * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the 3104 * row should require overlapping rendering to ensure that the overlapped view doesn't bleed 3105 * through when alpha fading. 3106 * <p> 3107 * Note that this currently works for top-level notifications which squish their height down 3108 * while collapsing the shade, but does not work for children inside groups, because the 3109 * accordion affect does not apply to those views, so super.hasOverlappingRendering() will 3110 * always return false to avoid the clipping caused when the view's measured height is less than 3111 * the 'actual height'. 3112 */ 3113 private boolean childrenRequireOverlappingRendering() { 3114 if (!FADE_LAYER_OPTIMIZATION_ENABLED) { 3115 return true; 3116 } 3117 // The colorized background is another layer with which all other elements overlap 3118 if (getEntry().getSbn().getNotification().isColorized()) { 3119 return true; 3120 } 3121 // Check if the showing layout has a need for overlapping rendering. 3122 // NOTE: We could check both public and private layouts here, but becuause these states 3123 // don't animate well, there are bigger visual artifacts if we start changing the shown 3124 // layout during shade expansion. 3125 NotificationContentView showingLayout = getShowingLayout(); 3126 return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering(); 3127 } 3128 3129 @Override 3130 public void setActualHeight(int height, boolean notifyListeners) { 3131 boolean changed = height != getActualHeight(); 3132 super.setActualHeight(height, notifyListeners); 3133 if (changed && isRemoved()) { 3134 // TODO: remove this once we found the gfx bug for this. 3135 // This is a hack since a removed view sometimes would just stay blank. it occured 3136 // when sending yourself a message and then clicking on it. 3137 ViewGroup parent = (ViewGroup) getParent(); 3138 if (parent != null) { 3139 parent.invalidate(); 3140 } 3141 } 3142 if (mGuts != null && mGuts.isExposed()) { 3143 mGuts.setActualHeight(height); 3144 return; 3145 } 3146 int contentHeight = Math.max(getMinHeight(), height); 3147 for (NotificationContentView l : mLayouts) { 3148 if (mInlineReplyAnimation.isEnabled()) { 3149 l.setContentHeight(height); 3150 } else { 3151 l.setContentHeight(contentHeight); 3152 } 3153 } 3154 if (mIsSummaryWithChildren) { 3155 mChildrenContainer.setActualHeight(height); 3156 } 3157 if (mGuts != null) { 3158 mGuts.setActualHeight(height); 3159 } 3160 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 3161 mMenuRow.onParentHeightUpdate(); 3162 } 3163 handleIntrinsicHeightReached(); 3164 } 3165 3166 @Override 3167 public int getMaxContentHeight() { 3168 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3169 return mChildrenContainer.getMaxContentHeight(); 3170 } 3171 NotificationContentView showingLayout = getShowingLayout(); 3172 return showingLayout.getMaxHeight(); 3173 } 3174 3175 @Override 3176 public int getMinHeight(boolean ignoreTemporaryStates) { 3177 if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) { 3178 return mGuts.getIntrinsicHeight(); 3179 } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp 3180 && mHeadsUpManager.isTrackingHeadsUp()) { 3181 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); 3182 } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) { 3183 return mChildrenContainer.getMinHeight(); 3184 } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) { 3185 return getHeadsUpHeight(); 3186 } 3187 NotificationContentView showingLayout = getShowingLayout(); 3188 return showingLayout.getMinHeight(); 3189 } 3190 3191 @Override 3192 public int getCollapsedHeight() { 3193 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3194 return mChildrenContainer.getCollapsedHeight(); 3195 } 3196 return getMinHeight(); 3197 } 3198 3199 @Override 3200 public int getHeadsUpHeightWithoutHeader() { 3201 if (!canShowHeadsUp() || !mIsHeadsUp) { 3202 return getCollapsedHeight(); 3203 } 3204 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3205 return mChildrenContainer.getCollapsedHeightWithoutHeader(); 3206 } 3207 return getShowingLayout().getHeadsUpHeight(true /* forceNoHeader */); 3208 } 3209 3210 @Override 3211 public void setClipTopAmount(int clipTopAmount) { 3212 super.setClipTopAmount(clipTopAmount); 3213 for (NotificationContentView l : mLayouts) { 3214 l.setClipTopAmount(clipTopAmount); 3215 } 3216 if (mGuts != null) { 3217 mGuts.setClipTopAmount(clipTopAmount); 3218 } 3219 } 3220 3221 @Override 3222 public void setClipBottomAmount(int clipBottomAmount) { 3223 if (mExpandAnimationRunning) { 3224 return; 3225 } 3226 if (clipBottomAmount != mClipBottomAmount) { 3227 super.setClipBottomAmount(clipBottomAmount); 3228 for (NotificationContentView l : mLayouts) { 3229 l.setClipBottomAmount(clipBottomAmount); 3230 } 3231 if (mGuts != null) { 3232 mGuts.setClipBottomAmount(clipBottomAmount); 3233 } 3234 } 3235 if (mChildrenContainer != null && !mChildIsExpanding) { 3236 // We have to update this even if it hasn't changed, since the children locations can 3237 // have changed 3238 mChildrenContainer.setClipBottomAmount(clipBottomAmount); 3239 } 3240 } 3241 3242 public NotificationContentView getShowingLayout() { 3243 return shouldShowPublic() ? mPublicLayout : mPrivateLayout; 3244 } 3245 3246 public void setLegacy(boolean legacy) { 3247 for (NotificationContentView l : mLayouts) { 3248 l.setLegacy(legacy); 3249 } 3250 } 3251 3252 @Override 3253 protected void updateBackgroundTint() { 3254 super.updateBackgroundTint(); 3255 updateBackgroundForGroupState(); 3256 if (mIsSummaryWithChildren) { 3257 List<ExpandableNotificationRow> notificationChildren = 3258 mChildrenContainer.getAttachedChildren(); 3259 for (int i = 0; i < notificationChildren.size(); i++) { 3260 ExpandableNotificationRow child = notificationChildren.get(i); 3261 child.updateBackgroundForGroupState(); 3262 } 3263 } 3264 } 3265 3266 /** 3267 * Called when a group has finished animating from collapsed or expanded state. 3268 */ 3269 public void onFinishedExpansionChange() { 3270 mGroupExpansionChanging = false; 3271 updateBackgroundForGroupState(); 3272 } 3273 3274 /** 3275 * Updates the parent and children backgrounds in a group based on the expansion state. 3276 */ 3277 public void updateBackgroundForGroupState() { 3278 if (mIsSummaryWithChildren) { 3279 // Only when the group has finished expanding do we hide its background. 3280 mShowNoBackground = !mShowGroupBackgroundWhenExpanded && isGroupExpanded() 3281 && !isGroupExpansionChanging() && !isUserLocked(); 3282 mChildrenContainer.updateHeaderForExpansion(mShowNoBackground); 3283 List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren(); 3284 for (int i = 0; i < children.size(); i++) { 3285 children.get(i).updateBackgroundForGroupState(); 3286 } 3287 } else if (isChildInGroup()) { 3288 final int childColor = getShowingLayout().getBackgroundColorForExpansionState(); 3289 // Only show a background if the group is expanded OR if it is expanding / collapsing 3290 // and has a custom background color. 3291 final boolean showBackground = isGroupExpanded() 3292 || ((mNotificationParent.isGroupExpansionChanging() 3293 || mNotificationParent.isUserLocked()) && childColor != 0); 3294 mShowNoBackground = !showBackground; 3295 } else { 3296 // Only children or parents ever need no background. 3297 mShowNoBackground = false; 3298 } 3299 updateOutline(); 3300 updateBackground(); 3301 } 3302 3303 @Override 3304 protected boolean hideBackground() { 3305 return mShowNoBackground || super.hideBackground(); 3306 } 3307 3308 public int getPositionOfChild(ExpandableNotificationRow childRow) { 3309 if (mIsSummaryWithChildren) { 3310 return mChildrenContainer.getPositionInLinearLayout(childRow); 3311 } 3312 return 0; 3313 } 3314 3315 public void onExpandedByGesture(boolean userExpanded) { 3316 int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; 3317 if (mGroupMembershipManager.isGroupSummary(mEntry)) { 3318 event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER; 3319 } 3320 mMetricsLogger.action(event, userExpanded); 3321 } 3322 3323 @Override 3324 protected boolean disallowSingleClick(MotionEvent event) { 3325 if (areGutsExposed()) { 3326 return false; 3327 } 3328 float x = event.getX(); 3329 float y = event.getY(); 3330 NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper(); 3331 NotificationHeaderView header = wrapper == null ? null : wrapper.getNotificationHeader(); 3332 // the extra translation only needs to be added, if we're translating the notification 3333 // contents, otherwise the motionEvent is already at the right place due to the 3334 // touch event system. 3335 float translation = !mDismissUsingRowTranslationX ? getTranslation() : 0; 3336 if (header != null && header.isInTouchRect(x - translation, y)) { 3337 return true; 3338 } 3339 if ((!mIsSummaryWithChildren || shouldShowPublic()) 3340 && getShowingLayout().disallowSingleClick(x, y)) { 3341 return true; 3342 } 3343 return super.disallowSingleClick(event); 3344 } 3345 3346 private void onExpansionChanged(boolean userAction, boolean wasExpanded) { 3347 boolean nowExpanded = isExpanded(); 3348 if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) { 3349 nowExpanded = mGroupExpansionManager.isGroupExpanded(mEntry); 3350 } 3351 if (nowExpanded != wasExpanded) { 3352 updateShelfIconColor(); 3353 if (mLogger != null) { 3354 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded); 3355 } 3356 if (mIsSummaryWithChildren) { 3357 mChildrenContainer.onExpansionChanged(); 3358 } 3359 if (mExpansionChangedListener != null) { 3360 mExpansionChangedListener.onExpansionChanged(nowExpanded); 3361 } 3362 } 3363 } 3364 3365 public void setOnExpansionChangedListener(@Nullable OnExpansionChangedListener listener) { 3366 mExpansionChangedListener = listener; 3367 } 3368 3369 /** 3370 * Perform an action when the notification height has reached its intrinsic height. 3371 * 3372 * @param runnable the runnable to run 3373 */ 3374 public void performOnIntrinsicHeightReached(@Nullable Runnable runnable) { 3375 mOnIntrinsicHeightReachedRunnable = runnable; 3376 handleIntrinsicHeightReached(); 3377 } 3378 3379 private void handleIntrinsicHeightReached() { 3380 if (mOnIntrinsicHeightReachedRunnable != null 3381 && getActualHeight() == getIntrinsicHeight()) { 3382 mOnIntrinsicHeightReachedRunnable.run(); 3383 mOnIntrinsicHeightReachedRunnable = null; 3384 } 3385 } 3386 3387 @Override 3388 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 3389 super.onInitializeAccessibilityNodeInfoInternal(info); 3390 final boolean isLongClickable = isNotificationRowLongClickable(); 3391 if (isLongClickable) { 3392 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 3393 } 3394 info.setLongClickable(isLongClickable); 3395 3396 if (canViewBeDismissed() && !mIsSnoozed) { 3397 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); 3398 } 3399 boolean expandable = shouldShowPublic(); 3400 boolean isExpanded = false; 3401 if (!expandable) { 3402 if (mIsSummaryWithChildren) { 3403 expandable = true; 3404 if (!mIsLowPriority || isExpanded()) { 3405 isExpanded = isGroupExpanded(); 3406 } 3407 } else { 3408 expandable = mPrivateLayout.isContentExpandable(); 3409 isExpanded = isExpanded(); 3410 } 3411 } 3412 if (expandable && !mIsSnoozed) { 3413 if (isExpanded) { 3414 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); 3415 } else { 3416 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); 3417 } 3418 } 3419 NotificationMenuRowPlugin provider = getProvider(); 3420 if (provider != null) { 3421 MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext()); 3422 if (snoozeMenu != null) { 3423 AccessibilityAction action = new AccessibilityAction(R.id.action_snooze, 3424 getContext().getResources() 3425 .getString(R.string.notification_menu_snooze_action)); 3426 info.addAction(action); 3427 } 3428 } 3429 } 3430 3431 @Override 3432 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 3433 if (super.performAccessibilityActionInternal(action, arguments)) { 3434 return true; 3435 } 3436 switch (action) { 3437 case AccessibilityNodeInfo.ACTION_DISMISS: 3438 performDismiss(true /* fromAccessibility */); 3439 return true; 3440 case AccessibilityNodeInfo.ACTION_COLLAPSE: 3441 case AccessibilityNodeInfo.ACTION_EXPAND: 3442 mExpandClickListener.onClick(this); 3443 return true; 3444 case AccessibilityNodeInfo.ACTION_LONG_CLICK: 3445 doLongClickCallback(); 3446 return true; 3447 default: 3448 if (action == R.id.action_snooze) { 3449 NotificationMenuRowPlugin provider = getProvider(); 3450 if (provider == null) { 3451 return false; 3452 } 3453 MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext()); 3454 if (snoozeMenu != null) { 3455 doLongClickCallback(getWidth() / 2, getHeight() / 2, snoozeMenu); 3456 } 3457 return true; 3458 } 3459 } 3460 return false; 3461 } 3462 3463 public interface OnExpandClickListener { 3464 void onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded); 3465 } 3466 3467 @Override 3468 @NonNull 3469 public ExpandableViewState createExpandableViewState() { 3470 return new NotificationViewState(); 3471 } 3472 3473 @Override 3474 public boolean isAboveShelf() { 3475 return (canShowHeadsUp() 3476 && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf) 3477 || mExpandAnimationRunning || mChildIsExpanding)); 3478 } 3479 3480 @Override 3481 protected boolean childNeedsClipping(View child) { 3482 if (child instanceof NotificationContentView) { 3483 NotificationContentView contentView = (NotificationContentView) child; 3484 if (isClippingNeeded()) { 3485 return true; 3486 } else if (hasRoundedCorner() 3487 && contentView.shouldClipToRounding(getTopRoundness() != 0.0f, 3488 getBottomRoundness() != 0.0f)) { 3489 return true; 3490 } 3491 } else if (child == mChildrenContainer) { 3492 if (isClippingNeeded() || hasRoundedCorner()) { 3493 return true; 3494 } 3495 } else if (child instanceof NotificationGuts) { 3496 return hasRoundedCorner(); 3497 } 3498 return super.childNeedsClipping(child); 3499 } 3500 3501 /** 3502 * Set a clip path to be set while expanding the notification. This is needed to nicely 3503 * clip ourselves during the launch if we were clipped rounded in the beginning 3504 */ 3505 public void setExpandingClipPath(Path path) { 3506 mExpandingClipPath = path; 3507 invalidate(); 3508 } 3509 3510 @Override 3511 protected void dispatchDraw(Canvas canvas) { 3512 canvas.save(); 3513 if (mExpandingClipPath != null && (mExpandAnimationRunning || mChildIsExpanding)) { 3514 // If we're launching a notification, let's clip if a clip rounded to the clipPath 3515 canvas.clipPath(mExpandingClipPath); 3516 } 3517 super.dispatchDraw(canvas); 3518 canvas.restore(); 3519 } 3520 3521 @Override 3522 public void applyRoundnessAndInvalidate() { 3523 applyChildrenRoundness(); 3524 super.applyRoundnessAndInvalidate(); 3525 } 3526 3527 private void applyChildrenRoundness() { 3528 if (mIsSummaryWithChildren) { 3529 mChildrenContainer.requestRoundness( 3530 /* top = */ getTopRoundness(), 3531 /* bottom = */ getBottomRoundness(), 3532 /* sourceType = */ FROM_PARENT, 3533 /* animate = */ false); 3534 } 3535 } 3536 3537 @Override 3538 public Path getCustomClipPath(View child) { 3539 if (child instanceof NotificationGuts) { 3540 return getClipPath(true /* ignoreTranslation */); 3541 } 3542 return super.getCustomClipPath(child); 3543 } 3544 3545 public boolean isMediaRow() { 3546 return mEntry.getSbn().getNotification().isMediaNotification(); 3547 } 3548 3549 public boolean isGroupNotFullyVisible() { 3550 return getClipTopAmount() > 0 || getTranslationY() < 0; 3551 } 3552 3553 public void setAboveShelf(boolean aboveShelf) { 3554 boolean wasAboveShelf = isAboveShelf(); 3555 mAboveShelf = aboveShelf; 3556 if (isAboveShelf() != wasAboveShelf) { 3557 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 3558 } 3559 } 3560 3561 private static class NotificationViewState extends ExpandableViewState { 3562 3563 @Override 3564 public void applyToView(View view) { 3565 if (view instanceof ExpandableNotificationRow) { 3566 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 3567 if (row.isExpandAnimationRunning()) { 3568 return; 3569 } 3570 handleFixedTranslationZ(row); 3571 super.applyToView(view); 3572 row.applyChildrenState(); 3573 } 3574 } 3575 3576 private void handleFixedTranslationZ(ExpandableNotificationRow row) { 3577 if (row.hasExpandingChild()) { 3578 setZTranslation(row.getTranslationZ()); 3579 clipTopAmount = row.getClipTopAmount(); 3580 } 3581 } 3582 3583 @Override 3584 protected void onYTranslationAnimationFinished(View view) { 3585 super.onYTranslationAnimationFinished(view); 3586 if (view instanceof ExpandableNotificationRow) { 3587 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 3588 if (row.isHeadsUpAnimatingAway()) { 3589 row.setHeadsUpAnimatingAway(false); 3590 } 3591 } 3592 } 3593 3594 @Override 3595 public void animateTo(View child, AnimationProperties properties) { 3596 if (child instanceof ExpandableNotificationRow) { 3597 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 3598 if (row.isExpandAnimationRunning()) { 3599 return; 3600 } 3601 handleFixedTranslationZ(row); 3602 super.animateTo(child, properties); 3603 row.startChildAnimation(properties); 3604 } 3605 } 3606 } 3607 3608 /** 3609 * Returns the Smart Suggestions backing the smart suggestion buttons in the notification. 3610 */ 3611 public InflatedSmartReplyState getExistingSmartReplyState() { 3612 return mPrivateLayout.getCurrentSmartReplyState(); 3613 } 3614 3615 @VisibleForTesting 3616 protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) { 3617 mChildrenContainer = childrenContainer; 3618 } 3619 3620 @VisibleForTesting 3621 protected void setPrivateLayout(NotificationContentView privateLayout) { 3622 mPrivateLayout = privateLayout; 3623 mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; 3624 } 3625 3626 @VisibleForTesting 3627 protected void setPublicLayout(NotificationContentView publicLayout) { 3628 mPublicLayout = publicLayout; 3629 mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; 3630 } 3631 3632 /** 3633 * Equivalent to View.OnLongClickListener with coordinates 3634 */ 3635 public interface LongPressListener { 3636 /** 3637 * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates 3638 * 3639 * @return whether the longpress was handled 3640 */ 3641 boolean onLongPress(View v, int x, int y, MenuItem item); 3642 } 3643 3644 /** 3645 * Called when notification drag and drop is finished successfully. 3646 */ 3647 public interface OnDragSuccessListener { 3648 /** 3649 * @param entry NotificationEntry that succeed to drop on proper target window. 3650 */ 3651 void onDragSuccess(NotificationEntry entry); 3652 } 3653 3654 /** 3655 * Equivalent to View.OnClickListener with coordinates 3656 */ 3657 public interface CoordinateOnClickListener { 3658 /** 3659 * Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates 3660 * 3661 * @return whether the click was handled 3662 */ 3663 boolean onClick(View v, int x, int y, MenuItem item); 3664 } 3665 3666 @Override 3667 public void dump(PrintWriter pwOriginal, String[] args) { 3668 IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); 3669 // Skip super call; dump viewState ourselves 3670 pw.println("Notification: " + mEntry.getKey()); 3671 DumpUtilsKt.withIncreasedIndent(pw, () -> { 3672 pw.println(this); 3673 pw.print("visibility: " + getVisibility()); 3674 pw.print(", alpha: " + getAlpha()); 3675 pw.print(", translation: " + getTranslation()); 3676 pw.print(", entry dismissable: " + canEntryBeDismissed()); 3677 pw.print(", mOnUserInteractionCallback null: " + (mOnUserInteractionCallback == null)); 3678 pw.print(", removed: " + isRemoved()); 3679 pw.print(", expandAnimationRunning: " + mExpandAnimationRunning); 3680 pw.print(", mShowingPublic: " + mShowingPublic); 3681 pw.print(", mShowingPublicInitialized: " + mShowingPublicInitialized); 3682 NotificationContentView showingLayout = getShowingLayout(); 3683 pw.print(", privateShowing: " + (showingLayout == mPrivateLayout)); 3684 pw.print(", mShowNoBackground: " + mShowNoBackground); 3685 pw.println(); 3686 showingLayout.dump(pw, args); 3687 3688 if (getViewState() != null) { 3689 getViewState().dump(pw, args); 3690 pw.println(); 3691 } else { 3692 pw.println("no viewState!!!"); 3693 } 3694 pw.println(getRoundableState().debugString()); 3695 dumpBackgroundView(pw, args); 3696 3697 int transientViewCount = mChildrenContainer == null 3698 ? 0 : mChildrenContainer.getTransientViewCount(); 3699 if (mIsSummaryWithChildren || transientViewCount > 0) { 3700 pw.println(mChildrenContainer.debugString()); 3701 pw.println(); 3702 List<ExpandableNotificationRow> notificationChildren = getAttachedChildren(); 3703 pw.print("Children: " + notificationChildren.size() + " {"); 3704 pw.increaseIndent(); 3705 for (ExpandableNotificationRow child : notificationChildren) { 3706 pw.println(); 3707 child.dump(pw, args); 3708 } 3709 pw.decreaseIndent(); 3710 pw.println("}"); 3711 pw.print("Transient Views: " + transientViewCount + " {"); 3712 pw.increaseIndent(); 3713 for (int i = 0; i < transientViewCount; i++) { 3714 pw.println(); 3715 ExpandableView child = (ExpandableView) mChildrenContainer.getTransientView(i); 3716 child.dump(pw, args); 3717 } 3718 pw.decreaseIndent(); 3719 pw.println("}"); 3720 } else if (mPrivateLayout != null) { 3721 mPrivateLayout.dumpSmartReplies(pw); 3722 } 3723 }); 3724 } 3725 3726 private void logKeepInParentChildDetached(ExpandableNotificationRow child) { 3727 if (mLogger != null) { 3728 mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry()); 3729 } 3730 } 3731 3732 private void logSkipAttachingKeepInParentChild(ExpandableNotificationRow child) { 3733 if (mLogger != null) { 3734 mLogger.logSkipAttachingKeepInParentChild(child.getEntry(), getEntry()); 3735 } 3736 } 3737 3738 private void setTargetPoint(Point p) { 3739 mTargetPoint = p; 3740 } 3741 3742 public Point getTargetPoint() { 3743 return mTargetPoint; 3744 } 3745 3746 /** Update the minimum roundness based on current state */ 3747 private void updateBaseRoundness() { 3748 if (isChildInGroup()) { 3749 requestRoundnessReset(BASE_VALUE); 3750 } else { 3751 requestRoundness(mSmallRoundness, mSmallRoundness, BASE_VALUE); 3752 } 3753 } 3754 3755 @Override 3756 protected void onAttachedToWindow() { 3757 super.onAttachedToWindow(); 3758 updateBaseRoundness(); 3759 } 3760 3761 /** Set whether this notification may show a snooze action. */ 3762 public void setShowSnooze(boolean showSnooze) { 3763 mShowSnooze = showSnooze; 3764 } 3765 3766 /** Whether this notification may show a snooze action. */ 3767 public boolean getShowSnooze() { 3768 return mShowSnooze; 3769 } 3770 3771 @Override 3772 public void removeFromTransientContainer() { 3773 final ViewGroup transientContainer = getTransientContainer(); 3774 final ViewParent parent = getParent(); 3775 // Only log when there is real removal of transient views 3776 if (transientContainer == null || transientContainer != parent) { 3777 super.removeFromTransientContainer(); 3778 return; 3779 } 3780 logRemoveFromTransientContainer(transientContainer); 3781 super.removeFromTransientContainer(); 3782 } 3783 3784 /** 3785 * Log calls to removeFromTransientContainer when the container is NotificationChildrenContainer 3786 * or NotificationStackScrollLayout. 3787 */ 3788 public void logRemoveFromTransientContainer(ViewGroup transientContainer) { 3789 if (mLogger == null) { 3790 return; 3791 } 3792 if (transientContainer instanceof NotificationChildrenContainer) { 3793 mLogger.logRemoveTransientFromContainer( 3794 /* childEntry = */ getEntry(), 3795 /* containerEntry = */ ((NotificationChildrenContainer) transientContainer) 3796 .getContainingNotification().getEntry() 3797 ); 3798 } else if (transientContainer instanceof NotificationStackScrollLayout) { 3799 mLogger.logRemoveTransientFromNssl( 3800 /* childEntry = */ getEntry() 3801 ); 3802 } else { 3803 mLogger.logRemoveTransientFromViewGroup( 3804 /* childEntry = */ getEntry(), 3805 /* containerView = */ transientContainer 3806 ); 3807 } 3808 } 3809 3810 @Override 3811 public void addTransientView(View view, int index) { 3812 if (view instanceof ExpandableNotificationRow) { 3813 logAddTransientRow((ExpandableNotificationRow) view, index); 3814 } 3815 super.addTransientView(view, index); 3816 } 3817 3818 private void logAddTransientRow(ExpandableNotificationRow row, int index) { 3819 if (mLogger == null) { 3820 return; 3821 } 3822 mLogger.logAddTransientRow(row.getEntry(), getEntry(), index); 3823 } 3824 3825 @Override 3826 public void removeTransientView(View view) { 3827 if (view instanceof ExpandableNotificationRow) { 3828 logRemoveTransientRow((ExpandableNotificationRow) view); 3829 } 3830 super.removeTransientView(view); 3831 } 3832 3833 private void logRemoveTransientRow(ExpandableNotificationRow row) { 3834 if (mLogger == null) { 3835 return; 3836 } 3837 mLogger.logRemoveTransientRow(row.getEntry(), getEntry()); 3838 } 3839 } 3840