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.Flags.notificationsRedesignTemplates; 20 import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY; 21 import static android.service.notification.NotificationListenerService.REASON_CANCEL; 22 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED; 23 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; 24 25 import static com.android.systemui.Flags.notificationRowAccessibilityExpanded; 26 import static com.android.systemui.Flags.notificationRowTransparency; 27 import static com.android.systemui.Flags.notificationsPinnedHunInShade; 28 import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE; 29 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE; 30 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; 31 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; 32 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; 33 import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE; 34 import static com.android.systemui.util.ColorUtilKt.hexColorString; 35 36 import android.animation.Animator; 37 import android.animation.AnimatorListenerAdapter; 38 import android.animation.ObjectAnimator; 39 import android.animation.ValueAnimator.AnimatorUpdateListener; 40 import android.app.Notification; 41 import android.content.Context; 42 import android.content.res.Configuration; 43 import android.content.res.Resources; 44 import android.graphics.Canvas; 45 import android.graphics.Color; 46 import android.graphics.Path; 47 import android.graphics.Point; 48 import android.graphics.Rect; 49 import android.graphics.drawable.AnimatedVectorDrawable; 50 import android.graphics.drawable.AnimationDrawable; 51 import android.graphics.drawable.Drawable; 52 import android.os.Build; 53 import android.os.Bundle; 54 import android.os.SystemClock; 55 import android.os.SystemProperties; 56 import android.os.Trace; 57 import android.os.UserHandle; 58 import android.text.TextUtils; 59 import android.util.AttributeSet; 60 import android.util.FloatProperty; 61 import android.util.IndentingPrintWriter; 62 import android.util.Log; 63 import android.util.MathUtils; 64 import android.view.KeyEvent; 65 import android.view.LayoutInflater; 66 import android.view.MotionEvent; 67 import android.view.NotificationHeaderView; 68 import android.view.View; 69 import android.view.ViewGroup; 70 import android.view.ViewParent; 71 import android.view.ViewStub; 72 import android.view.accessibility.AccessibilityEvent; 73 import android.view.accessibility.AccessibilityManager; 74 import android.view.accessibility.AccessibilityNodeInfo; 75 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 76 import android.widget.Chronometer; 77 import android.widget.FrameLayout; 78 import android.widget.ImageView; 79 80 import androidx.annotation.NonNull; 81 import androidx.annotation.Nullable; 82 import androidx.dynamicanimation.animation.FloatPropertyCompat; 83 import androidx.dynamicanimation.animation.SpringAnimation; 84 85 import com.android.app.animation.Interpolators; 86 import com.android.internal.annotations.VisibleForTesting; 87 import com.android.internal.graphics.ColorUtils; 88 import com.android.internal.logging.MetricsLogger; 89 import com.android.internal.logging.UiEventLogger; 90 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 91 import com.android.internal.statusbar.IStatusBarService; 92 import com.android.internal.util.ContrastColorUtil; 93 import com.android.internal.widget.CachingIconView; 94 import com.android.internal.widget.CallLayout; 95 import com.android.internal.widget.ConversationLayout; 96 import com.android.internal.widget.MessagingLayout; 97 import com.android.systemui.Flags; 98 import com.android.systemui.flags.RefactorFlag; 99 import com.android.systemui.plugins.FalsingManager; 100 import com.android.systemui.plugins.PluginListener; 101 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 102 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; 103 import com.android.systemui.plugins.statusbar.StatusBarStateController; 104 import com.android.systemui.res.R; 105 import com.android.systemui.scene.shared.flag.SceneContainerFlag; 106 import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType; 107 import com.android.systemui.statusbar.RemoteInputController; 108 import com.android.systemui.statusbar.SmartReplyController; 109 import com.android.systemui.statusbar.StatusBarIconView; 110 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; 111 import com.android.systemui.statusbar.notification.AboveShelfChangedListener; 112 import com.android.systemui.statusbar.notification.ColorUpdateLogger; 113 import com.android.systemui.statusbar.notification.FeedbackIcon; 114 import com.android.systemui.statusbar.notification.LaunchAnimationParameters; 115 import com.android.systemui.statusbar.notification.NmSummarizationUiFlag; 116 import com.android.systemui.statusbar.notification.NotificationFadeAware; 117 import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController; 118 import com.android.systemui.statusbar.notification.NotificationUtils; 119 import com.android.systemui.statusbar.notification.SourceType; 120 import com.android.systemui.statusbar.notification.collection.EntryAdapter; 121 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 122 import com.android.systemui.statusbar.notification.collection.PipelineEntry; 123 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; 124 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; 125 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; 126 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; 127 import com.android.systemui.statusbar.notification.headsup.PinnedStatus; 128 import com.android.systemui.statusbar.notification.logging.NotificationCounters; 129 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; 130 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; 131 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; 132 import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; 133 import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl; 134 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper; 135 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; 136 import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss; 137 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; 138 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; 139 import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix; 140 import com.android.systemui.statusbar.notification.stack.AmbientState; 141 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 142 import com.android.systemui.statusbar.notification.stack.ExpandableViewState; 143 import com.android.systemui.statusbar.notification.stack.MagneticRowListener; 144 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; 145 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger; 146 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; 147 import com.android.systemui.statusbar.notification.stack.SwipeableView; 148 import com.android.systemui.statusbar.phone.KeyguardBypassController; 149 import com.android.systemui.statusbar.policy.InflatedSmartReplyState; 150 import com.android.systemui.statusbar.policy.RemoteInputView; 151 import com.android.systemui.statusbar.policy.SmartReplyConstants; 152 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; 153 import com.android.systemui.util.Compile; 154 import com.android.systemui.util.DumpUtilsKt; 155 import com.android.systemui.util.ListenerSet; 156 import com.android.wm.shell.shared.animation.PhysicsAnimator; 157 158 import java.io.PrintWriter; 159 import java.util.ArrayList; 160 import java.util.Arrays; 161 import java.util.List; 162 import java.util.Map; 163 import java.util.concurrent.TimeUnit; 164 import java.util.function.BooleanSupplier; 165 import java.util.function.Consumer; 166 167 /** 168 * View representing a notification item - this can be either the individual child notification or 169 * the group summary (which contains 1 or more child notifications). 170 */ 171 public class ExpandableNotificationRow extends ActivatableNotificationView 172 implements PluginListener<NotificationMenuRowPlugin>, SwipeableView, 173 NotificationFadeAware.FadeOptimizedNotification { 174 175 private static final String TAG = "ExpandableNotifRow"; 176 private static final boolean DEBUG_ONMEASURE = 177 Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); 178 private static final int MENU_VIEW_INDEX = 0; 179 public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; 180 private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); 181 private static final SourceType BASE_VALUE = SourceType.from("BaseValue"); 182 private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)"); 183 private static final long INITIALIZATION_DELAY = 400; 184 185 // We don't correctly track dark mode until the content views are inflated, so always update 186 // the background on first content update just in case it happens to be during a theme change. 187 private boolean mUpdateSelfBackgroundOnUpdate = true; 188 private boolean mIsSnoozed; 189 private boolean mShowSnooze = false; 190 private boolean mIsFaded; 191 192 private boolean mHasStatusBarChipDuringHeadsUpAnimation = false; 193 194 @Nullable 195 public ImageModelIndex mImageModelIndex = null; 196 197 /** 198 * Listener for when {@link ExpandableNotificationRow} is laid out. 199 */ 200 public interface LayoutListener { onLayout()201 void onLayout(); 202 } 203 204 /** 205 * Listens for changes to the expansion state of this row. 206 */ 207 public interface OnExpansionChangedListener { onExpansionChanged(boolean isExpanded)208 void onExpansionChanged(boolean isExpanded); 209 } 210 211 private StatusBarStateController mStatusBarStateController; 212 private KeyguardBypassController mBypassController; 213 private LayoutListener mLayoutListener; 214 private RowContentBindStage mRowContentBindStage; 215 private PeopleNotificationIdentifier mPeopleNotificationIdentifier; 216 private MetricsLogger mMetricsLogger; 217 private NotificationChildrenContainerLogger mChildrenContainerLogger; 218 private ColorUpdateLogger mColorUpdateLogger; 219 private NotificationDismissibilityProvider mDismissibilityProvider; 220 private int mIconTransformContentShift; 221 private int mMaxHeadsUpHeightBeforeN; 222 private int mMaxHeadsUpHeightBeforeP; 223 private int mMaxHeadsUpHeightBeforeS; 224 private int mMaxHeadsUpHeight; 225 private int mMaxSmallHeightBeforeN; 226 private int mMaxSmallHeightBeforeP; 227 private int mMaxSmallHeightBeforeS; 228 private int mMaxSmallHeightWithSummarization; 229 private int mMaxSmallHeight; 230 private int mMaxExpandedHeight; 231 private int mMaxExpandedHeightForPromotedOngoing; 232 private int mNotificationLaunchHeight; 233 private boolean mMustStayOnScreen; 234 235 /** 236 * Does this row contain layouts that can adapt to row expansion 237 */ 238 private boolean mExpandable; 239 /** 240 * Has the user actively changed the expansion state of this row 241 */ 242 private boolean mHasUserChangedExpansion; 243 /** 244 * If {@link #mHasUserChangedExpansion}, has the user expanded this row 245 */ 246 private boolean mUserExpanded; 247 /** 248 * Has this notification been expanded while it was pinned 249 */ 250 private boolean mExpandedWhenPinned; 251 /** 252 * Is the user touching this row 253 */ 254 private boolean mUserLocked; 255 /** 256 * Are we showing the "public" version 257 */ 258 private boolean mShowingPublic; 259 private boolean mSensitive; 260 private boolean mSensitiveHiddenInGeneral; 261 private boolean mShowPublicExpander = true; 262 private boolean mShowingPublicInitialized; 263 private boolean mHideSensitiveForIntrinsicHeight; 264 private float mHeaderVisibleAmount = DEFAULT_HEADER_VISIBLE_AMOUNT; 265 266 /** 267 * Is this notification expanded by the system. The expansion state can be overridden by the 268 * user expansion. 269 */ 270 private boolean mIsSystemExpanded; 271 272 /** 273 * Whether the notification is on the keyguard and the expansion is disabled. 274 */ 275 private boolean mOnKeyguard; 276 277 private Animator mTranslateAnim; 278 private ArrayList<View> mTranslateableViews; 279 private NotificationContentView mPublicLayout; 280 private NotificationContentView mPrivateLayout; 281 private NotificationContentView[] mLayouts; 282 private ExpandableNotificationRowLogger mLogger; 283 private String mLoggingKey; 284 private String mKey; 285 private NotificationGuts mGuts; 286 private NotificationEntry mEntry; 287 private EntryAdapter mEntryAdapter; 288 private String mAppName; 289 private NotificationRebindingTracker mRebindingTracker; 290 private FalsingManager mFalsingManager; 291 292 /** 293 * Whether or not the notification is using the heads up view and should peek from the top. 294 */ 295 private boolean mIsHeadsUp; 296 297 /** 298 * Whether or not the notification is showing the app icon instead of the small icon. 299 */ 300 private boolean mIsShowingAppIcon; 301 302 private boolean mLastChronometerRunning = true; 303 private ViewStub mChildrenContainerStub; 304 private GroupMembershipManager mGroupMembershipManager; 305 private GroupExpansionManager mGroupExpansionManager; 306 private boolean mChildrenExpanded; 307 private boolean mIsSummaryWithChildren; 308 private NotificationChildrenContainer mChildrenContainer; 309 private NotificationMenuRowPlugin mMenuRow; 310 private ViewStub mGutsStub; 311 private boolean mIsSystemChildExpanded; 312 private PinnedStatus mPinnedStatus = PinnedStatus.NotPinned; 313 private boolean mExpandAnimationRunning; 314 private boolean mLaunchAnimationRunning; 315 private AboveShelfChangedListener mAboveShelfChangedListener; 316 private HeadsUpManager mHeadsUpManager; 317 private Consumer<Boolean> mHeadsUpAnimatingAwayListener; 318 private boolean mChildIsExpanding; 319 320 private boolean mJustClicked; 321 private boolean mAnimationRunning; 322 private boolean mShowNoBackground; 323 private ExpandableNotificationRow mNotificationParent; 324 private OnExpandClickListener mOnExpandClickListener; 325 private View.OnClickListener mOnFeedbackClickListener; 326 private Path mExpandingClipPath; 327 shouldSimulateSlowMeasure()328 private static boolean shouldSimulateSlowMeasure() { 329 return Compile.IS_DEBUG && RefactorFlag.forView( 330 ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE).isEnabled(); 331 } 332 333 private static final String SLOW_MEASURE_SIMULATE_DELAY_PROPERTY = 334 "persist.notifications.extra_measure_delay_ms"; 335 private static final int SLOW_MEASURE_SIMULATE_DELAY_MS = 336 SystemProperties.getInt(SLOW_MEASURE_SIMULATE_DELAY_PROPERTY, 150); 337 338 // Listener will be called when receiving a long click event. 339 // Use #setLongPressPosition to optionally assign positional data with the long press. 340 private LongPressListener mLongPressListener; 341 342 private ExpandableNotificationRowDragController mDragController; 343 344 private boolean mGroupExpansionChanging; 345 346 /** 347 * A supplier that returns true if keyguard is secure. 348 */ 349 private BooleanSupplier mSecureStateProvider; 350 351 /** 352 * Whether or not a notification that is not part of a group of notifications can be manually 353 * expanded by the user. 354 */ 355 private boolean mEnableNonGroupedNotificationExpand; 356 357 /** 358 * Whether or not to update the background of the header of the notification when its expanded. 359 * If {@code true}, the header background will disappear when expanded. 360 */ 361 private boolean mShowGroupBackgroundWhenExpanded; 362 363 /** 364 * True if we always show the collapsed layout on lockscreen because vertical space is low. 365 */ 366 private boolean mSaveSpaceOnLockscreen; 367 368 // indicates when this view was first attached to a window 369 // this value will reset when the view is completely removed from the shade (ie: filtered out) 370 private long initializationTime = -1; 371 372 /** 373 * It is added for unit testing purpose. 374 * Please do not use it for other purposes. 375 */ 376 @VisibleForTesting setIgnoreLockscreenConstraints(boolean ignoreLockscreenConstraints)377 public void setIgnoreLockscreenConstraints(boolean ignoreLockscreenConstraints) { 378 mIgnoreLockscreenConstraints = ignoreLockscreenConstraints; 379 } 380 381 /** 382 * True if we use intrinsic height regardless of vertical space available on lockscreen. 383 */ 384 private boolean mIgnoreLockscreenConstraints; 385 386 private final OnClickListener mExpandClickListener = new OnClickListener() { 387 @Override 388 public void onClick(View v) { 389 toggleExpansionState(v, /* shouldLogExpandClickMetric = */true); 390 } 391 }; 392 393 @Override cancelTranslationAnimations()394 protected void cancelTranslationAnimations() { 395 cancelSnapBackAnimation(); 396 cancelTranslateAnimation(); 397 } 398 cancelSnapBackAnimation()399 private void cancelSnapBackAnimation() { 400 PhysicsAnimator<ExpandableView> animator = 401 PhysicsAnimator.getInstanceIfExists(this /* target */); 402 if (animator != null) { 403 animator.cancel(); 404 } 405 } 406 407 /** 408 * Toggles expansion state. 409 */ toggleExpansionState()410 public void toggleExpansionState() { 411 toggleExpansionState(this, /*shouldLogExpandClickMetric*/ false); 412 } 413 toggleExpansionState(View v, boolean shouldLogExpandClickMetric)414 private void toggleExpansionState(View v, boolean shouldLogExpandClickMetric) { 415 if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot()) { 416 mGroupExpansionChanging = true; 417 if (NotificationBundleUi.isEnabled()) { 418 final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntryAdapter); 419 boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(mEntryAdapter); 420 mOnExpandClickListener.onExpandClicked(this, mEntryAdapter, nowExpanded); 421 if (shouldLogExpandClickMetric) { 422 mMetricsLogger.action( 423 MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, nowExpanded); 424 } 425 onExpansionChanged(true /* userAction */, wasExpanded); 426 } else { 427 final boolean wasExpanded = 428 mGroupExpansionManager.isGroupExpanded(getEntryLegacy()); 429 boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(getEntryLegacy()); 430 mOnExpandClickListener.onExpandClicked(getEntryLegacy(), v, nowExpanded); 431 if (shouldLogExpandClickMetric) { 432 mMetricsLogger.action( 433 MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, nowExpanded); 434 } 435 onExpansionChanged(true /* userAction */, wasExpanded); 436 } 437 } else if (mEnableNonGroupedNotificationExpand) { 438 if (v != null && v.isAccessibilityFocused()) { 439 mPrivateLayout.setFocusOnVisibilityChange(); 440 } 441 boolean nowExpanded; 442 if (isPinned()) { 443 nowExpanded = !mExpandedWhenPinned; 444 mExpandedWhenPinned = nowExpanded; 445 // Also notify any expansion changed listeners. This is necessary since the 446 // expansion doesn't actually change (it's already system expanded) but it 447 // changes visually 448 if (mExpansionChangedListener != null) { 449 mExpansionChangedListener.onExpansionChanged(nowExpanded); 450 } 451 } else { 452 nowExpanded = !isExpanded(); 453 setUserExpanded(nowExpanded); 454 } 455 456 notifyHeightChanged(/* needsAnimation= */ true); 457 if (NotificationBundleUi.isEnabled()) { 458 mOnExpandClickListener.onExpandClicked(this, mEntryAdapter, nowExpanded); 459 } else { 460 mOnExpandClickListener.onExpandClicked(getEntryLegacy(), v, nowExpanded); 461 } 462 if (shouldLogExpandClickMetric) { 463 mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded); 464 } 465 } 466 } 467 468 private boolean mKeepInParentForDismissAnimation; 469 private boolean mRemoved; 470 public static final FloatProperty<ExpandableNotificationRow> TRANSLATE_CONTENT = 471 new FloatProperty<>("translate") { 472 @Override 473 public void setValue(ExpandableNotificationRow object, float value) { 474 object.setTranslation(value); 475 } 476 477 @Override 478 public Float get(ExpandableNotificationRow object) { 479 return object.getTranslation(); 480 } 481 }; 482 483 private OnClickListener mOnClickListener; 484 @Nullable 485 private OnClickListener mBubbleClickListener; 486 private OnDragSuccessListener mOnDragSuccessListener; 487 private boolean mHeadsupDisappearRunning; 488 private View mChildAfterViewWhenDismissed; 489 private View mGroupParentWhenDismissed; 490 private boolean mAboveShelf; 491 private OnUserInteractionCallback mOnUserInteractionCallback; 492 private NotificationGutsManager mNotificationGutsManager; 493 private boolean mIsMinimized; 494 private float mTranslationWhenRemoved; 495 private boolean mWasChildInGroupWhenRemoved; 496 private final NotificationInlineImageResolver mImageResolver; 497 private BigPictureIconManager mBigPictureIconManager; 498 @Nullable 499 private OnExpansionChangedListener mExpansionChangedListener; 500 @Nullable 501 private Runnable mOnIntrinsicHeightReachedRunnable; 502 503 private final float mSmallRoundness; 504 505 private final ListenerSet<DismissButtonTargetVisibilityListener> 506 mDismissButtonTargetVisibilityListeners = new ListenerSet<>(); 507 @RedactionType 508 private int mRedactionType = REDACTION_TYPE_NONE; getLayouts()509 public NotificationContentView[] getLayouts() { 510 return Arrays.copyOf(mLayouts, mLayouts.length); 511 } 512 513 /** 514 * Is this entry pinned and was expanded while doing so 515 */ isPinnedAndExpanded()516 public boolean isPinnedAndExpanded() { 517 if (!isPinned()) { 518 return false; 519 } 520 return mExpandedWhenPinned; 521 } 522 523 @Override isGroupExpansionChanging()524 public boolean isGroupExpansionChanging() { 525 if (isChildInGroup()) { 526 return mNotificationParent.isGroupExpansionChanging(); 527 } 528 return mGroupExpansionChanging; 529 } 530 setSaveSpaceOnLockscreen(boolean saveSpace)531 public void setSaveSpaceOnLockscreen(boolean saveSpace) { 532 mSaveSpaceOnLockscreen = saveSpace; 533 } 534 getSaveSpaceOnLockscreen()535 public boolean getSaveSpaceOnLockscreen() { 536 return mSaveSpaceOnLockscreen; 537 } 538 setGroupExpansionChanging(boolean changing)539 public void setGroupExpansionChanging(boolean changing) { 540 mGroupExpansionChanging = changing; 541 } 542 543 @Override setActualHeightAnimating(boolean animating)544 public void setActualHeightAnimating(boolean animating) { 545 if (mPrivateLayout != null) { 546 mPrivateLayout.setContentHeightAnimating(animating); 547 } 548 } 549 getPrivateLayout()550 public NotificationContentView getPrivateLayout() { 551 return mPrivateLayout; 552 } 553 getPublicLayout()554 public NotificationContentView getPublicLayout() { 555 return mPublicLayout; 556 } 557 getLoggingKey()558 public String getLoggingKey() { 559 return mLoggingKey; 560 } 561 getKey()562 public String getKey() { 563 if (NotificationBundleUi.isEnabled()) { 564 return mKey; 565 } else { 566 return getEntryLegacy().getKey(); 567 } 568 } 569 570 /** 571 * Sets animations running in the layouts of this row, including public, private, and children. 572 * 573 * @param running whether the animations should be started running or stopped. 574 */ setAnimationRunning(boolean running)575 public void setAnimationRunning(boolean running) { 576 // Sets animations running in the private/public layouts. 577 for (NotificationContentView l : mLayouts) { 578 if (l != null) { 579 l.setContentAnimationRunning(running); 580 setIconAnimationRunning(running, l); 581 } 582 } 583 // For groups summaries with children, we want to set the children containers 584 // animating as well. 585 if (mIsSummaryWithChildren) { 586 NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper(); 587 if (viewWrapper != null) { 588 setIconAnimationRunningForChild(running, viewWrapper.getIcon()); 589 } 590 NotificationViewWrapper lowPriWrapper = mChildrenContainer 591 .getMinimizedGroupHeaderWrapper(); 592 if (lowPriWrapper != null) { 593 setIconAnimationRunningForChild(running, lowPriWrapper.getIcon()); 594 } 595 List<ExpandableNotificationRow> notificationChildren = 596 mChildrenContainer.getAttachedChildren(); 597 for (int i = 0; i < notificationChildren.size(); i++) { 598 ExpandableNotificationRow child = notificationChildren.get(i); 599 child.setAnimationRunning(running); 600 } 601 } 602 mAnimationRunning = running; 603 } 604 605 /** 606 * Starts or stops animations of the icons in all potential content views (regardless of 607 * whether they're contracted, expanded, etc). 608 * 609 * @param running whether to start or stop the icon's animation. 610 */ setIconAnimationRunning(boolean running, NotificationContentView layout)611 private void setIconAnimationRunning(boolean running, NotificationContentView layout) { 612 if (layout != null) { 613 View contractedChild = layout.getContractedChild(); 614 View expandedChild = layout.getExpandedChild(); 615 View headsUpChild = layout.getHeadsUpChild(); 616 setIconAnimationRunningForChild(running, contractedChild); 617 setIconAnimationRunningForChild(running, expandedChild); 618 setIconAnimationRunningForChild(running, headsUpChild); 619 } 620 } 621 622 /** 623 * Starts or stops animations of the icon in the provided view's icon and right icon. 624 * 625 * @param running whether to start or stop the icon's animation. 626 * @param child the view with the icon to start or stop. 627 */ setIconAnimationRunningForChild(boolean running, View child)628 private void setIconAnimationRunningForChild(boolean running, View child) { 629 if (child != null) { 630 ImageView icon = child.findViewById(com.android.internal.R.id.icon); 631 setImageViewAnimationRunning(icon, running); 632 ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon); 633 setImageViewAnimationRunning(rightIcon, running); 634 } 635 } 636 637 /** 638 * Starts or stops the animation of a provided image view if it's an AnimationDrawable or an 639 * AnimatedVectorDrawable. 640 * 641 * @param imageView the image view on which to start/stop animation. 642 * @param running whether to start or stop the view's animation. 643 */ setImageViewAnimationRunning(ImageView imageView, boolean running)644 private void setImageViewAnimationRunning(ImageView imageView, boolean running) { 645 if (imageView != null) { 646 Drawable drawable = imageView.getDrawable(); 647 if (drawable instanceof AnimationDrawable animationDrawable) { 648 if (running) { 649 animationDrawable.start(); 650 } else { 651 animationDrawable.stop(); 652 } 653 } else if (drawable instanceof AnimatedVectorDrawable animationDrawable) { 654 if (running) { 655 animationDrawable.start(); 656 } else { 657 animationDrawable.stop(); 658 } 659 } 660 } 661 } 662 663 /** 664 * Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif 665 * or is in an allowList). 666 */ getIsNonblockable()667 public boolean getIsNonblockable() { 668 NotificationBundleUi.assertInLegacyMode(); 669 if (getEntryLegacy() == null) { 670 return true; 671 } 672 return !getEntryLegacy().isBlockable(); 673 } 674 isConversation()675 private boolean isConversation() { 676 if (NotificationBundleUi.isEnabled()) { 677 return getEntryAdapter().getPeopleNotificationType() 678 != PeopleNotificationIdentifier.TYPE_NON_PERSON; 679 } else { 680 return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntryLegacy()) 681 != PeopleNotificationIdentifier.TYPE_NON_PERSON; 682 } 683 } 684 onNotificationUpdated()685 public void onNotificationUpdated() { 686 if (mIsSummaryWithChildren) { 687 Trace.beginSection("ExpNotRow#onNotifUpdated (summary)"); 688 } else { 689 Trace.beginSection("ExpNotRow#onNotifUpdated (leaf)"); 690 } 691 for (NotificationContentView l : mLayouts) { 692 l.onNotificationUpdated(getEntry()); 693 } 694 mShowingPublicInitialized = false; 695 if (mMenuRow != null) { 696 mMenuRow.onNotificationUpdated(); 697 mMenuRow.setAppName(mAppName); 698 } 699 if (mIsSummaryWithChildren) { 700 if (AsyncGroupHeaderViewInflation.isEnabled()) { 701 mChildrenContainer.updateGroupHeaderExpandState(); 702 } else { 703 // We create the header from the background thread instead 704 mChildrenContainer.recreateNotificationHeader(mExpandClickListener, 705 isConversation()); 706 } 707 mChildrenContainer.onNotificationUpdated(); 708 } 709 if (mAnimationRunning) { 710 setAnimationRunning(true); 711 } 712 if (mLastChronometerRunning) { 713 setChronometerRunning(true); 714 } 715 if (mNotificationParent != null) { 716 mNotificationParent.updateChildrenAppearance(); 717 } 718 onAttachedChildrenCountChanged(); 719 mPublicLayout.updateExpandButtons(mShowPublicExpander); 720 updateLimits(); 721 updateShelfIconColor(); 722 if (mUpdateSelfBackgroundOnUpdate) { 723 // Because this is triggered by UiMode change which we already propagated to children, 724 // we know that child rows will receive the same event, and will update their own 725 // backgrounds when they finish inflating, so propagating again would be redundant. 726 mUpdateSelfBackgroundOnUpdate = false; 727 updateBackgroundColorsOfSelf(); 728 } 729 Trace.endSection(); 730 } 731 updateBackgroundColorsOfSelf()732 private void updateBackgroundColorsOfSelf() { 733 super.updateBackgroundColors(); 734 if (mColorUpdateLogger.isEnabled()) { 735 mColorUpdateLogger.logNotificationEvent("ENR.updateBackgroundColorsOfSelf()", 736 mLoggingKey, 737 "normalBgColor=" + hexColorString(getNormalBgColor()) 738 + " background=" + mBackgroundNormal.toDumpString()); 739 } 740 } 741 742 @Override updateBackgroundColors()743 public void updateBackgroundColors() { 744 // Because this call is made by the NSSL only on attached rows at the moment of the 745 // UiMode or Theme change, we have to propagate to our child views. 746 updateBackgroundColorsOfSelf(); 747 if (mIsSummaryWithChildren) { 748 for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) { 749 child.updateBackgroundColors(); 750 } 751 } 752 } 753 754 /** 755 * Call when bubble state has changed and the button on the notification should be updated. 756 */ updateBubbleButton()757 public void updateBubbleButton() { 758 for (NotificationContentView l : mLayouts) { 759 l.updateBubbleButton(getEntry()); 760 } 761 } 762 763 @VisibleForTesting updateShelfIconColor()764 void updateShelfIconColor() { 765 StatusBarIconView expandedIcon = getShelfIcon(); 766 boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L)); 767 boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon, 768 ContrastColorUtil.getInstance(mContext)); 769 int color = StatusBarIconView.NO_COLOR; 770 if (colorize) { 771 color = getOriginalIconColor(); 772 } 773 expandedIcon.setStaticDrawableColor(color); 774 } 775 getOriginalIconColor()776 public int getOriginalIconColor() { 777 if (mIsSummaryWithChildren && !shouldShowPublic()) { 778 if (!AsyncGroupHeaderViewInflation.isEnabled()) { 779 return mChildrenContainer.getVisibleWrapper().getOriginalIconColor(); 780 } 781 } 782 int color = getShowingLayout().getOriginalIconColor(); 783 if (color != Notification.COLOR_INVALID) { 784 return color; 785 } else { 786 if (NotificationBundleUi.isEnabled()) { 787 return mEntryAdapter.getContrastedColor(mContext, mIsMinimized && !isExpanded(), 788 getBackgroundColorWithoutTint()); 789 } else { 790 return getEntryLegacy().getContrastedColor(mContext, mIsMinimized && !isExpanded(), 791 getBackgroundColorWithoutTint()); 792 } 793 } 794 } 795 setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener)796 public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) { 797 mAboveShelfChangedListener = aboveShelfChangedListener; 798 } 799 800 /** 801 * Sets a supplier that can determine whether the keyguard is secure or not. 802 * 803 * @param secureStateProvider A function that returns true if keyguard is secure. 804 */ setSecureStateProvider(BooleanSupplier secureStateProvider)805 public void setSecureStateProvider(BooleanSupplier secureStateProvider) { 806 mSecureStateProvider = secureStateProvider; 807 } 808 updateLimits()809 private void updateLimits() { 810 for (NotificationContentView l : mLayouts) { 811 updateLimitsForView(l); 812 } 813 } 814 815 public interface DismissButtonTargetVisibilityListener { 816 // Called when the notification dismiss button's target visibility changes. 817 // NOTE: This can be called when the dismiss button already has the target visibility. onTargetVisibilityChanged(boolean targetVisible)818 void onTargetVisibilityChanged(boolean targetVisible); 819 } 820 addDismissButtonTargetStateListener( DismissButtonTargetVisibilityListener listener)821 public void addDismissButtonTargetStateListener( 822 DismissButtonTargetVisibilityListener listener) { 823 if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) { 824 return; 825 } 826 827 mDismissButtonTargetVisibilityListeners.addIfAbsent(listener); 828 } 829 removeDismissButtonTargetStateListener( DismissButtonTargetVisibilityListener listener)830 public void removeDismissButtonTargetStateListener( 831 DismissButtonTargetVisibilityListener listener) { 832 if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) { 833 return; 834 } 835 836 mDismissButtonTargetVisibilityListeners.remove(listener); 837 } 838 839 @Override onInterceptHoverEvent(MotionEvent event)840 public boolean onInterceptHoverEvent(MotionEvent event) { 841 if (!NotificationAddXOnHoverToDismiss.isEnabled()) { 842 return super.onInterceptHoverEvent(event); 843 } 844 845 // Do not bother checking the dismiss button's target visibility if the notification cannot 846 // be dismissed. 847 if (!canEntryBeDismissed()) { 848 return false; 849 } 850 851 final Boolean targetVisible = getDismissButtonTargetVisibilityIfAny(event); 852 if (targetVisible != null) { 853 for (DismissButtonTargetVisibilityListener listener : 854 mDismissButtonTargetVisibilityListeners) { 855 listener.onTargetVisibilityChanged(targetVisible); 856 } 857 } 858 859 // Do not consume the hover event so that children still have a chance to process it. 860 return false; 861 } 862 getDismissButtonTargetVisibilityIfAny(MotionEvent event)863 private @Nullable Boolean getDismissButtonTargetVisibilityIfAny(MotionEvent event) { 864 // Returns the dismiss button's target visibility resulted by `event`. Returns null if the 865 // target visibility should not change. 866 867 if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { 868 // The notification dismiss button should be hidden when the hover exit event is located 869 // outside of the notification. NOTE: The hover exit event can be inside the 870 // notification if hover moves from one hoverable child to another. 871 final Rect localBounds = new Rect(0, 0, this.getWidth(), this.getActualHeight()); 872 if (!localBounds.contains((int) event.getX(), (int) event.getY())) { 873 return Boolean.FALSE; 874 } 875 } else if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { 876 return Boolean.TRUE; 877 } 878 879 return null; 880 } 881 updateLimitsForView(NotificationContentView layout)882 private void updateLimitsForView(NotificationContentView layout) { 883 final int maxExpandedHeight; 884 if (isPromotedOngoing()) { 885 maxExpandedHeight = mMaxExpandedHeightForPromotedOngoing; 886 } else { 887 maxExpandedHeight = mMaxExpandedHeight; 888 } 889 890 View contractedView = layout.getContractedChild(); 891 boolean customView = contractedView != null 892 && contractedView.getId() 893 != com.android.internal.R.id.status_bar_latest_event_content; 894 int targetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT; 895 if (NotificationBundleUi.isEnabled()) { 896 targetSdk = mEntryAdapter.getTargetSdk(); 897 } else { 898 targetSdk = getEntryLegacy().targetSdk; 899 } 900 901 boolean beforeN = targetSdk < Build.VERSION_CODES.N; 902 boolean beforeP = targetSdk < Build.VERSION_CODES.P; 903 boolean beforeS = targetSdk < Build.VERSION_CODES.S; 904 int smallHeight; 905 906 boolean isCallLayout = contractedView instanceof CallLayout; 907 boolean isMessagingLayout = contractedView instanceof MessagingLayout 908 || contractedView instanceof ConversationLayout; 909 910 String summarization = null; 911 if (NotificationBundleUi.isEnabled()) { 912 summarization = mEntryAdapter.getSummarization(); 913 } else { 914 summarization = getEntryLegacy().getRanking().getSummarization(); 915 } 916 917 if (customView && beforeS && !mIsSummaryWithChildren) { 918 if (beforeN) { 919 smallHeight = mMaxSmallHeightBeforeN; 920 } else if (beforeP) { 921 smallHeight = mMaxSmallHeightBeforeP; 922 } else { 923 smallHeight = mMaxSmallHeightBeforeS; 924 } 925 } else if (isCallLayout) { 926 smallHeight = maxExpandedHeight; 927 } else if (NmSummarizationUiFlag.isEnabled() 928 && isMessagingLayout 929 && !TextUtils.isEmpty(summarization)) { 930 smallHeight = mMaxSmallHeightWithSummarization; 931 } else { 932 smallHeight = mMaxSmallHeight; 933 } 934 boolean headsUpCustom = layout.getHeadsUpChild() != null && 935 layout.getHeadsUpChild().getId() 936 != com.android.internal.R.id.status_bar_latest_event_content; 937 int headsUpHeight; 938 if (headsUpCustom && beforeS) { 939 if (beforeN) { 940 headsUpHeight = mMaxHeadsUpHeightBeforeN; 941 } else if (beforeP) { 942 headsUpHeight = mMaxHeadsUpHeightBeforeP; 943 } else { 944 headsUpHeight = mMaxHeadsUpHeightBeforeS; 945 } 946 } else { 947 headsUpHeight = mMaxHeadsUpHeight; 948 } 949 NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper( 950 VISIBLE_TYPE_HEADSUP); 951 if (headsUpWrapper != null) { 952 headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight()); 953 } 954 955 layout.setHeights(smallHeight, headsUpHeight, maxExpandedHeight); 956 } 957 958 /** 959 * Check {@link NotificationBundleUi#isEnabled()} 960 * and use {@link #getEntryAdapter()} when true 961 * and {@link #getEntryLegacy()} when false. 962 */ 963 @NonNull 964 @Deprecated 965 public NotificationEntry getEntryLegacy() { 966 NotificationBundleUi.assertInLegacyMode(); 967 return mEntry; 968 } 969 970 /** 971 * Check {@link NotificationBundleUi#isEnabled()} 972 * and use {@link #getEntryAdapter()} when true 973 * and {@link #getEntryLegacy()} when false. 974 */ 975 @NonNull 976 @Deprecated 977 public NotificationEntry getEntry() { 978 return mEntry; 979 } 980 981 @NonNull 982 public EntryAdapter getEntryAdapter() { 983 NotificationBundleUi.unsafeAssertInNewMode(); 984 return mEntryAdapter; 985 } 986 987 @Override 988 public boolean isHeadsUp() { 989 return mIsHeadsUp; 990 } 991 992 public void setHeadsUp(boolean isHeadsUp) { 993 boolean wasAboveShelf = isAboveShelf(); 994 int intrinsicBefore = getIntrinsicHeight(); 995 mIsHeadsUp = isHeadsUp; 996 mPrivateLayout.setHeadsUp(isHeadsUp); 997 if (mIsSummaryWithChildren) { 998 // The overflow might change since we allow more lines as HUN. 999 mChildrenContainer.updateGroupOverflow(); 1000 } 1001 if (intrinsicBefore != getIntrinsicHeight()) { 1002 notifyHeightChanged(/* needsAnimation= */ false); 1003 } 1004 if (isHeadsUp) { 1005 mMustStayOnScreen = true; 1006 setAboveShelf(true); 1007 } else if (isAboveShelf() != wasAboveShelf) { 1008 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 1009 } 1010 if (notificationRowTransparency()) { 1011 updateBackgroundTint(); 1012 } 1013 } 1014 1015 /** 1016 * Indicate that the notification is showing the app icon instead of the small icon. 1017 */ 1018 public void setIsShowingAppIcon(boolean isShowingAppIcon) { 1019 mIsShowingAppIcon = isShowingAppIcon; 1020 } 1021 1022 /** 1023 * Whether or not the notification is showing the app icon instead of the small icon. 1024 */ 1025 public boolean isShowingAppIcon() { 1026 return mIsShowingAppIcon; 1027 } 1028 1029 @Override 1030 public boolean showingPulsing() { 1031 return isHeadsUpState() && (isDozing() || (mOnKeyguard && isBypassEnabled())); 1032 } 1033 1034 /** 1035 * @return if the view is in heads up state, i.e either still heads upped or it's disappearing. 1036 */ 1037 @Override 1038 public boolean isHeadsUpState() { 1039 return mIsHeadsUp || mHeadsupDisappearRunning; 1040 } 1041 1042 public void setRemoteInputController(RemoteInputController r) { 1043 mPrivateLayout.setRemoteInputController(r); 1044 } 1045 1046 /** 1047 * Return the cumulative y-value that the actions container expands via its scale animator when 1048 * remote input is activated. 1049 */ 1050 public float getRemoteInputActionsContainerExpandedOffset() { 1051 if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f; 1052 RemoteInputView expandedRemoteInput = mPrivateLayout.getExpandedRemoteInput(); 1053 if (expandedRemoteInput == null) return 0f; 1054 View actionsContainerLayout = expandedRemoteInput.getActionsContainerLayout(); 1055 if (actionsContainerLayout == null) return 0f; 1056 1057 return actionsContainerLayout.getHeight() * (1 - FOCUS_ANIMATION_MIN_SCALE) * 0.5f; 1058 } 1059 1060 public void addChildNotification(ExpandableNotificationRow row) { 1061 addChildNotification(row, -1); 1062 } 1063 1064 /** 1065 * Set the how much the header should be visible. A value of 0 will make the header fully gone 1066 * and a value of 1 will make the notification look just like normal. 1067 * This is being used for heads up notifications, when they are pinned to the top of the screen 1068 * and the header content is extracted to the statusbar. 1069 * 1070 * @param headerVisibleAmount the amount the header should be visible. 1071 */ 1072 public void setHeaderVisibleAmount(float headerVisibleAmount) { 1073 if (mHeaderVisibleAmount != headerVisibleAmount) { 1074 mHeaderVisibleAmount = headerVisibleAmount; 1075 for (NotificationContentView l : mLayouts) { 1076 l.setHeaderVisibleAmount(headerVisibleAmount); 1077 } 1078 if (mChildrenContainer != null) { 1079 mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount); 1080 } 1081 notifyHeightChanged(/* needsAnimation= */ false); 1082 } 1083 } 1084 1085 @Override 1086 public float getHeaderVisibleAmount() { 1087 return mHeaderVisibleAmount; 1088 } 1089 1090 @Override 1091 public void markHeadsUpSeen() { 1092 super.markHeadsUpSeen(); 1093 mMustStayOnScreen = false; 1094 } 1095 1096 /** 1097 * 1098 * @return true when compact version of Heads Up is on the screen. 1099 */ 1100 public boolean isCompactConversationHeadsUpOnScreen() { 1101 final NotificationViewWrapper viewWrapper = 1102 getVisibleNotificationViewWrapper(); 1103 1104 return viewWrapper instanceof NotificationCompactMessagingTemplateViewWrapper; 1105 } 1106 /** 1107 * @see NotificationChildrenContainer#setUntruncatedChildCount(int) 1108 */ 1109 public void setUntruncatedChildCount(int childCount) { 1110 if (mChildrenContainer == null) { 1111 mChildrenContainerStub.inflate(); 1112 } 1113 mChildrenContainer.setUntruncatedChildCount(childCount); 1114 } 1115 1116 /** 1117 * @see NotificationChildrenContainer#setNotificationGroupWhen(long) 1118 */ 1119 public void setNotificationGroupWhen(long whenMillis) { 1120 if (mIsSummaryWithChildren) { 1121 mChildrenContainer.setNotificationGroupWhen(whenMillis); 1122 mPublicLayout.setNotificationWhen(whenMillis); 1123 } else { 1124 Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")" 1125 + " mIsSummaryWithChildren: false" 1126 + " mChildrenContainer has not been inflated yet."); 1127 } 1128 } 1129 1130 /** 1131 * Called after children have been attached to set the expansion states 1132 */ 1133 public void resetChildSystemExpandedStates() { 1134 if (isSummaryWithChildren()) { 1135 mChildrenContainer.updateExpansionStates(); 1136 } 1137 } 1138 1139 /** 1140 * Add a child notification to this view. 1141 * 1142 * @param row the row to add 1143 * @param childIndex the index to add it at, if -1 it will be added at the end 1144 */ 1145 public void addChildNotification(ExpandableNotificationRow row, int childIndex) { 1146 if (mChildrenContainer == null) { 1147 mChildrenContainerStub.inflate(); 1148 } 1149 1150 if (row.keepInParentForDismissAnimation()) { 1151 logSkipAttachingKeepInParentChild(row); 1152 return; 1153 } 1154 1155 mChildrenContainer.addNotification(row, childIndex); 1156 onAttachedChildrenCountChanged(); 1157 row.setIsChildInGroup(true, this); 1158 } 1159 1160 public void removeChildNotification(ExpandableNotificationRow row) { 1161 if (mChildrenContainer != null) { 1162 mChildrenContainer.removeNotification(row); 1163 row.setKeepInParentForDismissAnimation(false); 1164 } 1165 onAttachedChildrenCountChanged(); 1166 row.setIsChildInGroup(false, null); 1167 } 1168 1169 /** 1170 * Removes the children notifications which were marked to keep for the dismissal animation. 1171 */ 1172 public void removeChildrenWithKeepInParent() { 1173 if (mChildrenContainer == null) return; 1174 1175 List<ExpandableNotificationRow> clonedList = new ArrayList<>( 1176 mChildrenContainer.getAttachedChildren()); 1177 boolean childCountChanged = false; 1178 for (ExpandableNotificationRow child : clonedList) { 1179 if (child.keepInParentForDismissAnimation()) { 1180 mChildrenContainer.removeNotification(child); 1181 child.setIsChildInGroup(false, null); 1182 child.setKeepInParentForDismissAnimation(false); 1183 logKeepInParentChildDetached(child); 1184 childCountChanged = true; 1185 } 1186 } 1187 1188 if (childCountChanged) { 1189 onAttachedChildrenCountChanged(); 1190 } 1191 } 1192 1193 /** 1194 * Returns the child notification at [index], or null if no such child. 1195 */ 1196 @Nullable 1197 public ExpandableNotificationRow getChildNotificationAt(int index) { 1198 if (mChildrenContainer == null 1199 || mChildrenContainer.getAttachedChildren().size() <= index) { 1200 return null; 1201 } else { 1202 return mChildrenContainer.getAttachedChildren().get(index); 1203 } 1204 } 1205 1206 @Override 1207 public boolean isChildInGroup() { 1208 return mNotificationParent != null; 1209 } 1210 1211 public ExpandableNotificationRow getNotificationParent() { 1212 return mNotificationParent; 1213 } 1214 1215 /** 1216 * @param isChildInGroup Is this notification now in a group 1217 * @param parent the new parent notification 1218 */ 1219 public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) { 1220 if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) { 1221 mNotificationParent.setChildIsExpanding(false); 1222 mNotificationParent.setExpandingClipPath(null); 1223 mNotificationParent.setExtraWidthForClipping(0.0f); 1224 mNotificationParent.setMinimumHeightForClipping(0); 1225 } 1226 mNotificationParent = isChildInGroup ? parent : null; 1227 mPrivateLayout.setIsChildInGroup(isChildInGroup); 1228 if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { 1229 mPublicLayout.setIsChildInGroup(isChildInGroup); 1230 } 1231 1232 updateBackgroundForGroupState(); 1233 updateClickAndFocus(); 1234 if (mNotificationParent != null) { 1235 setOverrideTintColor(NO_COLOR, 0.0f); 1236 mNotificationParent.updateBackgroundForGroupState(); 1237 } 1238 updateBackgroundClipping(); 1239 updateBaseRoundness(); 1240 } 1241 1242 @Override 1243 public boolean onInterceptTouchEvent(MotionEvent ev) { 1244 // Other parts of the system may intercept and handle all the falsing. 1245 // Otherwise, if we see motion and follow-on events, try to classify them as a tap. 1246 if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) { 1247 mFalsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY); 1248 } 1249 return super.onInterceptTouchEvent(ev); 1250 } 1251 1252 @Override 1253 public boolean onTouchEvent(MotionEvent event) { 1254 if (event.getActionMasked() != MotionEvent.ACTION_DOWN 1255 || !isChildInGroup() || isGroupExpanded()) { 1256 return super.onTouchEvent(event); 1257 } else { 1258 return false; 1259 } 1260 } 1261 1262 @Override 1263 public boolean isSummaryWithChildren() { 1264 return mIsSummaryWithChildren; 1265 } 1266 1267 @Override 1268 public boolean areChildrenExpanded() { 1269 return mChildrenExpanded; 1270 } 1271 1272 public List<ExpandableNotificationRow> getAttachedChildren() { 1273 return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren(); 1274 } 1275 1276 /** 1277 * Recursively collects the [{@link ExpandableViewState#location}]s populating the provided 1278 * map. 1279 * The visibility of each child is determined by the {@link View#getVisibility()}. 1280 * Locations are added to the provided map including locations from child views, that are 1281 * visible. 1282 */ 1283 public void collectVisibleLocations(Map<String, Integer> locationsMap) { 1284 if (getVisibility() == View.VISIBLE) { 1285 locationsMap.put(getKey(), getViewState().location); 1286 if (mChildrenContainer != null) { 1287 List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren(); 1288 for (int i = 0; i < children.size(); i++) { 1289 children.get(i).collectVisibleLocations(locationsMap); 1290 } 1291 } 1292 } 1293 } 1294 1295 /** 1296 * Updates states of all children. 1297 */ 1298 public void updateChildrenStates() { 1299 if (mIsSummaryWithChildren) { 1300 ExpandableViewState parentState = getViewState(); 1301 mChildrenContainer.updateState(parentState); 1302 } 1303 } 1304 1305 /** 1306 * Applies children states. 1307 */ 1308 public void applyChildrenState() { 1309 if (mIsSummaryWithChildren) { 1310 mChildrenContainer.applyState(); 1311 } 1312 } 1313 1314 /** 1315 * Starts child animations. 1316 */ 1317 public void startChildAnimation(AnimationProperties properties) { 1318 if (mIsSummaryWithChildren) { 1319 mChildrenContainer.startAnimationToState(properties); 1320 } 1321 } 1322 1323 public ExpandableNotificationRow getViewAtPosition(float y) { 1324 if (!mIsSummaryWithChildren || !mChildrenExpanded) { 1325 return this; 1326 } else { 1327 ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); 1328 return view == null ? this : view; 1329 } 1330 } 1331 1332 public NotificationGuts getGuts() { 1333 return mGuts; 1334 } 1335 1336 /** 1337 * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this 1338 * the notification will be rendered on top of the screen. 1339 */ 1340 public void setPinnedStatus(PinnedStatus pinnedStatus) { 1341 int intrinsicHeight = getIntrinsicHeight(); 1342 boolean wasAboveShelf = isAboveShelf(); 1343 mPinnedStatus = pinnedStatus; 1344 if (intrinsicHeight != getIntrinsicHeight()) { 1345 notifyHeightChanged(/* needsAnimation= */ false); 1346 } 1347 if (pinnedStatus.isPinned()) { 1348 setAnimationRunning(true); 1349 mExpandedWhenPinned = false; 1350 } else if (mExpandedWhenPinned) { 1351 setUserExpanded(true); 1352 } 1353 setChronometerRunning(mLastChronometerRunning); 1354 if (isAboveShelf() != wasAboveShelf) { 1355 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 1356 } 1357 } 1358 1359 @Override 1360 public boolean isPinned() { 1361 return mPinnedStatus.isPinned(); 1362 } 1363 1364 @Override 1365 public PinnedStatus getPinnedStatus() { 1366 return mPinnedStatus; 1367 } 1368 1369 @Override 1370 public int getPinnedHeadsUpHeight() { 1371 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); 1372 } 1373 1374 /** 1375 * @param atLeastMinHeight should the value returned be at least the minimum height. 1376 * Used to avoid cyclic calls 1377 * @return the height of the heads up notification when pinned 1378 */ 1379 private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) { 1380 if (mIsSummaryWithChildren) { 1381 return mChildrenContainer.getIntrinsicHeight(); 1382 } 1383 if (isPromotedOngoing()) { 1384 return getMaxExpandHeight(); 1385 } 1386 if (mExpandedWhenPinned) { 1387 return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); 1388 } else if (android.app.Flags.compactHeadsUpNotification() 1389 && getShowingLayout().isHUNCompact()) { 1390 return getHeadsUpHeight(); 1391 } else if (atLeastMinHeight) { 1392 return Math.max(getCollapsedHeight(), getHeadsUpHeight()); 1393 } else { 1394 return getHeadsUpHeight(); 1395 } 1396 } 1397 1398 /** 1399 * Mark whether this notification was just clicked, i.e. the user has just clicked this 1400 * notification in this frame. 1401 */ 1402 public void setJustClicked(boolean justClicked) { 1403 mJustClicked = justClicked; 1404 } 1405 1406 /** 1407 * @return true if this notification has been clicked in this frame, false otherwise 1408 */ 1409 public boolean wasJustClicked() { 1410 return mJustClicked; 1411 } 1412 1413 public void setChronometerRunning(boolean running) { 1414 mLastChronometerRunning = running; 1415 setChronometerRunning(running, mPrivateLayout); 1416 setChronometerRunning(running, mPublicLayout); 1417 if (mChildrenContainer != null) { 1418 List<ExpandableNotificationRow> notificationChildren = 1419 mChildrenContainer.getAttachedChildren(); 1420 for (int i = 0; i < notificationChildren.size(); i++) { 1421 ExpandableNotificationRow child = notificationChildren.get(i); 1422 child.setChronometerRunning(running); 1423 } 1424 } 1425 } 1426 1427 private void setChronometerRunning(boolean running, NotificationContentView layout) { 1428 if (layout != null) { 1429 running = running || isPinned(); 1430 View contractedChild = layout.getContractedChild(); 1431 View expandedChild = layout.getExpandedChild(); 1432 View headsUpChild = layout.getHeadsUpChild(); 1433 setChronometerRunningForChild(running, contractedChild); 1434 setChronometerRunningForChild(running, expandedChild); 1435 setChronometerRunningForChild(running, headsUpChild); 1436 } 1437 } 1438 1439 private void setChronometerRunningForChild(boolean running, View child) { 1440 if (child != null) { 1441 View chronometer = child.findViewById(com.android.internal.R.id.chronometer); 1442 if (chronometer instanceof Chronometer) { 1443 ((Chronometer) chronometer).setStarted(running); 1444 } 1445 } 1446 } 1447 1448 /** 1449 * @return the main notification view wrapper. 1450 */ 1451 public NotificationViewWrapper getNotificationViewWrapper() { 1452 if (mIsSummaryWithChildren) { 1453 return mChildrenContainer.getNotificationViewWrapper(); 1454 } 1455 return mPrivateLayout.getNotificationViewWrapper(); 1456 } 1457 1458 /** 1459 * @return the currently visible notification view wrapper. This can be different from 1460 * {@link #getNotificationViewWrapper()} in case it is a low-priority group. 1461 */ 1462 public NotificationViewWrapper getVisibleNotificationViewWrapper() { 1463 if (mIsSummaryWithChildren && !shouldShowPublic()) { 1464 return mChildrenContainer.getVisibleWrapper(); 1465 } 1466 return getShowingLayout().getVisibleWrapper(); 1467 } 1468 1469 /** 1470 * @return whether the notification row is long clickable or not. 1471 */ 1472 public boolean isNotificationRowLongClickable() { 1473 if (mLongPressListener == null) { 1474 return false; 1475 } 1476 1477 if (!areGutsExposed()) { // guts is not opened 1478 return true; 1479 } 1480 1481 // if it is leave behind, it shouldn't be long clickable. 1482 return !isGutsLeaveBehind(); 1483 } 1484 1485 public void setLongPressListener(LongPressListener longPressListener) { 1486 mLongPressListener = longPressListener; 1487 } 1488 1489 public void setDragController(ExpandableNotificationRowDragController dragController) { 1490 mDragController = dragController; 1491 } 1492 1493 @Override 1494 public void setOnClickListener(@Nullable OnClickListener l) { 1495 super.setOnClickListener(l); 1496 mOnClickListener = l; 1497 updateClickAndFocus(); 1498 } 1499 1500 /** 1501 * The click listener for the bubble button. 1502 */ 1503 @Nullable 1504 public View.OnClickListener getBubbleClickListener() { 1505 return mBubbleClickListener; 1506 } 1507 1508 /** 1509 * Sets the click listener for the bubble button. 1510 */ 1511 public void setBubbleClickListener(@Nullable OnClickListener l) { 1512 mBubbleClickListener = l; 1513 // ensure listener is passed to the content views 1514 mPrivateLayout.updateBubbleButton(getEntry()); 1515 mPublicLayout.updateBubbleButton(getEntry()); 1516 } 1517 1518 /** 1519 * The click listener for the snooze button. 1520 */ 1521 public View.OnClickListener getSnoozeClickListener(MenuItem item) { 1522 return v -> { 1523 // Dismiss a snoozed notification if one is still left behind 1524 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 1525 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 1526 false /* resetMenu */); 1527 mNotificationGutsManager.openGuts(this, 0, 0, item); 1528 mIsSnoozed = true; 1529 }; 1530 } 1531 1532 private void updateClickAndFocus() { 1533 boolean normalChild = !isChildInGroup() || isGroupExpanded(); 1534 boolean clickable = mOnClickListener != null && normalChild; 1535 if (isFocusable() != normalChild) { 1536 setFocusable(normalChild); 1537 } 1538 if (isClickable() != clickable) { 1539 setClickable(clickable); 1540 } 1541 } 1542 1543 public void setGutsView(MenuItem item) { 1544 if (getGuts() != null && item.getGutsView() instanceof NotificationGuts.GutsContent) { 1545 getGuts().setGutsContent((NotificationGuts.GutsContent) item.getGutsView()); 1546 } 1547 } 1548 1549 @Override 1550 public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { 1551 boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null; 1552 if (existed) { 1553 removeView(mMenuRow.getMenuView()); 1554 } 1555 if (plugin == null) { 1556 return; 1557 } 1558 mMenuRow = plugin; 1559 if (mMenuRow.shouldUseDefaultMenuItems()) { 1560 ArrayList<MenuItem> items = new ArrayList<>(); 1561 items.add(NotificationMenuRow.createConversationItem(mContext)); 1562 items.add(NotificationMenuRow.createPartialConversationItem(mContext)); 1563 items.add(NotificationMenuRow.createInfoItem(mContext)); 1564 items.add(NotificationMenuRow.createSnoozeItem(mContext)); 1565 mMenuRow.setMenuItems(items); 1566 } 1567 if (existed) { 1568 createMenu(); 1569 } 1570 } 1571 1572 @Override 1573 public void onPluginDisconnected(NotificationMenuRowPlugin plugin) { 1574 boolean existed = mMenuRow.getMenuView() != null; 1575 mMenuRow = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); 1576 if (existed) { 1577 createMenu(); 1578 } 1579 } 1580 1581 @Override 1582 public boolean hasFinishedInitialization() { 1583 if (NotificationBundleUi.isEnabled()) { 1584 return initializationTime != -1 1585 && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY; 1586 } else { 1587 return getEntryLegacy().hasFinishedInitialization(); 1588 } 1589 } 1590 1591 public void resetInitializationTime() { 1592 initializationTime = -1; 1593 } 1594 1595 public void setInitializationTime(long time) { 1596 if (initializationTime == -1) { 1597 initializationTime = time; 1598 } 1599 } 1600 1601 /** 1602 * Get a handle to a NotificationMenuRowPlugin whose menu view has been added to our hierarchy, 1603 * or null if there is no menu row 1604 * 1605 * @return a {@link NotificationMenuRowPlugin}, or null 1606 */ 1607 @Nullable 1608 public NotificationMenuRowPlugin createMenu() { 1609 if (mMenuRow == null) { 1610 return null; 1611 } 1612 if (mMenuRow.getMenuView() == null) { 1613 mMenuRow.createMenu(this); 1614 mMenuRow.setAppName(mAppName); 1615 FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 1616 LayoutParams.MATCH_PARENT); 1617 addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp); 1618 } 1619 return mMenuRow; 1620 } 1621 1622 @Nullable 1623 public NotificationMenuRowPlugin getProvider() { 1624 return mMenuRow; 1625 } 1626 1627 @Override 1628 public void onDensityOrFontScaleChanged() { 1629 super.onDensityOrFontScaleChanged(); 1630 initDimens(); 1631 initBackground(); 1632 reInflateViews(); 1633 } 1634 1635 private void reInflateViews() { 1636 Trace.beginSection("ExpandableNotificationRow#reInflateViews"); 1637 // Let's update our childrencontainer. This is intentionally not guarded with 1638 // mIsSummaryWithChildren since we might have had children but not anymore. 1639 if (mChildrenContainer != null) { 1640 mChildrenContainer.reInflateViews(mExpandClickListener); 1641 } 1642 if (mGuts != null) { 1643 NotificationGuts oldGuts = mGuts; 1644 int index = indexOfChild(oldGuts); 1645 removeView(oldGuts); 1646 mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate( 1647 R.layout.notification_guts, this, false); 1648 mGuts.setVisibility(oldGuts.isExposed() ? VISIBLE : GONE); 1649 addView(mGuts, index); 1650 } 1651 View oldMenu = mMenuRow == null ? null : mMenuRow.getMenuView(); 1652 if (oldMenu != null) { 1653 int menuIndex = indexOfChild(oldMenu); 1654 removeView(oldMenu); 1655 mMenuRow.createMenu(ExpandableNotificationRow.this); 1656 mMenuRow.setAppName(mAppName); 1657 addView(mMenuRow.getMenuView(), menuIndex); 1658 } 1659 for (NotificationContentView l : mLayouts) { 1660 l.reinflate(); 1661 l.reInflateViews(); 1662 } 1663 if (NotificationBundleUi.isEnabled()) { 1664 mEntryAdapter.prepareForInflation(); 1665 } else { 1666 getEntryLegacy().getSbn().clearPackageContext(); 1667 } 1668 // TODO: Move content inflation logic out of this call 1669 RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); 1670 params.setNeedsReinflation(true); 1671 1672 var rebindEndCallback = mRebindingTracker.trackRebinding(NotificationBundleUi.isEnabled() 1673 ? mEntryAdapter.getKey() : getEntryLegacy().getKey()); 1674 mRowContentBindStage.requestRebind(mEntry, (e) -> rebindEndCallback.onFinished()); 1675 Trace.endSection(); 1676 } 1677 1678 @Override 1679 public void onConfigurationChanged(Configuration newConfig) { 1680 super.onConfigurationChanged(newConfig); 1681 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 1682 mMenuRow.onConfigurationChanged(); 1683 } 1684 if (mImageResolver != null) { 1685 mImageResolver.updateMaxImageSizes(); 1686 } 1687 if (mBigPictureIconManager != null) { 1688 mBigPictureIconManager.updateMaxImageSizes(); 1689 } 1690 } 1691 1692 public void onUiModeChanged() { 1693 mUpdateSelfBackgroundOnUpdate = true; 1694 reInflateViews(); 1695 if (mChildrenContainer != null) { 1696 for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) { 1697 child.onUiModeChanged(); 1698 } 1699 } 1700 } 1701 1702 public void setContentBackground(int customBackgroundColor, boolean animate, 1703 NotificationContentView notificationContentView) { 1704 if (getShowingLayout() == notificationContentView) { 1705 setTintColor(customBackgroundColor, animate); 1706 } 1707 } 1708 1709 @Override 1710 protected void setBackgroundTintColor(int color) { 1711 if (notificationRowTransparency()) { 1712 boolean isColorized = false; 1713 if (NotificationBundleUi.isEnabled()) { 1714 if (mEntryAdapter != null) { 1715 isColorized = mEntryAdapter.isColorized(); 1716 } 1717 } else { 1718 if (mEntry != null) { 1719 isColorized = mEntry.getSbn().getNotification().isColorized(); 1720 } 1721 } 1722 boolean isTransparent = usesTransparentBackground(); 1723 if (isColorized) { 1724 // For colorized notifications, use a color that matches the tint color at 90% alpha 1725 // when the row is transparent. 1726 color = ColorUtils.setAlphaComponent( 1727 color, (int) (0xFF * (isTransparent ? 0.9f : 1))); 1728 } else { 1729 // For non-colorized notifications, use the semi-transparent normal color token 1730 // when the row is transparent, and the opaque color token otherwise. 1731 if (!isTransparent && mBgTint == NO_COLOR) { 1732 color = mOpaqueColor; 1733 } 1734 } 1735 } 1736 super.setBackgroundTintColor(color); 1737 NotificationContentView view = getShowingLayout(); 1738 if (view != null) { 1739 view.setBackgroundTintColor(color); 1740 } 1741 } 1742 1743 public void closeRemoteInput() { 1744 for (NotificationContentView l : mLayouts) { 1745 l.closeRemoteInput(); 1746 } 1747 } 1748 1749 /** 1750 * Set by how much the single line view should be indented. 1751 */ 1752 public void setSingleLineWidthIndention(int indention) { 1753 mPrivateLayout.setSingleLineWidthIndention(indention); 1754 } 1755 1756 public HybridNotificationView getSingleLineView() { 1757 return mPrivateLayout.getSingleLineView(); 1758 } 1759 1760 /** 1761 * Whether this row is displayed over the unoccluded lockscreen. Returns false on the 1762 * locked shade. 1763 */ 1764 public boolean isOnKeyguard() { 1765 return mOnKeyguard; 1766 } 1767 1768 @Override 1769 public void dismiss(boolean refocusOnDismiss) { 1770 super.dismiss(refocusOnDismiss); 1771 setLongPressListener(null); 1772 setDragController(null); 1773 mGroupParentWhenDismissed = mNotificationParent; 1774 mChildAfterViewWhenDismissed = null; 1775 if (isChildInGroup()) { 1776 List<ExpandableNotificationRow> notificationChildren = 1777 mNotificationParent.getAttachedChildren(); 1778 int i = notificationChildren.indexOf(this); 1779 if (i != -1 && i < notificationChildren.size() - 1) { 1780 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1); 1781 } 1782 } 1783 } 1784 1785 /** 1786 * @return if this entry should be kept in its parent during removal. 1787 */ 1788 public boolean keepInParentForDismissAnimation() { 1789 return mKeepInParentForDismissAnimation; 1790 } 1791 1792 public void setKeepInParentForDismissAnimation(boolean keepInParent) { 1793 mKeepInParentForDismissAnimation = keepInParent; 1794 } 1795 1796 /** @return true if the User has dismissed this notif's parent */ 1797 public boolean isParentDismissed() { 1798 if (NotificationBundleUi.isEnabled()) { 1799 return getEntryAdapter().getDismissState() == PARENT_DISMISSED; 1800 } else { 1801 return getEntryLegacy().getDismissState() == PARENT_DISMISSED; 1802 } 1803 } 1804 1805 @Override 1806 public boolean isRemoved() { 1807 return mRemoved; 1808 } 1809 1810 public void setRemoved() { 1811 mRemoved = true; 1812 mTranslationWhenRemoved = getTranslationY(); 1813 mWasChildInGroupWhenRemoved = isChildInGroup(); 1814 if (isChildInGroup()) { 1815 mTranslationWhenRemoved += getNotificationParent().getTranslationY(); 1816 } 1817 for (NotificationContentView l : mLayouts) { 1818 l.setRemoved(); 1819 } 1820 } 1821 1822 public boolean wasChildInGroupWhenRemoved() { 1823 return mWasChildInGroupWhenRemoved; 1824 } 1825 1826 public float getTranslationWhenRemoved() { 1827 return mTranslationWhenRemoved; 1828 } 1829 1830 public NotificationChildrenContainer getChildrenContainer() { 1831 return mChildrenContainer; 1832 } 1833 1834 /** 1835 * @return An non-null instance of mChildrenContainer, inflate it if not yet. 1836 */ 1837 public @NonNull NotificationChildrenContainer getChildrenContainerNonNull() { 1838 if (mChildrenContainer == null) { 1839 mChildrenContainerStub.inflate(); 1840 } 1841 return mChildrenContainer; 1842 } 1843 1844 /** 1845 * Set the group notification header view 1846 * @param headerView header view to set 1847 */ 1848 public void setGroupHeader(NotificationHeaderView headerView) { 1849 NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull(); 1850 childrenContainer.setGroupHeader( 1851 /* headerView= */ headerView, 1852 /* onClickListener= */ mExpandClickListener 1853 ); 1854 if (TransparentHeaderFix.isEnabled()) { 1855 updateBackgroundForGroupState(); 1856 } 1857 } 1858 1859 /** 1860 * Set the low-priority group notification header view 1861 * @param headerView header view to set 1862 */ 1863 public void setMinimizedGroupHeader(NotificationHeaderView headerView) { 1864 NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull(); 1865 childrenContainer.setLowPriorityGroupHeader( 1866 /* headerViewLowPriority= */ headerView, 1867 /* onClickListener= */ mExpandClickListener 1868 ); 1869 } 1870 1871 /** 1872 * Set the redaction type of the row. 1873 */ 1874 public void setRedactionType(@RedactionType int redactionType) { 1875 mRedactionType = redactionType; 1876 } 1877 1878 /** 1879 * Init the bundle header view. The ComposeView is initialized within with the passed viewModel. 1880 * This can only be init once and not in conjunction with any other header view. 1881 */ 1882 public void initBundleHeader(@NonNull BundleHeaderViewModelImpl bundleHeaderViewModel) { 1883 if (NotificationBundleUi.isUnexpectedlyInLegacyMode()) return; 1884 NotificationChildrenContainer childrenContainer = getChildrenContainerNonNull(); 1885 bundleHeaderViewModel.setOnExpandClickListener(mExpandClickListener); 1886 1887 childrenContainer.initBundleHeader(bundleHeaderViewModel); 1888 1889 if (TransparentHeaderFix.isEnabled()) { 1890 updateBackgroundForGroupState(); 1891 } 1892 } 1893 1894 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { 1895 boolean wasAboveShelf = isAboveShelf(); 1896 boolean changed = headsUpAnimatingAway != mHeadsupDisappearRunning; 1897 mHeadsupDisappearRunning = headsUpAnimatingAway; 1898 mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway); 1899 if (changed && mHeadsUpAnimatingAwayListener != null) { 1900 mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway); 1901 } 1902 if (isAboveShelf() != wasAboveShelf) { 1903 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 1904 } 1905 } 1906 1907 public void setHeadsUpAnimatingAwayListener(Consumer<Boolean> listener) { 1908 mHeadsUpAnimatingAwayListener = listener; 1909 } 1910 1911 /** 1912 * @return if the view was just heads upped and is now animating away. During such a time the 1913 * layout needs to be kept consistent 1914 */ 1915 @Override 1916 public boolean isHeadsUpAnimatingAway() { 1917 return mHeadsupDisappearRunning; 1918 } 1919 1920 public View getChildAfterViewWhenDismissed() { 1921 return mChildAfterViewWhenDismissed; 1922 } 1923 1924 public View getGroupParentWhenDismissed() { 1925 return mGroupParentWhenDismissed; 1926 } 1927 1928 /** 1929 * Dismisses the notification. 1930 * 1931 * @param fromAccessibility whether this dismiss is coming from an accessibility action 1932 */ 1933 public void performDismiss(boolean fromAccessibility) { 1934 mMetricsLogger.count(NotificationCounters.NOTIFICATION_DISMISSED, 1); 1935 dismiss(fromAccessibility); 1936 if (canEntryBeDismissed()) { 1937 if (mOnUserInteractionCallback != null) { 1938 if (Flags.notificationReentrantDismiss()) { 1939 Runnable futureDismissal = mOnUserInteractionCallback.registerFutureDismissal( 1940 mEntry, REASON_CANCEL); 1941 post(futureDismissal); 1942 } else { 1943 mOnUserInteractionCallback.registerFutureDismissal(mEntry, REASON_CANCEL).run(); 1944 } 1945 } 1946 } 1947 } 1948 1949 @Override 1950 public View getShelfTransformationTarget() { 1951 if (mIsSummaryWithChildren && !shouldShowPublic()) { 1952 NotificationViewWrapper viewWrapper = mChildrenContainer.getVisibleWrapper(); 1953 if (AsyncGroupHeaderViewInflation.isEnabled() && viewWrapper == null) { 1954 return null; 1955 } 1956 return viewWrapper.getShelfTransformationTarget(); 1957 } 1958 return getShowingLayout().getShelfTransformationTarget(); 1959 } 1960 1961 /** 1962 * @return whether the notification is currently showing a view with an icon. 1963 */ 1964 public boolean isShowingIcon() { 1965 if (areGutsExposed()) { 1966 return false; 1967 } 1968 return getShelfTransformationTarget() != null; 1969 } 1970 1971 @Override 1972 protected void updateContentTransformation() { 1973 if (mExpandAnimationRunning) { 1974 return; 1975 } 1976 super.updateContentTransformation(); 1977 } 1978 1979 @Override 1980 protected void applyContentTransformation(float contentAlpha, float translationY) { 1981 super.applyContentTransformation(contentAlpha, translationY); 1982 if (!mIsLastChild) { 1983 // Don't fade views unless we're last 1984 contentAlpha = 1.0f; 1985 } 1986 for (NotificationContentView l : mLayouts) { 1987 l.setAlpha(contentAlpha); 1988 l.setTranslationY(translationY); 1989 } 1990 if (mChildrenContainer != null) { 1991 mChildrenContainer.setAlpha(contentAlpha); 1992 mChildrenContainer.setTranslationY(translationY); 1993 // TODO: handle children fade out better 1994 } 1995 } 1996 1997 /** 1998 * Sets the alpha on the content, while leaving the background of the row itself as is. 1999 * 2000 * @param alpha alpha value to apply to the notification content 2001 */ 2002 public void setContentAlpha(float alpha) { 2003 for (NotificationContentView l : mLayouts) { 2004 l.setAlpha(alpha); 2005 } 2006 if (mChildrenContainer != null) { 2007 mChildrenContainer.setContentAlpha(alpha); 2008 } 2009 } 2010 2011 /** 2012 * Set if the row is minimized. 2013 */ 2014 public void setIsMinimized(boolean isMinimized) { 2015 mIsMinimized = isMinimized; 2016 mPrivateLayout.setIsLowPriority(isMinimized); 2017 if (mChildrenContainer != null) { 2018 mChildrenContainer.setIsMinimized(isMinimized); 2019 } 2020 } 2021 2022 public boolean isMinimized() { 2023 return mIsMinimized; 2024 } 2025 2026 /** 2027 * Interface for logging {{@link ExpandableNotificationRow} events.} 2028 */ 2029 public interface ExpandableNotificationRowLogger { 2030 /** 2031 * Called when the notification is expanded / collapsed. 2032 */ 2033 void logNotificationExpansion(String key, int location, boolean userAction, 2034 boolean expanded); 2035 2036 /** 2037 * Called when a notification which was previously kept in its parent for the 2038 * dismiss animation is finally detached from its parent. 2039 */ 2040 void logKeepInParentChildDetached(String child, String oldParent); 2041 2042 /** 2043 * Called when we want to attach a notification to a new parent, 2044 * but it still has the keepInParent flag set, so we skip it. 2045 */ 2046 void logSkipAttachingKeepInParentChild( 2047 String child, 2048 String newParent 2049 ); 2050 2051 /** 2052 * Called when an ExpandableNotificationRow transient view is removed from the 2053 * NotificationChildrenContainer 2054 */ 2055 void logRemoveTransientFromContainer( 2056 String childEntry, 2057 String containerEntry 2058 ); 2059 2060 /** 2061 * Called when an ExpandableNotificationRow transient view is removed from the 2062 * NotificationStackScrollLayout 2063 */ 2064 void logRemoveTransientFromNssl( 2065 String childEntry 2066 ); 2067 2068 /** 2069 * Called when an ExpandableNotificationRow transient view is removed from a ViewGroup that 2070 * is not NotificationChildrenContainer or NotificationStackScrollLayout 2071 */ 2072 void logRemoveTransientFromViewGroup( 2073 String childEntry, 2074 ViewGroup containerView 2075 ); 2076 2077 /** 2078 * Called when an ExpandableNotificationRow transient view is added to this 2079 * ExpandableNotificationRow 2080 */ 2081 void logAddTransientRow( 2082 String childEntry, 2083 String containerEntry, 2084 int index 2085 ); 2086 2087 /** 2088 * Called when an ExpandableNotificationRow transient view is removed from this 2089 * ExpandableNotificationRow 2090 */ 2091 void logRemoveTransientRow( 2092 String childEntry, 2093 String containerEntry 2094 ); 2095 2096 /** 2097 * Called when resetting the alpha value for content views 2098 */ 2099 void logResetAllContentAlphas( 2100 String entry 2101 ); 2102 2103 /** 2104 * Called when resetting the alpha value for content views is skipped 2105 */ 2106 void logSkipResetAllContentAlphas( 2107 String entry 2108 ); 2109 2110 /** Called when we start an appear animation. */ 2111 void logStartAppearAnimation(String entry, boolean isAppear); 2112 2113 /** Called when we cancel the running appear animation. */ 2114 void logCancelAppearDrawing(String entry, boolean wasDrawing); 2115 2116 /** Called when the animator of the appear animation is started. */ 2117 void logAppearAnimationStarted(String entry, boolean isAppear); 2118 2119 /** Called when we prepared an appear animation, but the animator was never started. */ 2120 void logAppearAnimationSkipped(String entry, boolean isAppear); 2121 2122 /** Called when the animator of the appear animation is finished. */ 2123 void logAppearAnimationFinished( 2124 String entry, 2125 boolean isAppear, 2126 boolean cancelled 2127 ); 2128 } 2129 2130 /** 2131 * Constructs an ExpandableNotificationRow. Used by layout inflation. 2132 * 2133 * @param context passed to image resolver 2134 * @param attrs attributes used to initialize parent view 2135 */ 2136 public ExpandableNotificationRow(Context context, AttributeSet attrs) { 2137 this(context, attrs, context); 2138 // NOTE(b/317503801): Always crash when using the insecure constructor. 2139 throw new UnsupportedOperationException("Insecure constructor"); 2140 } 2141 2142 /** 2143 * Constructs an ExpandableNotificationRow. Used by layout inflation (with a custom {@code 2144 * AsyncLayoutFactory} in {@link RowInflaterTask}. 2145 * 2146 * @param context context context of the view 2147 * @param attrs attributes used to initialize parent view 2148 * @param user the user the row is associated to 2149 */ 2150 public ExpandableNotificationRow(Context context, AttributeSet attrs, UserHandle user) { 2151 this(context, attrs, userContextForEntry(context, user)); 2152 NotificationBundleUi.unsafeAssertInNewMode(); 2153 } 2154 2155 /** 2156 * Constructs an ExpandableNotificationRow. Used by layout inflation (with a custom {@code 2157 * AsyncLayoutFactory} in {@link RowInflaterTask}. 2158 * 2159 * @param context context context of the view 2160 * @param attrs attributes used to initialize parent view 2161 * @param entry notification that the row will be associated to (determines the user for the 2162 * ImageResolver) 2163 */ 2164 public ExpandableNotificationRow(Context context, AttributeSet attrs, NotificationEntry entry) { 2165 this(context, attrs, userContextForEntry(context, entry)); 2166 NotificationBundleUi.assertInLegacyMode(); 2167 } 2168 2169 private static Context userContextForEntry(Context base, NotificationEntry entry) { 2170 if (base.getUserId() == entry.getSbn().getNormalizedUserId()) { 2171 return base; 2172 } 2173 return base.createContextAsUser( 2174 UserHandle.of(entry.getSbn().getNormalizedUserId()), /* flags= */ 0); 2175 } 2176 2177 private static Context userContextForEntry(Context base, UserHandle user) { 2178 if (base.getUserId() == user.getIdentifier()) { 2179 return base; 2180 } 2181 return base.createContextAsUser(user, /* flags= */ 0); 2182 } 2183 2184 private ExpandableNotificationRow(Context sysUiContext, AttributeSet attrs, 2185 Context userContext) { 2186 super(sysUiContext, attrs); 2187 mImageResolver = new NotificationInlineImageResolver(userContext, 2188 new NotificationInlineImageCache()); 2189 float radius = getResources().getDimension(R.dimen.notification_corner_radius_small); 2190 mSmallRoundness = radius / getMaxRadius(); 2191 mMagneticAnimator = new SpringAnimation( 2192 this, FloatPropertyCompat.createFloatPropertyCompat(TRANSLATE_CONTENT)); 2193 initDimens(); 2194 } 2195 2196 /** 2197 * Initialize row. 2198 */ 2199 public void initialize( 2200 EntryAdapter entryAdapter, 2201 PipelineEntry entry, 2202 RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, 2203 String appName, 2204 @NonNull String notificationKey, 2205 ExpandableNotificationRowLogger logger, 2206 KeyguardBypassController bypassController, 2207 GroupMembershipManager groupMembershipManager, 2208 GroupExpansionManager groupExpansionManager, 2209 HeadsUpManager headsUpManager, 2210 RowContentBindStage rowContentBindStage, 2211 OnExpandClickListener onExpandClickListener, 2212 CoordinateOnClickListener onFeedbackClickListener, 2213 FalsingManager falsingManager, 2214 StatusBarStateController statusBarStateController, 2215 PeopleNotificationIdentifier peopleNotificationIdentifier, 2216 OnUserInteractionCallback onUserInteractionCallback, 2217 NotificationGutsManager gutsManager, 2218 NotificationDismissibilityProvider dismissibilityProvider, 2219 MetricsLogger metricsLogger, 2220 NotificationChildrenContainerLogger childrenContainerLogger, 2221 ColorUpdateLogger colorUpdateLogger, 2222 SmartReplyConstants smartReplyConstants, 2223 SmartReplyController smartReplyController, 2224 IStatusBarService statusBarService, 2225 UiEventLogger uiEventLogger, 2226 NotificationRebindingTracker notificationRebindingTracker) { 2227 2228 if (NotificationBundleUi.isEnabled()) { 2229 mEntryAdapter = entryAdapter; 2230 // TODO (b/395857098): remove when all usages are migrated 2231 mEntry = (NotificationEntry) entry; 2232 } else { 2233 mEntry = (NotificationEntry) entry; 2234 } 2235 mAppName = appName; 2236 mRebindingTracker = notificationRebindingTracker; 2237 if (mMenuRow == null) { 2238 mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier); 2239 } 2240 if (mMenuRow.getMenuView() != null) { 2241 mMenuRow.setAppName(mAppName); 2242 } 2243 mLogger = logger; 2244 mKey = notificationKey; 2245 mLoggingKey = logKey(notificationKey); 2246 mBypassController = bypassController; 2247 mGroupMembershipManager = groupMembershipManager; 2248 mGroupExpansionManager = groupExpansionManager; 2249 mPrivateLayout.setGroupMembershipManager(groupMembershipManager); 2250 mHeadsUpManager = headsUpManager; 2251 mRowContentBindStage = rowContentBindStage; 2252 mOnExpandClickListener = onExpandClickListener; 2253 setOnFeedbackClickListener(onFeedbackClickListener); 2254 mFalsingManager = falsingManager; 2255 mStatusBarStateController = statusBarStateController; 2256 mPeopleNotificationIdentifier = peopleNotificationIdentifier; 2257 for (NotificationContentView l : mLayouts) { 2258 l.initialize( 2259 mPeopleNotificationIdentifier, 2260 rivSubcomponentFactory, 2261 smartReplyConstants, 2262 smartReplyController, 2263 statusBarService, 2264 uiEventLogger 2265 ); 2266 } 2267 mOnUserInteractionCallback = onUserInteractionCallback; 2268 mNotificationGutsManager = gutsManager; 2269 mMetricsLogger = metricsLogger; 2270 mChildrenContainerLogger = childrenContainerLogger; 2271 mColorUpdateLogger = colorUpdateLogger; 2272 mDismissibilityProvider = dismissibilityProvider; 2273 setHapticFeedbackEnabled(!Flags.msdlFeedback()); 2274 } 2275 2276 private void initDimens() { 2277 mMaxSmallHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, 2278 R.dimen.notification_min_height_legacy); 2279 mMaxSmallHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, 2280 R.dimen.notification_min_height_before_p); 2281 mMaxSmallHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext, 2282 R.dimen.notification_min_height_before_s); 2283 if (notificationsRedesignTemplates()) { 2284 mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext, 2285 R.dimen.notification_2025_min_height); 2286 } else { 2287 mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext, 2288 R.dimen.notification_min_height); 2289 } 2290 mMaxSmallHeightWithSummarization = NotificationUtils.getFontScaledHeight(mContext, 2291 com.android.internal.R.dimen.notification_collapsed_height_with_summarization); 2292 mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext, 2293 R.dimen.notification_max_height); 2294 mMaxExpandedHeightForPromotedOngoing = NotificationUtils.getFontScaledHeight(mContext, 2295 R.dimen.notification_max_height_for_promoted_ongoing); 2296 mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, 2297 R.dimen.notification_max_heads_up_height_legacy); 2298 mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, 2299 R.dimen.notification_max_heads_up_height_before_p); 2300 mMaxHeadsUpHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext, 2301 R.dimen.notification_max_heads_up_height_before_s); 2302 mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext, 2303 R.dimen.notification_max_heads_up_height); 2304 2305 Resources res = getResources(); 2306 mEnableNonGroupedNotificationExpand = 2307 res.getBoolean(R.bool.config_enableNonGroupedNotificationExpand); 2308 mShowGroupBackgroundWhenExpanded = 2309 res.getBoolean(R.bool.config_showGroupNotificationBgWhenExpanded); 2310 } 2311 2312 NotificationInlineImageResolver getImageResolver() { 2313 return mImageResolver; 2314 } 2315 2316 public BigPictureIconManager getBigPictureIconManager() { 2317 return mBigPictureIconManager; 2318 } 2319 2320 public void setBigPictureIconManager( 2321 BigPictureIconManager bigPictureIconManager) { 2322 mBigPictureIconManager = bigPictureIconManager; 2323 } 2324 2325 2326 /** 2327 * Resets this view so it can be re-used for an updated notification. 2328 */ 2329 public void reset() { 2330 mShowingPublicInitialized = false; 2331 unDismiss(); 2332 if (mMenuRow == null || !mMenuRow.isMenuVisible()) { 2333 resetTranslation(); 2334 } 2335 onHeightReset(); 2336 requestLayout(); 2337 2338 setTargetPoint(null); 2339 } 2340 2341 /** 2342 * Shows the given feedback icon, or hides the icon if null. 2343 */ 2344 public void setFeedbackIcon(@Nullable FeedbackIcon icon) { 2345 if (mIsSummaryWithChildren) { 2346 mChildrenContainer.setFeedbackIcon(icon); 2347 } 2348 mPrivateLayout.setFeedbackIcon(icon); 2349 mPublicLayout.setFeedbackIcon(icon); 2350 } 2351 2352 /** 2353 * Sets the last time the notification being displayed audibly alerted the user. 2354 */ 2355 public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) { 2356 long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs; 2357 boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS; 2358 2359 applyAudiblyAlertedRecently(alertedRecently); 2360 2361 removeCallbacks(mExpireRecentlyAlertedFlag); 2362 if (alertedRecently) { 2363 long timeUntilNoLongerRecent = RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly; 2364 postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent); 2365 } 2366 } 2367 2368 @VisibleForTesting 2369 @Deprecated 2370 protected void setEntryLegacy(NotificationEntry entry) { 2371 NotificationBundleUi.assertInLegacyMode(); 2372 mEntry = entry; 2373 } 2374 2375 @VisibleForTesting 2376 protected void setEntryAdapter(EntryAdapter entry) { 2377 mEntryAdapter = entry; 2378 } 2379 2380 private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false); 2381 2382 private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) { 2383 if (mIsSummaryWithChildren) { 2384 mChildrenContainer.setRecentlyAudiblyAlerted(audiblyAlertedRecently); 2385 } 2386 mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); 2387 mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); 2388 } 2389 2390 public View.OnClickListener getFeedbackOnClickListener() { 2391 return mOnFeedbackClickListener; 2392 } 2393 2394 void setOnFeedbackClickListener(CoordinateOnClickListener l) { 2395 mOnFeedbackClickListener = v -> { 2396 createMenu(); 2397 NotificationMenuRowPlugin provider = getProvider(); 2398 if (provider == null) { 2399 return; 2400 } 2401 MenuItem menuItem = provider.getFeedbackMenuItem(mContext); 2402 if (menuItem != null) { 2403 l.onClick(this, v.getWidth() / 2, v.getHeight() / 2, menuItem); 2404 } 2405 }; 2406 } 2407 2408 /** 2409 * Retrieves an OnClickListener for the close button of a notification, which when invoked, 2410 * dismisses the notificationc represented by the given ExpandableNotificationRow. 2411 * 2412 * @param row The ExpandableNotificationRow representing the notification to be dismissed. 2413 * @return An OnClickListener instance that dismisses the notification(s) when invoked. 2414 */ 2415 public View.OnClickListener getCloseButtonOnClickListener(ExpandableNotificationRow row) { 2416 return v -> { 2417 if (row != null) { 2418 row.performDismiss(false); 2419 } 2420 }; 2421 } 2422 2423 @Override 2424 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 2425 Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure")); 2426 if (DEBUG_ONMEASURE) { 2427 Log.d(TAG, "onMeasure(" 2428 + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", " 2429 + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")"); 2430 } 2431 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 2432 2433 if (shouldSimulateSlowMeasure()) { 2434 simulateExtraMeasureDelay(); 2435 } 2436 Trace.endSection(); 2437 } 2438 2439 private void simulateExtraMeasureDelay() { 2440 // Add extra delay in a notification row instead of NotificationStackScrollLayout 2441 // to make sure that when the measure cache is used we won't add this delay 2442 try { 2443 Trace.beginSection("ExtraDebugMeasureDelay"); 2444 Thread.sleep(SLOW_MEASURE_SIMULATE_DELAY_MS); 2445 } catch (InterruptedException e) { 2446 throw new RuntimeException(e); 2447 } finally { 2448 Trace.endSection(); 2449 } 2450 } 2451 2452 /** 2453 * Generates and appends "(MessagingStyle)" type tag to passed string for tracing. 2454 */ 2455 @NonNull 2456 private String appendTraceStyleTag(@NonNull String traceTag) { 2457 if (!Trace.isEnabled()) { 2458 return traceTag; 2459 } 2460 2461 if (NotificationBundleUi.isEnabled()) { 2462 return traceTag + "(" + getEntryAdapter().getStyle() + ")"; 2463 } else { 2464 return traceTag + "(" + getEntryLegacy().getNotificationStyle() + ")"; 2465 } 2466 } 2467 2468 @Override 2469 protected void onFinishInflate() { 2470 super.onFinishInflate(); 2471 mPublicLayout = findViewById(R.id.expandedPublic); 2472 mPrivateLayout = findViewById(R.id.expanded); 2473 mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; 2474 2475 for (NotificationContentView l : mLayouts) { 2476 l.setExpandClickListener(mExpandClickListener); 2477 l.setContainingNotification(this); 2478 } 2479 mGutsStub = findViewById(R.id.notification_guts_stub); 2480 mGutsStub.setOnInflateListener((stub, inflated) -> { 2481 mGuts = (NotificationGuts) inflated; 2482 mGuts.setClipTopAmount(getClipTopAmount()); 2483 mGuts.setActualHeight(getActualHeight()); 2484 mGutsStub = null; 2485 }); 2486 mChildrenContainerStub = findViewById(R.id.child_container_stub); 2487 mChildrenContainerStub.setOnInflateListener((stub, inflated) -> { 2488 mChildrenContainer = (NotificationChildrenContainer) inflated; 2489 mChildrenContainer.setIsMinimized(mIsMinimized); 2490 mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this); 2491 mChildrenContainer.onNotificationUpdated(); 2492 mChildrenContainer.setLogger(mChildrenContainerLogger); 2493 2494 mTranslateableViews.add(mChildrenContainer); 2495 }); 2496 2497 // Add the views that we translate to reveal the menu 2498 mTranslateableViews = new ArrayList<>(); 2499 for (int i = 0; i < getChildCount(); i++) { 2500 mTranslateableViews.add(getChildAt(i)); 2501 } 2502 // Remove views that don't translate 2503 mTranslateableViews.remove(mChildrenContainerStub); 2504 mTranslateableViews.remove(mGutsStub); 2505 // We don't handle focus highlight in this view, it's done in background drawable instead 2506 setDefaultFocusHighlightEnabled(false); 2507 2508 if (NotificationAddXOnHoverToDismiss.isEnabled()) { 2509 addDismissButtonTargetStateListener(findViewById(R.id.backgroundNormal)); 2510 } 2511 } 2512 2513 /** 2514 * Called once when starting drag motion after opening notification guts, 2515 * in case of notification that has {@link android.app.Notification#contentIntent} 2516 * and it is to start an activity. 2517 */ doDragCallback(float x, float y)2518 public void doDragCallback(float x, float y) { 2519 if (mDragController != null) { 2520 setTargetPoint(new Point((int) x, (int) y)); 2521 mDragController.startDragAndDrop(this); 2522 } 2523 } 2524 setOnDragSuccessListener(OnDragSuccessListener listener)2525 public void setOnDragSuccessListener(OnDragSuccessListener listener) { 2526 mOnDragSuccessListener = listener; 2527 } 2528 2529 /** 2530 * Called when a notification is dropped on proper target window. 2531 */ dragAndDropSuccess()2532 public void dragAndDropSuccess() { 2533 if (mOnDragSuccessListener != null) { 2534 if (NotificationBundleUi.isEnabled()) { 2535 mOnDragSuccessListener.onDragSuccess(getEntryAdapter()); 2536 } else { 2537 mOnDragSuccessListener.onDragSuccess(getEntryLegacy()); 2538 } 2539 } 2540 } 2541 doLongClickCallback()2542 private void doLongClickCallback() { 2543 doLongClickCallback(getWidth() / 2, getHeight() / 2); 2544 } 2545 doLongClickCallback(int x, int y)2546 public void doLongClickCallback(int x, int y) { 2547 createMenu(); 2548 NotificationMenuRowPlugin provider = getProvider(); 2549 MenuItem menuItem = null; 2550 if (provider != null) { 2551 menuItem = provider.getLongpressMenuItem(mContext); 2552 } 2553 doLongClickCallback(x, y, menuItem); 2554 } 2555 2556 /** 2557 * Perform a smart action which triggers a longpress (expose guts). 2558 * Based on the semanticAction passed, may update the state of the guts view. 2559 * 2560 * @param semanticAction associated with this smart action click 2561 */ doSmartActionClick(int x, int y, int semanticAction)2562 public void doSmartActionClick(int x, int y, int semanticAction) { 2563 createMenu(); 2564 NotificationMenuRowPlugin provider = getProvider(); 2565 MenuItem menuItem = null; 2566 if (provider != null) { 2567 menuItem = provider.getLongpressMenuItem(mContext); 2568 } 2569 if (SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == semanticAction 2570 && menuItem.getGutsView() instanceof NotificationConversationInfo) { 2571 NotificationConversationInfo info = 2572 (NotificationConversationInfo) menuItem.getGutsView(); 2573 info.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE); 2574 } 2575 doLongClickCallback(x, y, menuItem); 2576 } 2577 doLongClickCallback(int x, int y, MenuItem menuItem)2578 private void doLongClickCallback(int x, int y, MenuItem menuItem) { 2579 if (mLongPressListener != null && menuItem != null) { 2580 mLongPressListener.onLongPress(this, x, y, menuItem); 2581 } 2582 } 2583 2584 @Override onKeyDown(int keyCode, KeyEvent event)2585 public boolean onKeyDown(int keyCode, KeyEvent event) { 2586 if (KeyEvent.isConfirmKey(keyCode)) { 2587 event.startTracking(); 2588 return true; 2589 } 2590 return super.onKeyDown(keyCode, event); 2591 } 2592 2593 @Override onKeyUp(int keyCode, KeyEvent event)2594 public boolean onKeyUp(int keyCode, KeyEvent event) { 2595 if (KeyEvent.isConfirmKey(keyCode)) { 2596 if (!event.isCanceled()) { 2597 performClick(); 2598 } 2599 return true; 2600 } 2601 return super.onKeyUp(keyCode, event); 2602 } 2603 2604 @Override onKeyLongPress(int keyCode, KeyEvent event)2605 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 2606 if (KeyEvent.isConfirmKey(keyCode)) { 2607 doLongClickCallback(); 2608 return true; 2609 } 2610 return false; 2611 } 2612 resetTranslation()2613 public void resetTranslation() { 2614 if (mTranslateAnim != null) { 2615 mTranslateAnim.cancel(); 2616 } 2617 2618 if (mDismissUsingRowTranslationX) { 2619 setTranslationX(0); 2620 } else if (mTranslateableViews != null) { 2621 for (int i = 0; i < mTranslateableViews.size(); i++) { 2622 mTranslateableViews.get(i).setTranslationX(0); 2623 } 2624 invalidateOutline(); 2625 getShelfIcon().setScrollX(0); 2626 } 2627 2628 if (mMenuRow != null) { 2629 mMenuRow.resetMenu(); 2630 } 2631 } 2632 onGutsOpened()2633 void onGutsOpened() { 2634 resetTranslation(); 2635 updateContentAccessibilityImportanceForGuts(false /* isEnabled */); 2636 } 2637 onGutsClosed()2638 void onGutsClosed() { 2639 updateContentAccessibilityImportanceForGuts(true /* isEnabled */); 2640 mIsSnoozed = false; 2641 } 2642 2643 /** 2644 * Updates whether all the non-guts content inside this row is important for accessibility. 2645 * 2646 * @param isEnabled whether the content views should be enabled for accessibility 2647 */ updateContentAccessibilityImportanceForGuts(boolean isEnabled)2648 private void updateContentAccessibilityImportanceForGuts(boolean isEnabled) { 2649 updateAccessibilityImportance(isEnabled); 2650 2651 if (mChildrenContainer != null) { 2652 updateChildAccessibilityImportance(mChildrenContainer, isEnabled); 2653 } 2654 if (mLayouts != null) { 2655 for (View view : mLayouts) { 2656 updateChildAccessibilityImportance(view, isEnabled); 2657 } 2658 } 2659 2660 if (isEnabled) { 2661 this.requestAccessibilityFocus(); 2662 } 2663 } 2664 2665 /** 2666 * Updates whether this view is important for accessibility based on {@code isEnabled}. 2667 */ updateAccessibilityImportance(boolean isEnabled)2668 private void updateAccessibilityImportance(boolean isEnabled) { 2669 setImportantForAccessibility(isEnabled 2670 ? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO 2671 : View.IMPORTANT_FOR_ACCESSIBILITY_NO); 2672 } 2673 2674 /** 2675 * Updates whether the given childView is important for accessibility based on 2676 * {@code isEnabled}. 2677 */ updateChildAccessibilityImportance(View childView, boolean isEnabled)2678 private void updateChildAccessibilityImportance(View childView, boolean isEnabled) { 2679 childView.setImportantForAccessibility(isEnabled 2680 ? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO 2681 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 2682 } 2683 2684 /** 2685 * Reset the translation with an animation. 2686 */ animateResetTranslation()2687 public void animateResetTranslation() { 2688 if (mTranslateAnim != null) { 2689 mTranslateAnim.cancel(); 2690 } 2691 mTranslateAnim = getTranslateViewAnimator(0, null /* updateListener */); 2692 if (mTranslateAnim != null) { 2693 mTranslateAnim.start(); 2694 } 2695 } 2696 2697 /** 2698 * Whether to allow dismissal with the whole-row translation animation. 2699 * 2700 * If true, either animation is permissible. 2701 * If false, usingRTX behavior is forbidden, only clipping animation should be used. 2702 * 2703 * Usually either is OK, except for promoted notifications, where we always need to 2704 * dismiss with content clipping/partial translation animation instead, so that we 2705 * can show the demotion options. 2706 * @return 2707 */ allowDismissUsingRowTranslationX()2708 private boolean allowDismissUsingRowTranslationX() { 2709 if (Flags.permissionHelperInlineUiRichOngoing()) { 2710 return !isPromotedOngoing(); 2711 } else { 2712 // Don't change behavior unless the flag is on. 2713 return true; 2714 } 2715 } 2716 2717 /** 2718 * Set the dismiss behavior of the view. 2719 * 2720 * @param usingRowTranslationX {@code true} if the view should translate using regular 2721 * translationX, otherwise the contents will be 2722 * translated. 2723 * @param forceUpdateChildren {@code true} to force initialization, {@code false} if lazy 2724 * behavior is OK. 2725 */ 2726 @Override setDismissUsingRowTranslationX(boolean usingRowTranslationX, boolean forceUpdateChildren)2727 public void setDismissUsingRowTranslationX(boolean usingRowTranslationX, 2728 boolean forceUpdateChildren) { 2729 // Before updating dismiss behavior, make sure this is an allowable configuration for this 2730 // notification. 2731 usingRowTranslationX = usingRowTranslationX && allowDismissUsingRowTranslationX(); 2732 2733 if (forceUpdateChildren || (usingRowTranslationX != mDismissUsingRowTranslationX)) { 2734 // In case we were already transitioning, let's switch over! 2735 float previousTranslation = getTranslation(); 2736 if (previousTranslation != 0) { 2737 setTranslation(0); 2738 } 2739 super.setDismissUsingRowTranslationX(usingRowTranslationX, forceUpdateChildren); 2740 if (previousTranslation != 0) { 2741 setTranslation(previousTranslation); 2742 } 2743 2744 if (mChildrenContainer != null) { 2745 List<ExpandableNotificationRow> notificationChildren = 2746 mChildrenContainer.getAttachedChildren(); 2747 for (int i = 0; i < notificationChildren.size(); i++) { 2748 ExpandableNotificationRow child = notificationChildren.get(i); 2749 child.setDismissUsingRowTranslationX(usingRowTranslationX, forceUpdateChildren); 2750 } 2751 } 2752 } 2753 } 2754 2755 @Override setTranslation(float translationX)2756 public void setTranslation(float translationX) { 2757 invalidate(); 2758 if (mDismissUsingRowTranslationX) { 2759 setTranslationX(translationX); 2760 } else if (mTranslateableViews != null) { 2761 // Translate the group of views 2762 for (int i = 0; i < mTranslateableViews.size(); i++) { 2763 if (mTranslateableViews.get(i) != null) { 2764 mTranslateableViews.get(i).setTranslationX(translationX); 2765 } 2766 } 2767 invalidateOutline(); 2768 2769 // In order to keep the shelf in sync with this swiping, we're simply translating 2770 // it's icon by the same amount. The translation is already being used for the normal 2771 // positioning, so we can use the scrollX instead. 2772 getShelfIcon().setScrollX((int) -translationX); 2773 } 2774 2775 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 2776 mMenuRow.onParentTranslationUpdate(translationX); 2777 } 2778 } 2779 2780 @Override getTranslation()2781 public float getTranslation() { 2782 if (mDismissUsingRowTranslationX) { 2783 return getTranslationX(); 2784 } 2785 2786 if (mTranslateableViews != null && !mTranslateableViews.isEmpty()) { 2787 // All of the views in the list should have same translation, just use first one. 2788 return mTranslateableViews.get(0).getTranslationX(); 2789 } 2790 2791 return 0; 2792 } 2793 getTranslateViewAnimator(final float leftTarget, AnimatorUpdateListener listener)2794 public Animator getTranslateViewAnimator(final float leftTarget, 2795 AnimatorUpdateListener listener) { 2796 if (mTranslateAnim != null) { 2797 mTranslateAnim.cancel(); 2798 } 2799 2800 final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT, 2801 leftTarget); 2802 if (listener != null) { 2803 translateAnim.addUpdateListener(listener); 2804 } 2805 translateAnim.addListener(new AnimatorListenerAdapter() { 2806 boolean cancelled = false; 2807 2808 @Override 2809 public void onAnimationCancel(Animator anim) { 2810 cancelled = true; 2811 } 2812 2813 @Override 2814 public void onAnimationEnd(Animator anim) { 2815 if (!cancelled && leftTarget == 0) { 2816 if (mMenuRow != null) { 2817 mMenuRow.resetMenu(); 2818 } 2819 } 2820 mTranslateAnim = null; 2821 } 2822 }); 2823 mTranslateAnim = translateAnim; 2824 return translateAnim; 2825 } 2826 2827 /** Cancels the ongoing translate animation if there is any. */ cancelTranslateAnimation()2828 public void cancelTranslateAnimation() { 2829 if (mTranslateAnim != null) { 2830 mTranslateAnim.cancel(); 2831 } 2832 } 2833 ensureGutsInflated()2834 void ensureGutsInflated() { 2835 if (mGuts == null) { 2836 mGutsStub.inflate(); 2837 } 2838 } 2839 updateChildrenVisibility()2840 private void updateChildrenVisibility() { 2841 boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null 2842 && mGuts.isExposed(); 2843 mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren 2844 && !hideContentWhileLaunching ? VISIBLE : INVISIBLE); 2845 if (mChildrenContainer != null) { 2846 mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren 2847 && !hideContentWhileLaunching ? VISIBLE 2848 : INVISIBLE); 2849 } 2850 // The limits might have changed if the view suddenly became a group or vice versa 2851 updateLimits(); 2852 } 2853 2854 @Override onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event)2855 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 2856 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 2857 // Add a record for the entire layout since its content is somehow small. 2858 // The event comes from a leaf view that is interacted with. 2859 AccessibilityEvent record = AccessibilityEvent.obtain(); 2860 onInitializeAccessibilityEvent(record); 2861 dispatchPopulateAccessibilityEvent(record); 2862 event.appendRecord(record); 2863 return true; 2864 } 2865 return false; 2866 } 2867 applyLaunchAnimationParams(LaunchAnimationParameters params)2868 public void applyLaunchAnimationParams(LaunchAnimationParameters params) { 2869 if (params == null) { 2870 // `null` params indicates the animation is over, which means we can't access 2871 // params.getParentStartClipTopAmount() which has the value we want to restore. 2872 // Fortunately, only NotificationShelf actually uses these values for anything other 2873 // than this launch animation, so we can restore the value to 0 and it's right for now. 2874 if (mNotificationParent != null) { 2875 mNotificationParent.setClipTopAmount(0); 2876 } 2877 setTranslationX(0); 2878 return; 2879 } 2880 2881 if (!params.getVisible()) { 2882 if (getVisibility() == View.VISIBLE) { 2883 setVisibility(View.INVISIBLE); 2884 } 2885 return; 2886 } 2887 2888 float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( 2889 params.getProgress(0, 50)); 2890 float translationZ = MathUtils.lerp(params.getStartTranslationZ(), 2891 mNotificationLaunchHeight, 2892 zProgress); 2893 setTranslationZ(translationZ); 2894 float extraWidthForClipping = params.getWidth() - getWidth(); 2895 setExtraWidthForClipping(extraWidthForClipping); 2896 2897 int top; 2898 if (params.getStartRoundedTopClipping() > 0) { 2899 // If we were clipping initially, let's interpolate from the start position to the 2900 // top. Otherwise, we just take the top directly. 2901 float expandProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( 2902 params.getProgress(0, 2903 NotificationTransitionAnimatorController 2904 .ANIMATION_DURATION_TOP_ROUNDING)); 2905 int startTop = params.getStartNotificationTop(); 2906 top = (int) Math.min(MathUtils.lerp(startTop, params.getTop(), expandProgress), 2907 startTop); 2908 } else { 2909 top = params.getTop(); 2910 } 2911 int actualHeight = params.getBottom() - top; 2912 setFinalActualHeight(actualHeight); 2913 2914 int notificationStackTop = params.getNotificationParentTop(); 2915 top -= notificationStackTop; 2916 int startClipTopAmount = params.getStartClipTopAmount(); 2917 int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, params.getProgress()); 2918 if (mNotificationParent != null) { 2919 float parentTranslationY = mNotificationParent.getTranslationY(); 2920 top -= (int) parentTranslationY; 2921 mNotificationParent.setTranslationZ(translationZ); 2922 2923 // When the expanding notification is below its parent, the parent must be clipped 2924 // exactly how it was clipped before the animation. When the expanding notification is 2925 // on or above its parent (top <= 0), then the parent must be clipped exactly 'top' 2926 // pixels to show the expanding notification, while still taking the decreasing 2927 // notification clipTopAmount into consideration, so 'top + clipTopAmount'. 2928 int parentStartClipTopAmount = params.getParentStartClipTopAmount(); 2929 int parentClipTopAmount = Math.min(parentStartClipTopAmount, top + clipTopAmount); 2930 mNotificationParent.setClipTopAmount(parentClipTopAmount); 2931 2932 mNotificationParent.setExtraWidthForClipping(extraWidthForClipping); 2933 float clipBottom = Math.max(params.getBottom() - notificationStackTop, 2934 parentTranslationY + mNotificationParent.getActualHeight() 2935 - mNotificationParent.getClipBottomAmount()); 2936 float clipTop = Math.min(params.getTop() - notificationStackTop, parentTranslationY); 2937 int minimumHeightForClipping = (int) (clipBottom - clipTop); 2938 mNotificationParent.setMinimumHeightForClipping(minimumHeightForClipping); 2939 } else if (startClipTopAmount != 0) { 2940 setClipTopAmount(clipTopAmount); 2941 } 2942 setTranslationY(top); 2943 2944 float absoluteCenterX = getLocationOnScreen()[0] + getWidth() / 2f - getTranslationX(); 2945 setTranslationX(params.getCenterX() - absoluteCenterX); 2946 2947 invalidateOutline(); 2948 2949 mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight); 2950 2951 if (Flags.notificationsLaunchRadius()) { 2952 mBackgroundNormal.setRadius(params.getTopCornerRadius(), 2953 params.getBottomCornerRadius()); 2954 } 2955 } 2956 setExpandAnimationRunning(boolean expandAnimationRunning)2957 public void setExpandAnimationRunning(boolean expandAnimationRunning) { 2958 if (expandAnimationRunning) { 2959 setAboveShelf(true); 2960 mExpandAnimationRunning = true; 2961 getViewState().cancelAnimations(this); 2962 mNotificationLaunchHeight = AmbientState.getNotificationLaunchHeight(getContext()); 2963 } else { 2964 mExpandAnimationRunning = false; 2965 setAboveShelf(isAboveShelf()); 2966 setVisibility(View.VISIBLE); 2967 if (mGuts != null) { 2968 mGuts.setAlpha(1.0f); 2969 } 2970 resetAllContentAlphas(); 2971 setExtraWidthForClipping(0.0f); 2972 if (mNotificationParent != null) { 2973 mNotificationParent.setExtraWidthForClipping(0.0f); 2974 mNotificationParent.setMinimumHeightForClipping(0); 2975 } 2976 } 2977 if (mNotificationParent != null) { 2978 mNotificationParent.setChildIsExpanding(mExpandAnimationRunning); 2979 } 2980 updateChildrenVisibility(); 2981 updateClipping(); 2982 mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning); 2983 } 2984 setChildIsExpanding(boolean isExpanding)2985 private void setChildIsExpanding(boolean isExpanding) { 2986 mChildIsExpanding = isExpanding; 2987 updateClipping(); 2988 invalidate(); 2989 } 2990 2991 @Override hasExpandingChild()2992 public boolean hasExpandingChild() { 2993 return mChildIsExpanding; 2994 } 2995 2996 @Override getShelfIcon()2997 public @NonNull StatusBarIconView getShelfIcon() { 2998 if (NotificationBundleUi.isEnabled()) { 2999 return getEntryAdapter().getIcons().getShelfIcon(); 3000 } else { 3001 return getEntryLegacy().getIcons().getShelfIcon(); 3002 } 3003 } 3004 3005 @Override shouldClipToActualHeight()3006 protected boolean shouldClipToActualHeight() { 3007 return super.shouldClipToActualHeight() && !mExpandAnimationRunning; 3008 } 3009 3010 @Override isExpandAnimationRunning()3011 public boolean isExpandAnimationRunning() { 3012 return mExpandAnimationRunning; 3013 } 3014 3015 /** 3016 * Tap sounds should not be played when we're unlocking. 3017 * Doing so would cause audio collision and the system would feel unpolished. 3018 */ 3019 @Override isSoundEffectsEnabled()3020 public boolean isSoundEffectsEnabled() { 3021 final boolean mute = mStatusBarStateController != null 3022 && mStatusBarStateController.isDozing() 3023 && mSecureStateProvider != null && 3024 !mSecureStateProvider.getAsBoolean(); 3025 return !mute && super.isSoundEffectsEnabled(); 3026 } 3027 isExpandable()3028 public boolean isExpandable() { 3029 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3030 return !mChildrenExpanded; 3031 } 3032 if (isPromotedOngoing()) { 3033 return false; 3034 } 3035 return mEnableNonGroupedNotificationExpand && mExpandable; 3036 } 3037 setExpandable(boolean expandable)3038 public void setExpandable(boolean expandable) { 3039 mExpandable = expandable; 3040 mPrivateLayout.updateExpandButtons(isExpandable()); 3041 } 3042 3043 3044 /** 3045 * Sets whether the status bar is showing a chip corresponding to this notification. 3046 * 3047 * Only set when this notification's heads-up status changes since that's the only time it's 3048 * relevant. 3049 */ setHasStatusBarChipDuringHeadsUpAnimation(boolean hasStatusBarChip)3050 public void setHasStatusBarChipDuringHeadsUpAnimation(boolean hasStatusBarChip) { 3051 if (StatusBarNotifChips.isUnexpectedlyInLegacyMode()) { 3052 return; 3053 } 3054 mHasStatusBarChipDuringHeadsUpAnimation = hasStatusBarChip; 3055 } 3056 3057 /** 3058 * Returns true if the status bar is showing a chip corresponding to this notification during a 3059 * heads-up appear or disappear animation. 3060 * 3061 * Note that this value is only set when this notification's heads-up status changes since 3062 * that's the only time it's relevant. 3063 */ hasStatusBarChipDuringHeadsUpAnimation()3064 public boolean hasStatusBarChipDuringHeadsUpAnimation() { 3065 return StatusBarNotifChips.isEnabled() && mHasStatusBarChipDuringHeadsUpAnimation; 3066 } 3067 3068 @Override setClipToActualHeight(boolean clipToActualHeight)3069 public void setClipToActualHeight(boolean clipToActualHeight) { 3070 super.setClipToActualHeight(clipToActualHeight || isUserLocked()); 3071 getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked()); 3072 } 3073 3074 /** 3075 * @return whether the user has changed the expansion state 3076 */ hasUserChangedExpansion()3077 public boolean hasUserChangedExpansion() { 3078 return mHasUserChangedExpansion; 3079 } 3080 isUserExpanded()3081 public boolean isUserExpanded() { 3082 return mUserExpanded; 3083 } 3084 3085 /** 3086 * Set this notification to be expanded by the user 3087 * 3088 * @param userExpanded whether the user wants this notification to be expanded 3089 */ setUserExpanded(boolean userExpanded)3090 public void setUserExpanded(boolean userExpanded) { 3091 setUserExpanded(userExpanded, false /* allowChildExpansion */); 3092 } 3093 3094 /** 3095 * Set this notification to be expanded by the user 3096 * 3097 * @param userExpanded whether the user wants this notification to be expanded 3098 * @param allowChildExpansion whether a call to this method allows expanding children 3099 */ setUserExpanded(boolean userExpanded, boolean allowChildExpansion)3100 public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { 3101 if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion 3102 && !mChildrenContainer.showingAsLowPriority()) { 3103 final boolean wasExpanded = isGroupExpanded(); 3104 if (NotificationBundleUi.isEnabled()) { 3105 if (mEntryAdapter.isGroupRoot()) { 3106 mGroupExpansionManager.setGroupExpanded(mEntryAdapter, userExpanded); 3107 } 3108 } else { 3109 mGroupExpansionManager.setGroupExpanded(getEntryLegacy(), userExpanded); 3110 } 3111 onExpansionChanged(true /* userAction */, wasExpanded); 3112 return; 3113 } 3114 if (userExpanded && !mExpandable) return; 3115 final boolean wasExpanded = isExpanded(); 3116 mHasUserChangedExpansion = true; 3117 mUserExpanded = userExpanded; 3118 onExpansionChanged(true /* userAction */, wasExpanded); 3119 if (!wasExpanded && isExpanded() 3120 && getActualHeight() != getIntrinsicHeight()) { 3121 notifyHeightChanged(/* needsAnimation= */ true); 3122 } 3123 } 3124 resetUserExpansion()3125 public void resetUserExpansion() { 3126 boolean wasExpanded = isExpanded(); 3127 mHasUserChangedExpansion = false; 3128 mUserExpanded = false; 3129 if (wasExpanded != isExpanded()) { 3130 if (mIsSummaryWithChildren) { 3131 mChildrenContainer.onExpansionChanged(); 3132 } 3133 notifyHeightChanged(/* needsAnimation= */ false); 3134 } 3135 updateShelfIconColor(); 3136 } 3137 isUserLocked()3138 public boolean isUserLocked() { 3139 return mUserLocked; 3140 } 3141 setUserLocked(boolean userLocked)3142 public void setUserLocked(boolean userLocked) { 3143 if (isPromotedOngoing()) return; 3144 3145 mUserLocked = userLocked; 3146 mPrivateLayout.setUserExpanding(userLocked); 3147 if (android.app.Flags.expandingPublicView()) { 3148 mPublicLayout.setUserExpanding(userLocked); 3149 } 3150 // This is intentionally not guarded with mIsSummaryWithChildren since we might have had 3151 // children but not anymore. 3152 if (mChildrenContainer != null) { 3153 mChildrenContainer.setUserLocked(userLocked); 3154 if (mIsSummaryWithChildren && (userLocked || !isGroupExpanded())) { 3155 updateBackgroundForGroupState(); 3156 } 3157 } 3158 } 3159 3160 /** 3161 * @return has the system set this notification to be expanded 3162 */ isSystemExpanded()3163 public boolean isSystemExpanded() { 3164 return mIsSystemExpanded; 3165 } 3166 3167 /** 3168 * Set this notification to be expanded by the system. 3169 * 3170 * @param expand whether the system wants this notification to be expanded. 3171 */ setSystemExpanded(boolean expand)3172 public void setSystemExpanded(boolean expand) { 3173 if (expand != mIsSystemExpanded) { 3174 final boolean wasExpanded = isExpanded(); 3175 mIsSystemExpanded = expand; 3176 notifyHeightChanged(/* needsAnimation= */ false); 3177 onExpansionChanged(false /* userAction */, wasExpanded); 3178 if (mIsSummaryWithChildren) { 3179 mChildrenContainer.updateGroupOverflow(); 3180 resetChildSystemExpandedStates(); 3181 } 3182 } 3183 } 3184 3185 /** @see #isOnKeyguard() */ setOnKeyguard(boolean onKeyguard)3186 public void setOnKeyguard(boolean onKeyguard) { 3187 if (onKeyguard != mOnKeyguard) { 3188 boolean wasAboveShelf = isAboveShelf(); 3189 final boolean wasExpanded = isExpanded(); 3190 mOnKeyguard = onKeyguard; 3191 onExpansionChanged(false /* userAction */, wasExpanded); 3192 if (wasExpanded != isExpanded()) { 3193 if (mIsSummaryWithChildren) { 3194 mChildrenContainer.updateGroupOverflow(); 3195 } 3196 notifyHeightChanged(/* needsAnimation= */ false); 3197 } 3198 if (isAboveShelf() != wasAboveShelf) { 3199 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 3200 } 3201 if (SceneContainerFlag.isEnabled()) { 3202 if (mIsSummaryWithChildren) { 3203 mChildrenContainer.setOnKeyguard(onKeyguard); 3204 } 3205 } 3206 if (notificationRowTransparency()) { 3207 updateBackgroundTint(); 3208 } 3209 } 3210 } 3211 3212 @Override getHeightWithoutLockscreenConstraints()3213 public int getHeightWithoutLockscreenConstraints() { 3214 mIgnoreLockscreenConstraints = true; 3215 final int height = getIntrinsicHeight(); 3216 mIgnoreLockscreenConstraints = false; 3217 return height; 3218 } 3219 3220 @Override getIntrinsicHeight()3221 public int getIntrinsicHeight() { 3222 if (isUserLocked()) { 3223 return getActualHeight(); 3224 } else if (mGuts != null && mGuts.isExposed()) { 3225 return mGuts.getIntrinsicHeight(); 3226 } else if ((isChildInGroup() && !isGroupExpanded())) { 3227 return mPrivateLayout.getMinHeight(); 3228 } else if (mSensitive && mHideSensitiveForIntrinsicHeight) { 3229 return getMinHeight(); 3230 } else if (mIsSummaryWithChildren) { 3231 return mChildrenContainer.getIntrinsicHeight(); 3232 } else if (canShowHeadsUp() && isHeadsUpState()) { 3233 if (isPinned() || mHeadsupDisappearRunning) { 3234 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); 3235 } else if (isExpanded()) { 3236 return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); 3237 } else { 3238 return Math.max(getCollapsedHeight(), getHeadsUpHeight()); 3239 } 3240 } else if (isExpanded()) { 3241 return getMaxExpandHeight(); 3242 } else { 3243 return getCollapsedHeight(); 3244 } 3245 } 3246 3247 /** 3248 * @return {@code true} if the notification can show it's heads up layout. This is mostly true 3249 * except for legacy use cases. 3250 */ canShowHeadsUp()3251 public boolean canShowHeadsUp() { 3252 boolean canEntryHun = NotificationBundleUi.isEnabled() 3253 ? mEntryAdapter.canPeek() 3254 : getEntryLegacy().isStickyAndNotDemoted(); 3255 if (mOnKeyguard && !isDozing() && !isBypassEnabled() && 3256 (!canEntryHun 3257 || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) { 3258 return false; 3259 } 3260 return true; 3261 } 3262 isBypassEnabled()3263 private boolean isBypassEnabled() { 3264 return mBypassController == null || mBypassController.getBypassEnabled(); 3265 } 3266 isDozing()3267 private boolean isDozing() { 3268 return mStatusBarStateController != null && mStatusBarStateController.isDozing(); 3269 } 3270 3271 @Override isGroupExpanded()3272 public boolean isGroupExpanded() { 3273 if (NotificationBundleUi.isEnabled()) { 3274 return mGroupExpansionManager.isGroupExpanded(mEntryAdapter); 3275 } 3276 return mGroupExpansionManager.isGroupExpanded(getEntryLegacy()); 3277 } 3278 isGroupRoot()3279 private boolean isGroupRoot() { 3280 return NotificationBundleUi.isEnabled() 3281 ? mGroupMembershipManager.isGroupRoot(mEntryAdapter) 3282 : mGroupMembershipManager.isGroupSummary(getEntryLegacy()); 3283 } 3284 onAttachedChildrenCountChanged()3285 private void onAttachedChildrenCountChanged() { 3286 final boolean wasSummary = mIsSummaryWithChildren; 3287 mIsSummaryWithChildren = mChildrenContainer != null 3288 && mChildrenContainer.getNotificationChildCount() > 0; 3289 if (mIsSummaryWithChildren) { 3290 Trace.beginSection("ExpNotRow#onChildCountChanged (summary)"); 3291 if (!AsyncGroupHeaderViewInflation.isEnabled()) { 3292 NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper(); 3293 if (wrapper == null || wrapper.getNotificationHeader() == null) { 3294 mChildrenContainer.recreateNotificationHeader(mExpandClickListener, 3295 isConversation()); 3296 } 3297 } 3298 } 3299 if (!mIsSummaryWithChildren && wasSummary) { 3300 // Reset the 'when' once the row stops being a summary 3301 if (NotificationBundleUi.isEnabled()) { 3302 mPublicLayout.setNotificationWhen(mEntryAdapter.getWhen()); 3303 } else { 3304 mPublicLayout.setNotificationWhen( 3305 getEntryLegacy().getSbn().getNotification().getWhen()); 3306 } 3307 } 3308 getShowingLayout().updateBackgroundColor(false /* animate */); 3309 mPrivateLayout.updateExpandButtons(isExpandable()); 3310 updateChildrenAppearance(); 3311 updateChildrenVisibility(); 3312 applyChildrenRoundness(); 3313 if (mIsSummaryWithChildren) { 3314 Trace.endSection(); 3315 } 3316 } 3317 3318 /** 3319 * Triggers expand click listener to expand the notification. 3320 */ expandNotification()3321 public void expandNotification() { 3322 mExpandClickListener.onClick(this); 3323 } 3324 3325 /** 3326 * If this is a group, update the appearance of the children. 3327 */ updateChildrenAppearance()3328 public void updateChildrenAppearance() { 3329 if (mIsSummaryWithChildren) { 3330 mChildrenContainer.updateChildrenAppearance(); 3331 } 3332 } 3333 isPromotedOngoing()3334 public boolean isPromotedOngoing() { 3335 if (!PromotedNotificationUi.isEnabled()) { 3336 return false; 3337 } 3338 3339 if (NotificationBundleUi.isEnabled()) { 3340 final EntryAdapter entryAdapter = mEntryAdapter; 3341 if (entryAdapter == null) { 3342 return false; 3343 } 3344 return entryAdapter.isPromotedOngoing(); 3345 } else { 3346 final NotificationEntry entry = mEntry; 3347 if (entry == null) { 3348 return false; 3349 } 3350 return entry.isPromotedOngoing(); 3351 } 3352 } 3353 isPromotedNotificationExpanded(boolean allowOnKeyguard)3354 private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) { 3355 // public view in non group notifications is always collapsed. 3356 if (shouldShowPublic()) { 3357 return false; 3358 } 3359 // RON will always be expanded when it is not on keyguard. 3360 if (!mOnKeyguard) { 3361 return true; 3362 } 3363 // RON will always be expanded when it is allowed on keyguard. 3364 // allowOnKeyguard is used for getting the maximum height by NotificationContentView and 3365 // NotificationChildrenContainer. 3366 if (allowOnKeyguard) { 3367 return true; 3368 } 3369 3370 // RON will be expanded when it needs to ignore lockscreen constraints. 3371 if (mIgnoreLockscreenConstraints) { 3372 return true; 3373 } 3374 3375 // RON will need be collapsed when it needs to save space on the lock screen. 3376 return !mSaveSpaceOnLockscreen; 3377 } 3378 3379 /** 3380 * Is this row currently showing an expanded state? This method is different from 3381 * {@link #isExpanded()}, because it also handles groups, and pinned notifications. 3382 */ isShowingExpanded()3383 private boolean isShowingExpanded() { 3384 if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot()) { 3385 // is group and expanded? 3386 return isGroupExpanded(); 3387 } else if (mEnableNonGroupedNotificationExpand) { 3388 if (isPinned()) { 3389 // is pinned and expanded? 3390 return mExpandedWhenPinned; 3391 } else { 3392 // is regular notification and expanded? 3393 return isExpanded(); 3394 } 3395 } else { 3396 return false; 3397 } 3398 } 3399 3400 /** 3401 * Check whether the view state is currently expanded. This is given by the system in {@link 3402 * #setSystemExpanded(boolean)} and can be overridden by user expansion or 3403 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this 3404 * view can differ from this state, if layout params are modified from outside. 3405 * 3406 * @return whether the view state is currently expanded. 3407 */ isExpanded()3408 public boolean isExpanded() { 3409 return isExpanded(false /* allowOnKeyguard */); 3410 } 3411 isExpanded(boolean allowOnKeyguard)3412 public boolean isExpanded(boolean allowOnKeyguard) { 3413 if (isPromotedOngoing()) { 3414 return isPromotedNotificationExpanded(allowOnKeyguard); 3415 } 3416 3417 return (!shouldShowPublic()) && (!mOnKeyguard || allowOnKeyguard) 3418 && (!hasUserChangedExpansion() 3419 && (isSystemExpanded() || isSystemChildExpanded()) 3420 || isUserExpanded()); 3421 } 3422 isSystemChildExpanded()3423 private boolean isSystemChildExpanded() { 3424 return mIsSystemChildExpanded; 3425 } 3426 setSystemChildExpanded(boolean expanded)3427 public void setSystemChildExpanded(boolean expanded) { 3428 mIsSystemChildExpanded = expanded; 3429 } 3430 setLayoutListener(@ullable LayoutListener listener)3431 public void setLayoutListener(@Nullable LayoutListener listener) { 3432 mLayoutListener = listener; 3433 } 3434 3435 @Override onLayout(boolean changed, int left, int top, int right, int bottom)3436 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 3437 Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout")); 3438 int intrinsicBefore = getIntrinsicHeight(); 3439 super.onLayout(changed, left, top, right, bottom); 3440 if (intrinsicBefore != getIntrinsicHeight() 3441 && (intrinsicBefore != 0 || getActualHeight() > 0)) { 3442 notifyHeightChanged(/* needsAnimation= */ true); 3443 } 3444 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 3445 mMenuRow.onParentHeightUpdate(); 3446 } 3447 updateContentShiftHeight(); 3448 if (mLayoutListener != null) { 3449 mLayoutListener.onLayout(); 3450 } 3451 Trace.endSection(); 3452 } 3453 3454 /** 3455 * Updates the content shift height such that the header is completely hidden when coming from 3456 * the top. 3457 */ updateContentShiftHeight()3458 private void updateContentShiftHeight() { 3459 NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper(); 3460 CachingIconView icon = wrapper == null ? null : wrapper.getIcon(); 3461 if (icon != null) { 3462 mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight(); 3463 } else { 3464 mIconTransformContentShift = mContentShift; 3465 } 3466 } 3467 3468 @Override getContentTransformationShift()3469 protected float getContentTransformationShift() { 3470 return mIconTransformContentShift; 3471 } 3472 3473 @Override notifyHeightChanged(boolean needsAnimation)3474 public void notifyHeightChanged(boolean needsAnimation) { 3475 super.notifyHeightChanged(needsAnimation); 3476 getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked()); 3477 } 3478 setSensitive(boolean sensitive, boolean hideSensitive)3479 public void setSensitive(boolean sensitive, boolean hideSensitive) { 3480 if (notificationsRedesignTemplates() 3481 && sensitive == mSensitive && hideSensitive == mSensitiveHiddenInGeneral) { 3482 return; // nothing has changed 3483 } 3484 3485 int intrinsicBefore = getIntrinsicHeight(); 3486 mSensitive = sensitive; 3487 mSensitiveHiddenInGeneral = hideSensitive; 3488 int intrinsicAfter = getIntrinsicHeight(); 3489 if (intrinsicBefore != intrinsicAfter) { 3490 notifyHeightChanged(/* needsAnimation= */ true); 3491 } else if (notificationsRedesignTemplates()) { 3492 // Just request the correct layout, even if the height hasn't changed 3493 getShowingLayout().requestSelectLayout(/* needsAnimation= */ true); 3494 } 3495 } 3496 3497 /** Sets whether this notification row should show the notification expander or not */ setPublicExpanderVisible(boolean showPublicExpander)3498 public void setPublicExpanderVisible(boolean showPublicExpander) { 3499 if (mShowPublicExpander != showPublicExpander) { 3500 mShowPublicExpander = showPublicExpander; 3501 mPublicLayout.updateExpandButtons(mShowPublicExpander); 3502 } 3503 } 3504 3505 @Override setHideSensitiveForIntrinsicHeight(boolean hideSensitive)3506 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { 3507 mHideSensitiveForIntrinsicHeight = hideSensitive; 3508 if (mIsSummaryWithChildren) { 3509 List<ExpandableNotificationRow> notificationChildren = 3510 mChildrenContainer.getAttachedChildren(); 3511 for (int i = 0; i < notificationChildren.size(); i++) { 3512 ExpandableNotificationRow child = notificationChildren.get(i); 3513 child.setHideSensitiveForIntrinsicHeight(hideSensitive); 3514 } 3515 } 3516 } 3517 3518 @Override setHideSensitive(boolean hideSensitive, boolean animated, long delay, long duration)3519 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, 3520 long duration) { 3521 if (getVisibility() == GONE) { 3522 // If we are GONE, the hideSensitive parameter will not be calculated and always be 3523 // false, which is incorrect, let's wait until a real call comes in later. 3524 return; 3525 } 3526 boolean oldShowingPublic = mShowingPublic; 3527 mShowingPublic = mSensitive && hideSensitive; 3528 boolean isShowingLayoutNotChanged = mShowingPublic == oldShowingPublic; 3529 if (mShowingPublicInitialized && isShowingLayoutNotChanged) { 3530 return; 3531 } 3532 3533 final boolean shouldSkipHideSensitiveAnimation = 3534 Flags.skipHideSensitiveNotifAnimation() && isShowingLayoutNotChanged; 3535 if (!animated || shouldSkipHideSensitiveAnimation) { 3536 if (!NotificationContentAlphaOptimization.isEnabled() 3537 || mShowingPublic != oldShowingPublic) { 3538 // Don't reset the alpha or cancel the animation if the showing layout doesn't 3539 // change 3540 mPublicLayout.animate().cancel(); 3541 mPrivateLayout.animate().cancel(); 3542 if (mChildrenContainer != null) { 3543 mChildrenContainer.animate().cancel(); 3544 } 3545 resetAllContentAlphas(); 3546 } else { 3547 mLogger.logSkipResetAllContentAlphas(mLoggingKey); 3548 } 3549 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); 3550 updateChildrenVisibility(); 3551 } else { 3552 animateShowingPublic(delay, duration, mShowingPublic); 3553 } 3554 NotificationContentView showingLayout = getShowingLayout(); 3555 showingLayout.updateBackgroundColor(animated); 3556 mPrivateLayout.updateExpandButtons(isExpandable()); 3557 updateShelfIconColor(); 3558 mShowingPublicInitialized = true; 3559 } 3560 animateShowingPublic(long delay, long duration, boolean showingPublic)3561 private void animateShowingPublic(long delay, long duration, boolean showingPublic) { 3562 View[] privateViews = mIsSummaryWithChildren 3563 ? new View[]{mChildrenContainer} 3564 : new View[]{mPrivateLayout}; 3565 View[] publicViews = new View[]{mPublicLayout}; 3566 View[] hiddenChildren = showingPublic ? privateViews : publicViews; 3567 View[] shownChildren = showingPublic ? publicViews : privateViews; 3568 // disappear/appear overlap: 10 percent of duration 3569 long overlap = duration / 10; 3570 // disappear duration: 1/3 of duration + half of overlap 3571 long disappearDuration = duration / 3 + overlap / 2; 3572 // appear duration: 2/3 of duration + half of overlap 3573 long appearDuration = (duration - disappearDuration) + overlap / 2; 3574 for (final View hiddenView : hiddenChildren) { 3575 hiddenView.setVisibility(View.VISIBLE); 3576 hiddenView.animate().cancel(); 3577 hiddenView.animate() 3578 .alpha(0f) 3579 .setStartDelay(delay) 3580 .setDuration(disappearDuration) 3581 .withEndAction(() -> { 3582 hiddenView.setVisibility(View.INVISIBLE); 3583 resetAllContentAlphas(); 3584 }); 3585 } 3586 for (View showView : shownChildren) { 3587 showView.setVisibility(View.VISIBLE); 3588 showView.setAlpha(0f); 3589 showView.animate().cancel(); 3590 showView.animate() 3591 .alpha(1f) 3592 .setStartDelay(delay + duration - appearDuration) 3593 .setDuration(appearDuration); 3594 } 3595 } 3596 3597 @Override mustStayOnScreen()3598 public boolean mustStayOnScreen() { 3599 // Must stay on screen in the open shade regardless how much the stack is scrolled if: 3600 // 1. Is HUN and not marked as seen yet (isHeadsUp && mustStayOnScreen) 3601 // 2. Is an FSI HUN (isPinned) 3602 return mIsHeadsUp && mMustStayOnScreen || notificationsPinnedHunInShade() && isPinned(); 3603 } 3604 3605 /** 3606 * For the case of an {@link ExpandableNotificationRow}, the dismissibility of the row considers 3607 * the exposure of guts, the state of the notification entry, and if the view itself is allowed 3608 * to be dismissed. 3609 */ 3610 @Override canExpandableViewBeDismissed()3611 public boolean canExpandableViewBeDismissed() { 3612 if (areGutsExposed() || !hasFinishedInitialization()) { 3613 return false; 3614 } 3615 return canViewBeDismissed(); 3616 } 3617 3618 /** 3619 * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as 3620 * otherwise some state might not be updated. 3621 */ canViewBeDismissed()3622 public boolean canViewBeDismissed() { 3623 return canEntryBeDismissed() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); 3624 } 3625 canEntryBeDismissed()3626 private boolean canEntryBeDismissed() { 3627 return mDismissibilityProvider.isDismissable(getKey()); 3628 } 3629 3630 /** 3631 * @return Whether this view is allowed to be cleared with clear all. Only valid for visible 3632 * notifications as otherwise some state might not be updated. To request about the general 3633 * clearability see {@link NotificationEntry#isClearable()}. 3634 */ canViewBeCleared()3635 public boolean canViewBeCleared() { 3636 if (NotificationBundleUi.isEnabled()) { 3637 return mEntryAdapter.isClearable() 3638 && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); 3639 } else { 3640 return getEntryLegacy().isClearable() 3641 && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); 3642 } 3643 } 3644 shouldShowPublic()3645 private boolean shouldShowPublic() { 3646 return mSensitive && mHideSensitiveForIntrinsicHeight; 3647 } 3648 makeActionsVisibile()3649 public void makeActionsVisibile() { 3650 setUserExpanded(true, true); 3651 if (isChildInGroup()) { 3652 if (!NotificationBundleUi.isEnabled()) { 3653 // this is only called if row.getParent() instanceof NotificationStackScrollLayout, 3654 // so there is never a group to expand 3655 mGroupExpansionManager.setGroupExpanded(getEntryLegacy(), true); 3656 } 3657 } 3658 notifyHeightChanged(/* needsAnimation= */ false); 3659 } 3660 setChildrenExpanded(boolean expanded)3661 public void setChildrenExpanded(boolean expanded) { 3662 mChildrenExpanded = expanded; 3663 if (mChildrenContainer != null) { 3664 mChildrenContainer.setChildrenExpanded(expanded); 3665 } 3666 updateBackgroundForGroupState(); 3667 updateClickAndFocus(); 3668 } 3669 getMaxExpandHeight()3670 public int getMaxExpandHeight() { 3671 return mPrivateLayout.getExpandHeight(); 3672 } 3673 3674 getHeadsUpHeight()3675 private int getHeadsUpHeight() { 3676 return getShowingLayout().getHeadsUpHeight(false /* forceNoHeader */); 3677 } 3678 areGutsExposed()3679 public boolean areGutsExposed() { 3680 return (mGuts != null && mGuts.isExposed()); 3681 } 3682 isGutsLeaveBehind()3683 private boolean isGutsLeaveBehind() { 3684 return (mGuts != null && mGuts.isLeavebehind()); 3685 } 3686 3687 @Override isContentExpandable()3688 public boolean isContentExpandable() { 3689 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3690 return true; 3691 } 3692 NotificationContentView showingLayout = getShowingLayout(); 3693 return showingLayout.isContentExpandable(); 3694 } 3695 3696 @Override getContentView()3697 protected View getContentView() { 3698 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3699 return mChildrenContainer; 3700 } 3701 return getShowingLayout(); 3702 } 3703 3704 @Override performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, boolean isHeadsUpCycling, Runnable onFinishRunnable)3705 public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, 3706 boolean isHeadsUpCycling, Runnable onFinishRunnable) { 3707 mLogger.logStartAppearAnimation(mLoggingKey, /* isAppear = */ true); 3708 super.performAddAnimation(delay, duration, isHeadsUpAppear, isHeadsUpCycling, 3709 onFinishRunnable); 3710 } 3711 3712 @Override performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, boolean isHeadsUpCycling, Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener, ClipSide clipSide)3713 public long performRemoveAnimation(long duration, long delay, float translationDirection, 3714 boolean isHeadsUpAnimation, boolean isHeadsUpCycling, Runnable onStartedRunnable, 3715 Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener, 3716 ClipSide clipSide) { 3717 mLogger.logStartAppearAnimation(mLoggingKey, /* isAppear = */ false); 3718 if (mMenuRow != null && mMenuRow.isMenuVisible()) { 3719 Animator anim = getTranslateViewAnimator(0f, null /* listener */); 3720 if (anim != null) { 3721 anim.addListener(new AnimatorListenerAdapter() { 3722 @Override 3723 public void onAnimationStart(Animator animation) { 3724 if (onStartedRunnable != null) { 3725 onStartedRunnable.run(); 3726 } 3727 } 3728 3729 @Override 3730 public void onAnimationEnd(Animator animation) { 3731 ExpandableNotificationRow.super.performRemoveAnimation(duration, delay, 3732 translationDirection, isHeadsUpAnimation, isHeadsUpCycling, null, 3733 onFinishedRunnable, animationListener, ClipSide.BOTTOM); 3734 } 3735 }); 3736 anim.start(); 3737 return anim.getDuration(); 3738 } 3739 } 3740 return super.performRemoveAnimation(duration, delay, translationDirection, 3741 isHeadsUpAnimation, isHeadsUpCycling, onStartedRunnable, onFinishedRunnable, 3742 animationListener, clipSide); 3743 } 3744 3745 @Override onAppearAnimationStarted(boolean isAppear)3746 protected void onAppearAnimationStarted(boolean isAppear) { 3747 mLogger.logAppearAnimationStarted(mLoggingKey, /* isAppear = */ isAppear); 3748 super.onAppearAnimationStarted(isAppear); 3749 } 3750 3751 @Override onAppearAnimationSkipped(boolean isAppear)3752 protected void onAppearAnimationSkipped(boolean isAppear) { 3753 mLogger.logAppearAnimationSkipped(mLoggingKey, /* isAppear = */ isAppear); 3754 super.onAppearAnimationSkipped(isAppear); 3755 } 3756 3757 @Override onAppearAnimationFinished(boolean wasAppearing, boolean cancelled)3758 protected void onAppearAnimationFinished(boolean wasAppearing, boolean cancelled) { 3759 mLogger.logAppearAnimationFinished( 3760 /* entry = */ mLoggingKey, 3761 /* isAppear = */ wasAppearing, 3762 /* cancelled = */ cancelled 3763 ); 3764 super.onAppearAnimationFinished(wasAppearing, cancelled); 3765 if (wasAppearing) { 3766 // During the animation the visible view might have changed, so let's make sure all 3767 // alphas are reset 3768 resetAllContentAlphas(); 3769 if (FADE_LAYER_OPTIMIZATION_ENABLED) { 3770 setNotificationFaded(false); 3771 } else { 3772 setNotificationFadedOnChildren(false); 3773 } 3774 } else { 3775 setHeadsUpAnimatingAway(false); 3776 } 3777 } 3778 3779 @Override cancelAppearDrawing()3780 public void cancelAppearDrawing() { 3781 mLogger.logCancelAppearDrawing(mLoggingKey, isDrawingAppearAnimation()); 3782 super.cancelAppearDrawing(); 3783 } 3784 3785 @Override resetAllContentAlphas()3786 public void resetAllContentAlphas() { 3787 mLogger.logResetAllContentAlphas(mLoggingKey); 3788 mPrivateLayout.setAlpha(1f); 3789 mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null); 3790 mPublicLayout.setAlpha(1f); 3791 mPublicLayout.setLayerType(LAYER_TYPE_NONE, null); 3792 if (mChildrenContainer != null) { 3793 mChildrenContainer.setAlpha(1f); 3794 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null); 3795 } 3796 } 3797 3798 /** 3799 * Gets the last value set with {@link #setNotificationFaded(boolean)} 3800 */ 3801 @Override isNotificationFaded()3802 public boolean isNotificationFaded() { 3803 return mIsFaded; 3804 } 3805 3806 /** 3807 * This class needs to delegate the faded state set on it by 3808 * {@link com.android.systemui.statusbar.notification.stack.ViewState} to its children. 3809 * Having each notification use layerType of HARDWARE anytime it fades in/out can result in 3810 * extremely large layers (in the case of groups, it can even exceed the device height). 3811 * Because these large renders can cause serious jank when rendering, we instead have 3812 * notifications return false from {@link #hasOverlappingRendering()} and delegate the 3813 * layerType to child views which really need it in order to render correctly, such as icon 3814 * views or the conversation face pile. 3815 * <p> 3816 * Another compounding factor for notifications is that we change clipping on each frame of the 3817 * animation, so the hardware layer isn't able to do any caching at the top level, but the 3818 * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we 3819 * never invalidate them. 3820 */ 3821 @Override setNotificationFaded(boolean faded)3822 public void setNotificationFaded(boolean faded) { 3823 mIsFaded = faded; 3824 if (childrenRequireOverlappingRendering()) { 3825 // == Simple Scenario == 3826 // If a child (like remote input) needs this to have overlapping rendering, then set 3827 // the layerType of this view and reset the children to render as if the notification is 3828 // not fading. 3829 NotificationFadeAware.setLayerTypeForFaded(this, faded); 3830 setNotificationFadedOnChildren(false); 3831 } else { 3832 // == Delegating Scenario == 3833 // This is the new normal for alpha: Explicitly reset this view's layer type to NONE, 3834 // and require that all children use their own hardware layer if they have bad 3835 // overlapping rendering. 3836 NotificationFadeAware.setLayerTypeForFaded(this, false); 3837 setNotificationFadedOnChildren(faded); 3838 } 3839 } 3840 3841 /** 3842 * Private helper for iterating over the layouts and children containers to set faded state 3843 */ setNotificationFadedOnChildren(boolean faded)3844 private void setNotificationFadedOnChildren(boolean faded) { 3845 delegateNotificationFaded(mChildrenContainer, faded); 3846 for (NotificationContentView layout : mLayouts) { 3847 delegateNotificationFaded(layout, faded); 3848 } 3849 } 3850 delegateNotificationFaded(@ullable View view, boolean faded)3851 private static void delegateNotificationFaded(@Nullable View view, boolean faded) { 3852 if (FADE_LAYER_OPTIMIZATION_ENABLED && view instanceof NotificationFadeAware) { 3853 ((NotificationFadeAware) view).setNotificationFaded(faded); 3854 } else { 3855 NotificationFadeAware.setLayerTypeForFaded(view, faded); 3856 } 3857 } 3858 3859 /** 3860 * Only declare overlapping rendering if independent children of the view require it. 3861 */ 3862 @Override hasOverlappingRendering()3863 public boolean hasOverlappingRendering() { 3864 return super.hasOverlappingRendering() && childrenRequireOverlappingRendering(); 3865 } 3866 3867 /** 3868 * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the 3869 * row should require overlapping rendering to ensure that the overlapped view doesn't bleed 3870 * through when alpha fading. 3871 * <p> 3872 * Note that this currently works for top-level notifications which squish their height down 3873 * while collapsing the shade, but does not work for children inside groups, because the 3874 * accordion affect does not apply to those views, so super.hasOverlappingRendering() will 3875 * always return false to avoid the clipping caused when the view's measured height is less than 3876 * the 'actual height'. 3877 */ childrenRequireOverlappingRendering()3878 private boolean childrenRequireOverlappingRendering() { 3879 if (!FADE_LAYER_OPTIMIZATION_ENABLED) { 3880 return true; 3881 } 3882 // The colorized background is another layer with which all other elements overlap 3883 if (NotificationBundleUi.isEnabled()) { 3884 if (mEntryAdapter.isColorized()) { 3885 return true; 3886 } 3887 } else { 3888 if (getEntryLegacy().getSbn().getNotification().isColorized()) { 3889 return true; 3890 } 3891 } 3892 // Check if the showing layout has a need for overlapping rendering. 3893 // NOTE: We could check both public and private layouts here, but becuause these states 3894 // don't animate well, there are bigger visual artifacts if we start changing the shown 3895 // layout during shade expansion. 3896 NotificationContentView showingLayout = getShowingLayout(); 3897 return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering(); 3898 } 3899 3900 @Override setActualHeight(int height, boolean notifyListeners)3901 public void setActualHeight(int height, boolean notifyListeners) { 3902 boolean changed = height != getActualHeight(); 3903 super.setActualHeight(height, notifyListeners); 3904 if (changed && isRemoved()) { 3905 // TODO: remove this once we found the gfx bug for this. 3906 // This is a hack since a removed view sometimes would just stay blank. it occured 3907 // when sending yourself a message and then clicking on it. 3908 ViewGroup parent = (ViewGroup) getParent(); 3909 if (parent != null) { 3910 parent.invalidate(); 3911 } 3912 } 3913 if (mGuts != null && mGuts.isExposed()) { 3914 mGuts.setActualHeight(height); 3915 return; 3916 } 3917 for (NotificationContentView l : mLayouts) { 3918 l.setContentHeight(height); 3919 } 3920 if (mIsSummaryWithChildren) { 3921 mChildrenContainer.setActualHeight(height); 3922 } 3923 if (mGuts != null) { 3924 mGuts.setActualHeight(height); 3925 } 3926 if (mMenuRow != null && mMenuRow.getMenuView() != null) { 3927 mMenuRow.onParentHeightUpdate(); 3928 } 3929 handleIntrinsicHeightReached(); 3930 } 3931 3932 @Override getMaxContentHeight()3933 public int getMaxContentHeight() { 3934 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3935 return mChildrenContainer.getMaxContentHeight(); 3936 } 3937 NotificationContentView showingLayout = getShowingLayout(); 3938 return showingLayout.getMaxHeight(); 3939 } 3940 3941 @Override getMinHeight(boolean ignoreTemporaryStates)3942 public int getMinHeight(boolean ignoreTemporaryStates) { 3943 if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) { 3944 return mGuts.getIntrinsicHeight(); 3945 } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp 3946 && mHeadsUpManager.isTrackingHeadsUp().getValue()) { 3947 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); 3948 } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) { 3949 return mChildrenContainer.getMinHeight(); 3950 } else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) { 3951 return getHeadsUpHeight(); 3952 } 3953 NotificationContentView showingLayout = getShowingLayout(); 3954 return showingLayout.getMinHeight(); 3955 } 3956 3957 @Override getCollapsedHeight()3958 public int getCollapsedHeight() { 3959 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3960 return mChildrenContainer.getCollapsedHeight(); 3961 } 3962 return getMinHeight(); 3963 } 3964 3965 @Override getHeadsUpHeightWithoutHeader()3966 public int getHeadsUpHeightWithoutHeader() { 3967 if (!canShowHeadsUp() || !mIsHeadsUp) { 3968 return getCollapsedHeight(); 3969 } 3970 if (mIsSummaryWithChildren && !shouldShowPublic()) { 3971 return mChildrenContainer.getCollapsedHeightWithoutHeader(); 3972 } 3973 return getShowingLayout().getHeadsUpHeight(true /* forceNoHeader */); 3974 } 3975 3976 @Override setClipTopAmount(int clipTopAmount)3977 public void setClipTopAmount(int clipTopAmount) { 3978 super.setClipTopAmount(clipTopAmount); 3979 for (NotificationContentView l : mLayouts) { 3980 l.setClipTopAmount(clipTopAmount); 3981 } 3982 if (mGuts != null) { 3983 mGuts.setClipTopAmount(clipTopAmount); 3984 } 3985 } 3986 3987 @Override setClipBottomAmount(int clipBottomAmount)3988 public void setClipBottomAmount(int clipBottomAmount) { 3989 if (mExpandAnimationRunning) { 3990 return; 3991 } 3992 if (clipBottomAmount != mClipBottomAmount) { 3993 super.setClipBottomAmount(clipBottomAmount); 3994 for (NotificationContentView l : mLayouts) { 3995 l.setClipBottomAmount(clipBottomAmount); 3996 } 3997 if (mGuts != null) { 3998 mGuts.setClipBottomAmount(clipBottomAmount); 3999 } 4000 } 4001 if (mChildrenContainer != null && !mChildIsExpanding) { 4002 // We have to update this even if it hasn't changed, since the children locations can 4003 // have changed 4004 mChildrenContainer.setClipBottomAmount(clipBottomAmount); 4005 } 4006 } 4007 getShowingLayout()4008 public NotificationContentView getShowingLayout() { 4009 return shouldShowPublic() ? mPublicLayout : mPrivateLayout; 4010 } 4011 setLegacy(boolean legacy)4012 public void setLegacy(boolean legacy) { 4013 for (NotificationContentView l : mLayouts) { 4014 l.setLegacy(legacy); 4015 } 4016 } 4017 4018 @Override updateBackgroundTint()4019 protected void updateBackgroundTint() { 4020 super.updateBackgroundTint(); 4021 updateBackgroundForGroupState(); 4022 if (mIsSummaryWithChildren) { 4023 List<ExpandableNotificationRow> notificationChildren = 4024 mChildrenContainer.getAttachedChildren(); 4025 for (int i = 0; i < notificationChildren.size(); i++) { 4026 ExpandableNotificationRow child = notificationChildren.get(i); 4027 child.updateBackgroundForGroupState(); 4028 } 4029 } 4030 } 4031 4032 /** 4033 * Called when a group has finished animating from collapsed or expanded state. 4034 */ onFinishedExpansionChange()4035 public void onFinishedExpansionChange() { 4036 mGroupExpansionChanging = false; 4037 updateBackgroundForGroupState(); 4038 } 4039 4040 /** 4041 * Updates the parent and children backgrounds in a group based on the expansion state. 4042 */ updateBackgroundForGroupState()4043 public void updateBackgroundForGroupState() { 4044 if (mIsSummaryWithChildren) { 4045 // Only when the group has finished expanding do we hide its background. 4046 mShowNoBackground = !mShowGroupBackgroundWhenExpanded && isGroupExpanded() 4047 && !isGroupExpansionChanging() && !isUserLocked(); 4048 mChildrenContainer.updateHeaderForExpansion(mShowNoBackground); 4049 List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren(); 4050 for (int i = 0; i < children.size(); i++) { 4051 children.get(i).updateBackgroundForGroupState(); 4052 } 4053 } else if (isChildInGroup()) { 4054 final int childColor = getShowingLayout().getBackgroundColorForExpansionState(); 4055 if ((Flags.notificationRowTransparency() || notificationsRedesignTemplates()) 4056 && childColor == Color.TRANSPARENT) { 4057 // If child is not customizing its background color, switch from the parent to 4058 // the child background when the expansion finishes. 4059 mShowNoBackground = !mNotificationParent.mShowNoBackground; 4060 } else { 4061 // Only show a background if the group is expanded OR if it is 4062 // expanding / collapsing and has a custom background color. 4063 final boolean showBackground = isGroupExpanded() 4064 || ((mNotificationParent.isGroupExpansionChanging() 4065 || mNotificationParent.isUserLocked()) && childColor != 0); 4066 mShowNoBackground = !showBackground; 4067 } 4068 } else { 4069 // Only children or parents ever need no background. 4070 mShowNoBackground = false; 4071 } 4072 updateOutline(); 4073 updateBackground(); 4074 } 4075 4076 @Override hideBackground()4077 protected boolean hideBackground() { 4078 return mShowNoBackground || super.hideBackground(); 4079 } 4080 getPositionOfChild(ExpandableNotificationRow childRow)4081 public int getPositionOfChild(ExpandableNotificationRow childRow) { 4082 if (mIsSummaryWithChildren) { 4083 return mChildrenContainer.getPositionInLinearLayout(childRow); 4084 } 4085 return 0; 4086 } 4087 onExpandedByGesture(boolean userExpanded)4088 public void onExpandedByGesture(boolean userExpanded) { 4089 int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; 4090 if (isGroupRoot()) { 4091 event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER; 4092 } 4093 mMetricsLogger.action(event, userExpanded); 4094 } 4095 4096 @Override disallowSingleClick(MotionEvent event)4097 protected boolean disallowSingleClick(MotionEvent event) { 4098 if (areGutsExposed()) { 4099 return false; 4100 } 4101 float x = event.getX(); 4102 float y = event.getY(); 4103 NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper(); 4104 NotificationHeaderView header = wrapper == null ? null : wrapper.getNotificationHeader(); 4105 // the extra translation only needs to be added, if we're translating the notification 4106 // contents, otherwise the motionEvent is already at the right place due to the 4107 // touch event system. 4108 float translation = !mDismissUsingRowTranslationX ? getTranslation() : 0; 4109 if (header != null && header.isInTouchRect(x - translation, y)) { 4110 return true; 4111 } 4112 if ((!mIsSummaryWithChildren || shouldShowPublic()) 4113 && getShowingLayout().disallowSingleClick(x, y)) { 4114 return true; 4115 } 4116 return super.disallowSingleClick(event); 4117 } 4118 4119 // TODO: b/388470175 - Although this does get triggered when a notification 4120 // is expanded by the system (e.g. the first notication in the shade), it 4121 // will not be when a notification is collapsed by the system (such as when 4122 // the shade is closed). onExpansionChanged(boolean userAction, boolean wasExpanded)4123 private void onExpansionChanged(boolean userAction, boolean wasExpanded) { 4124 boolean nowExpanded = isExpanded(); 4125 if (mIsSummaryWithChildren && (!mIsMinimized || wasExpanded)) { 4126 nowExpanded = isGroupExpanded(); 4127 } 4128 // Note: nowExpanded is going to be true here on the first expansion of minimized groups, 4129 // even though the group itself is not expanded. Use mGroupExpansionManager to get the real 4130 // group expansion if needed. 4131 if (nowExpanded != wasExpanded) { 4132 updateShelfIconColor(); 4133 if (mLogger != null) { 4134 mLogger.logNotificationExpansion(mLoggingKey, getViewState().location, userAction, 4135 nowExpanded); 4136 } 4137 if (mIsSummaryWithChildren) { 4138 mChildrenContainer.onExpansionChanged(); 4139 } 4140 if (mExpansionChangedListener != null) { 4141 mExpansionChangedListener.onExpansionChanged(nowExpanded); 4142 } 4143 if (notificationRowAccessibilityExpanded()) { 4144 notifyAccessibilityContentExpansionChanged(); 4145 } 4146 } 4147 } 4148 notifyAccessibilityContentExpansionChanged()4149 private void notifyAccessibilityContentExpansionChanged() { 4150 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 4151 AccessibilityEvent event = AccessibilityEvent.obtain(); 4152 event.setEventType(TYPE_WINDOW_CONTENT_CHANGED); 4153 event.setContentChangeTypes(CONTENT_CHANGE_TYPE_EXPANDED); 4154 sendAccessibilityEventUnchecked(event); 4155 } 4156 } 4157 setOnExpansionChangedListener(@ullable OnExpansionChangedListener listener)4158 public void setOnExpansionChangedListener(@Nullable OnExpansionChangedListener listener) { 4159 mExpansionChangedListener = listener; 4160 } 4161 4162 /** 4163 * Perform an action when the notification height has reached its intrinsic height. 4164 * 4165 * @param runnable the runnable to run 4166 */ performOnIntrinsicHeightReached(@ullable Runnable runnable)4167 public void performOnIntrinsicHeightReached(@Nullable Runnable runnable) { 4168 mOnIntrinsicHeightReachedRunnable = runnable; 4169 handleIntrinsicHeightReached(); 4170 } 4171 handleIntrinsicHeightReached()4172 private void handleIntrinsicHeightReached() { 4173 if (mOnIntrinsicHeightReachedRunnable != null 4174 && getActualHeight() == getIntrinsicHeight()) { 4175 mOnIntrinsicHeightReachedRunnable.run(); 4176 mOnIntrinsicHeightReachedRunnable = null; 4177 } 4178 } 4179 4180 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)4181 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 4182 super.onInitializeAccessibilityNodeInfoInternal(info); 4183 final boolean isLongClickable = isNotificationRowLongClickable(); 4184 if (isLongClickable) { 4185 info.addAction(AccessibilityAction.ACTION_LONG_CLICK); 4186 } 4187 info.setLongClickable(isLongClickable); 4188 4189 if (canViewBeDismissed() && !mIsSnoozed) { 4190 info.addAction(AccessibilityAction.ACTION_DISMISS); 4191 } 4192 4193 if (notificationRowAccessibilityExpanded()) { 4194 if (isAccessibilityExpandable()) { 4195 if (isShowingExpanded()) { 4196 info.addAction(AccessibilityAction.ACTION_COLLAPSE); 4197 info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_FULL); 4198 } else { 4199 info.addAction(AccessibilityAction.ACTION_EXPAND); 4200 info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_COLLAPSED); 4201 } 4202 } else { 4203 info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_UNDEFINED); 4204 } 4205 } else { 4206 boolean expandable = shouldShowPublic(); 4207 boolean isExpanded = false; 4208 if (!expandable) { 4209 if (mIsSummaryWithChildren) { 4210 expandable = true; 4211 if (!mIsMinimized || isExpanded()) { 4212 isExpanded = isGroupExpanded(); 4213 } 4214 } else { 4215 expandable = mPrivateLayout.isContentExpandable(); 4216 isExpanded = isExpanded(); 4217 } 4218 } 4219 4220 if (expandable) { 4221 if (isExpanded) { 4222 info.addAction(AccessibilityAction.ACTION_COLLAPSE); 4223 } else { 4224 info.addAction(AccessibilityAction.ACTION_EXPAND); 4225 } 4226 } 4227 } 4228 4229 NotificationMenuRowPlugin provider = getProvider(); 4230 if (provider != null) { 4231 MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext()); 4232 if (snoozeMenu != null) { 4233 AccessibilityAction action = new AccessibilityAction(R.id.action_snooze, 4234 getContext().getResources() 4235 .getString(R.string.notification_menu_snooze_action)); 4236 info.addAction(action); 4237 } 4238 } 4239 } 4240 4241 /** @return whether this row's expansion state can be toggled by an accessibility action. */ isAccessibilityExpandable()4242 private boolean isAccessibilityExpandable() { 4243 // don't add expand accessibility actions to snoozed notifications 4244 return !mIsSnoozed && isContentExpandable(); 4245 } 4246 4247 @Override performAccessibilityActionInternal(int action, Bundle arguments)4248 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 4249 if (super.performAccessibilityActionInternal(action, arguments)) { 4250 return true; 4251 } 4252 switch (action) { 4253 case AccessibilityNodeInfo.ACTION_DISMISS: 4254 performDismiss(true /* fromAccessibility */); 4255 return true; 4256 case AccessibilityNodeInfo.ACTION_COLLAPSE: 4257 case AccessibilityNodeInfo.ACTION_EXPAND: 4258 mExpandClickListener.onClick(this); 4259 return true; 4260 case AccessibilityNodeInfo.ACTION_LONG_CLICK: 4261 doLongClickCallback(); 4262 return true; 4263 default: 4264 if (action == R.id.action_snooze) { 4265 NotificationMenuRowPlugin provider = getProvider(); 4266 if (provider == null) { 4267 return false; 4268 } 4269 MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext()); 4270 if (snoozeMenu != null) { 4271 doLongClickCallback(getWidth() / 2, getHeight() / 2, snoozeMenu); 4272 } 4273 return true; 4274 } 4275 } 4276 return false; 4277 } 4278 4279 public interface OnExpandClickListener { onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded)4280 void onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded); 4281 onExpandClicked(ExpandableNotificationRow row, EntryAdapter clickedEntry, boolean nowExpanded)4282 void onExpandClicked(ExpandableNotificationRow row, EntryAdapter clickedEntry, 4283 boolean nowExpanded); 4284 } 4285 4286 @Override 4287 @NonNull createExpandableViewState()4288 public ExpandableViewState createExpandableViewState() { 4289 return new NotificationViewState(); 4290 } 4291 4292 @Override isAboveShelf()4293 public boolean isAboveShelf() { 4294 return (canShowHeadsUp() 4295 && (mPinnedStatus.isPinned() 4296 || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf) 4297 || mExpandAnimationRunning || mChildIsExpanding)); 4298 } 4299 4300 @Override childNeedsClipping(View child)4301 protected boolean childNeedsClipping(View child) { 4302 if (child instanceof NotificationContentView contentView) { 4303 if (isClippingNeeded()) { 4304 return true; 4305 } else if (hasRoundedCorner() 4306 && contentView.shouldClipToRounding(getTopRoundness() != 0.0f, 4307 getBottomRoundness() != 0.0f)) { 4308 return true; 4309 } 4310 } else if (child == mChildrenContainer) { 4311 if (isClippingNeeded() || hasRoundedCorner()) { 4312 return true; 4313 } 4314 } else if (child instanceof NotificationGuts) { 4315 return hasRoundedCorner(); 4316 } 4317 return super.childNeedsClipping(child); 4318 } 4319 4320 /** 4321 * Set a clip path to be set while expanding the notification. This is needed to nicely 4322 * clip ourselves during the launch if we were clipped rounded in the beginning 4323 */ setExpandingClipPath(Path path)4324 public void setExpandingClipPath(Path path) { 4325 mExpandingClipPath = path; 4326 invalidate(); 4327 } 4328 4329 @Override dispatchDraw(Canvas canvas)4330 protected void dispatchDraw(Canvas canvas) { 4331 canvas.save(); 4332 if (mExpandingClipPath != null && (mExpandAnimationRunning || mChildIsExpanding)) { 4333 // If we're launching a notification, let's clip if a clip rounded to the clipPath 4334 canvas.clipPath(mExpandingClipPath); 4335 } 4336 super.dispatchDraw(canvas); 4337 canvas.restore(); 4338 } 4339 4340 @Override applyRoundnessAndInvalidate()4341 public void applyRoundnessAndInvalidate() { 4342 applyChildrenRoundness(); 4343 super.applyRoundnessAndInvalidate(); 4344 } 4345 applyChildrenRoundness()4346 private void applyChildrenRoundness() { 4347 if (mIsSummaryWithChildren) { 4348 mChildrenContainer.requestRoundness( 4349 /* top = */ getTopRoundness(), 4350 /* bottom = */ getBottomRoundness(), 4351 /* sourceType = */ FROM_PARENT, 4352 /* animate = */ false); 4353 } 4354 } 4355 4356 @Override getCustomClipPath(View child)4357 public Path getCustomClipPath(View child) { 4358 if (child instanceof NotificationGuts) { 4359 return getClipPath(true /* ignoreTranslation */); 4360 } 4361 return super.getCustomClipPath(child); 4362 } 4363 isMediaRow()4364 public boolean isMediaRow() { 4365 NotificationBundleUi.assertInLegacyMode(); 4366 return getEntryLegacy().getSbn().getNotification().isMediaNotification(); 4367 } 4368 setAboveShelf(boolean aboveShelf)4369 public void setAboveShelf(boolean aboveShelf) { 4370 boolean wasAboveShelf = isAboveShelf(); 4371 mAboveShelf = aboveShelf; 4372 if (isAboveShelf() != wasAboveShelf) { 4373 mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); 4374 } 4375 } 4376 4377 private static class NotificationViewState extends ExpandableViewState { 4378 4379 @Override applyToView(View view)4380 public void applyToView(View view) { 4381 if (view instanceof ExpandableNotificationRow row) { 4382 if (row.isExpandAnimationRunning()) { 4383 return; 4384 } 4385 handleFixedTranslationZ(row); 4386 super.applyToView(view); 4387 row.applyChildrenState(); 4388 } 4389 } 4390 handleFixedTranslationZ(ExpandableNotificationRow row)4391 private void handleFixedTranslationZ(ExpandableNotificationRow row) { 4392 if (row.hasExpandingChild()) { 4393 setZTranslation(row.getTranslationZ()); 4394 clipTopAmount = row.getClipTopAmount(); 4395 } 4396 } 4397 4398 @Override onYTranslationAnimationFinished(View view)4399 protected void onYTranslationAnimationFinished(View view) { 4400 super.onYTranslationAnimationFinished(view); 4401 if (view instanceof ExpandableNotificationRow row) { 4402 if (row.isHeadsUpAnimatingAway()) { 4403 row.setHeadsUpAnimatingAway(false); 4404 } 4405 } 4406 } 4407 4408 @Override animateTo(View child, AnimationProperties properties)4409 public void animateTo(View child, AnimationProperties properties) { 4410 if (child instanceof ExpandableNotificationRow row) { 4411 if (row.isExpandAnimationRunning()) { 4412 return; 4413 } 4414 handleFixedTranslationZ(row); 4415 super.animateTo(child, properties); 4416 row.startChildAnimation(properties); 4417 } 4418 } 4419 } 4420 4421 /** 4422 * Returns the Smart Suggestions backing the smart suggestion buttons in the notification. 4423 */ getExistingSmartReplyState()4424 public InflatedSmartReplyState getExistingSmartReplyState() { 4425 return mPrivateLayout.getCurrentSmartReplyState(); 4426 } 4427 4428 @VisibleForTesting setChildrenContainer(NotificationChildrenContainer childrenContainer)4429 protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) { 4430 mChildrenContainer = childrenContainer; 4431 } 4432 4433 @VisibleForTesting setPrivateLayout(NotificationContentView privateLayout)4434 protected void setPrivateLayout(NotificationContentView privateLayout) { 4435 mPrivateLayout = privateLayout; 4436 mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; 4437 } 4438 4439 @VisibleForTesting setPublicLayout(NotificationContentView publicLayout)4440 protected void setPublicLayout(NotificationContentView publicLayout) { 4441 mPublicLayout = publicLayout; 4442 mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout}; 4443 } 4444 4445 @VisibleForTesting setMagneticRowListener(MagneticRowListener listener)4446 public void setMagneticRowListener(MagneticRowListener listener) { 4447 mMagneticRowListener = listener; 4448 } 4449 4450 /** 4451 * Equivalent to View.OnLongClickListener with coordinates 4452 */ 4453 public interface LongPressListener { 4454 /** 4455 * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates 4456 * 4457 * @return whether the longpress was handled 4458 */ onLongPress(View v, int x, int y, MenuItem item)4459 boolean onLongPress(View v, int x, int y, MenuItem item); 4460 } 4461 4462 /** 4463 * Called when notification drag and drop is finished successfully. 4464 */ 4465 public interface OnDragSuccessListener { 4466 /** 4467 * @param entry NotificationEntry that succeed to drop on proper target window. 4468 */ onDragSuccess(NotificationEntry entry)4469 void onDragSuccess(NotificationEntry entry); 4470 4471 /** 4472 * @param entryAdapter The EntryAdapter that successfully dropped on the proper 4473 * target window 4474 */ onDragSuccess(EntryAdapter entryAdapter)4475 void onDragSuccess(EntryAdapter entryAdapter); 4476 } 4477 4478 /** 4479 * Equivalent to View.OnClickListener with coordinates 4480 */ 4481 public interface CoordinateOnClickListener { 4482 /** 4483 * Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates 4484 * 4485 * @return whether the click was handled 4486 */ onClick(View v, int x, int y, MenuItem item)4487 boolean onClick(View v, int x, int y, MenuItem item); 4488 } 4489 4490 @Override dump(PrintWriter pwOriginal, String[] args)4491 public void dump(PrintWriter pwOriginal, String[] args) { 4492 IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); 4493 // Skip super call; dump viewState ourselves 4494 pw.println("Notification: " + getKey()); 4495 DumpUtilsKt.withIncreasedIndent(pw, () -> { 4496 pw.println(this); 4497 pw.print("visibility: " + getVisibility()); 4498 pw.print(", alpha: " + getAlpha()); 4499 pw.print(", translation: " + getTranslation()); 4500 pw.print(", entry dismissable: " + canEntryBeDismissed()); 4501 pw.print(", mOnUserInteractionCallback==null: " + (mOnUserInteractionCallback == null)); 4502 pw.print(", removed: " + isRemoved()); 4503 pw.print(", expandAnimationRunning: " + mExpandAnimationRunning); 4504 pw.print(", mShowingPublic: " + mShowingPublic); 4505 pw.print(", mShowingPublicInitialized: " + mShowingPublicInitialized); 4506 NotificationContentView showingLayout = getShowingLayout(); 4507 pw.print(", privateShowing: " + (showingLayout == mPrivateLayout)); 4508 pw.print(", childrenContainerShowing: " 4509 + (!shouldShowPublic() && mIsSummaryWithChildren)); 4510 pw.print(", mShowNoBackground: " + mShowNoBackground); 4511 pw.print(", clipBounds: " + getClipBounds()); 4512 pw.print(", isPromotedOngoing: " + isPromotedOngoing()); 4513 if (notificationRowAccessibilityExpanded()) { 4514 pw.print(", isShowingExpanded: " + isShowingExpanded()); 4515 pw.print(", isAccessibilityExpandable: " + isAccessibilityExpandable()); 4516 } 4517 pw.print(", isExpandable: " + isExpandable()); 4518 pw.print(", mExpandable: " + mExpandable); 4519 pw.print(", isUserExpanded: " + isUserExpanded()); 4520 pw.print(", hasUserChangedExpansion: " + mHasUserChangedExpansion); 4521 pw.print(", isOnKeyguard: " + isOnKeyguard()); 4522 pw.print(", isSummaryWithChildren: " + mIsSummaryWithChildren); 4523 pw.print(", enableNonGroupedExpand: " + mEnableNonGroupedNotificationExpand); 4524 pw.print(", isPinned: " + isPinned()); 4525 pw.print(", expandedWhenPinned: " + mExpandedWhenPinned); 4526 pw.print(", isMinimized: " + mIsMinimized); 4527 pw.print(", isAboveShelf: " + isAboveShelf()); 4528 pw.print(", redactionType: " + mRedactionType); 4529 4530 pw.println(); 4531 if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) { 4532 dumpHeights(pw); 4533 } 4534 showingLayout.dump(pw, args); 4535 dumpAppearAnimationProperties(pw, args); 4536 dumpCustomOutline(pw, args); 4537 dumpClipping(pw, args); 4538 if (getViewState() != null) { 4539 getViewState().dump(pw, args); 4540 pw.println(); 4541 } else { 4542 pw.println("no viewState!!!"); 4543 } 4544 pw.println(getRoundableState().debugString()); 4545 if (mBigPictureIconManager != null) { 4546 mBigPictureIconManager.dump(pw, args); 4547 } 4548 dumpBackgroundView(pw, args); 4549 4550 int transientViewCount = mChildrenContainer == null 4551 ? 0 : mChildrenContainer.getTransientViewCount(); 4552 if (mIsSummaryWithChildren || transientViewCount > 0) { 4553 pw.println(mChildrenContainer.debugString()); 4554 pw.println("Children Container Intrinsic Height: " 4555 + mChildrenContainer.getIntrinsicHeight()); 4556 pw.println(); 4557 dumpChildren(pw, args); 4558 dumpTransientViews(transientViewCount, pw, args); 4559 } else if (mPrivateLayout != null) { 4560 mPrivateLayout.dumpSmartReplies(pw); 4561 } 4562 }); 4563 } 4564 dumpChildren(IndentingPrintWriter pw, String[] args)4565 private void dumpChildren(IndentingPrintWriter pw, String[] args) { 4566 List<ExpandableNotificationRow> notificationChildren = getAttachedChildren(); 4567 pw.print("Children: " + notificationChildren.size() + " {"); 4568 DumpUtilsKt.withIncreasedIndent(pw, () -> { 4569 for (ExpandableNotificationRow child : notificationChildren) { 4570 pw.println(); 4571 child.dump(pw, args); 4572 } 4573 }); 4574 pw.println("}"); 4575 } 4576 dumpTransientViews(int transientCount, IndentingPrintWriter pw, String[] args)4577 private void dumpTransientViews(int transientCount, IndentingPrintWriter pw, String[] args) { 4578 pw.print("Transient Views: " + transientCount + " {"); 4579 DumpUtilsKt.withIncreasedIndent(pw, () -> { 4580 for (int i = 0; i < transientCount; i++) { 4581 pw.println(); 4582 ExpandableView child = (ExpandableView) mChildrenContainer.getTransientView(i); 4583 child.dump(pw, args); 4584 } 4585 }); 4586 pw.println("}"); 4587 } 4588 dumpHeights(IndentingPrintWriter pw)4589 private void dumpHeights(IndentingPrintWriter pw) { 4590 pw.print("Heights: "); 4591 pw.print("intrinsic", getIntrinsicHeight()); 4592 pw.print("actual", getActualHeight()); 4593 pw.print("maxContent", getMaxContentHeight()); 4594 pw.print("maxExpanded", getMaxExpandHeight()); 4595 pw.print("collapsed", getCollapsedHeight()); 4596 pw.print("headsup", getHeadsUpHeight()); 4597 pw.print("headsup without header", getHeadsUpHeightWithoutHeader()); 4598 pw.print("minHeight", getMinHeight()); 4599 pw.print("pinned headsup", getPinnedHeadsUpHeight( 4600 true /* atLeastMinHeight */)); 4601 pw.println(); 4602 pw.print("Intrinsic Height Factors: "); 4603 pw.print("isUserLocked()", isUserLocked()); 4604 pw.print("isChildInGroup()", isChildInGroup()); 4605 pw.print("isGroupExpanded()", isGroupExpanded()); 4606 pw.print("sensitive", mSensitive); 4607 pw.print("hideSensitiveForIntrinsicHeight", mHideSensitiveForIntrinsicHeight); 4608 pw.print("isSummaryWithChildren", mIsSummaryWithChildren); 4609 pw.print("canShowHeadsUp()", canShowHeadsUp()); 4610 pw.print("isHeadsUpState()", isHeadsUpState()); 4611 pw.print("isPinned()", isPinned()); 4612 pw.print("headsupDisappearRunning", mHeadsupDisappearRunning); 4613 pw.print("isExpanded()", isExpanded()); 4614 pw.println(); 4615 } 4616 logKeepInParentChildDetached(ExpandableNotificationRow child)4617 private void logKeepInParentChildDetached(ExpandableNotificationRow child) { 4618 if (mLogger != null) { 4619 mLogger.logKeepInParentChildDetached(child.getLoggingKey(), mLoggingKey); 4620 } 4621 } 4622 logSkipAttachingKeepInParentChild(ExpandableNotificationRow child)4623 private void logSkipAttachingKeepInParentChild(ExpandableNotificationRow child) { 4624 if (mLogger != null) { 4625 mLogger.logSkipAttachingKeepInParentChild(child.getLoggingKey(), mLoggingKey); 4626 } 4627 } 4628 setTargetPoint(Point p)4629 private void setTargetPoint(Point p) { 4630 mTargetPoint = p; 4631 } 4632 4633 /** Update the minimum roundness based on current state */ updateBaseRoundness()4634 private void updateBaseRoundness() { 4635 if (isChildInGroup()) { 4636 requestRoundnessReset(BASE_VALUE); 4637 } else { 4638 requestRoundness(mSmallRoundness, mSmallRoundness, BASE_VALUE); 4639 } 4640 } 4641 4642 @Override onAttachedToWindow()4643 protected void onAttachedToWindow() { 4644 super.onAttachedToWindow(); 4645 updateBaseRoundness(); 4646 } 4647 4648 /** Set whether this notification may show a snooze action. */ setShowSnooze(boolean showSnooze)4649 public void setShowSnooze(boolean showSnooze) { 4650 mShowSnooze = showSnooze; 4651 } 4652 4653 /** Whether this notification may show a snooze action. */ getShowSnooze()4654 public boolean getShowSnooze() { 4655 return mShowSnooze; 4656 } 4657 4658 @Override removeFromTransientContainer()4659 public void removeFromTransientContainer() { 4660 final ViewGroup transientContainer = getTransientContainer(); 4661 final ViewParent parent = getParent(); 4662 // Only log when there is real removal of transient views 4663 if (transientContainer == null || transientContainer != parent) { 4664 super.removeFromTransientContainer(); 4665 return; 4666 } 4667 logRemoveFromTransientContainer(transientContainer); 4668 super.removeFromTransientContainer(); 4669 } 4670 4671 /** 4672 * Log calls to removeFromTransientContainer when the container is NotificationChildrenContainer 4673 * or NotificationStackScrollLayout. 4674 */ logRemoveFromTransientContainer(ViewGroup transientContainer)4675 public void logRemoveFromTransientContainer(ViewGroup transientContainer) { 4676 if (mLogger == null) { 4677 return; 4678 } 4679 if (transientContainer instanceof NotificationChildrenContainer) { 4680 mLogger.logRemoveTransientFromContainer( 4681 /* childEntry = */ mLoggingKey, 4682 /* containerEntry = */ ((NotificationChildrenContainer) transientContainer) 4683 .getContainingNotification().getLoggingKey() 4684 ); 4685 } else if (transientContainer instanceof NotificationStackScrollLayout) { 4686 mLogger.logRemoveTransientFromNssl( 4687 /* childEntry = */ mLoggingKey 4688 ); 4689 } else { 4690 mLogger.logRemoveTransientFromViewGroup( 4691 /* childEntry = */ mLoggingKey, 4692 /* containerView = */ transientContainer 4693 ); 4694 } 4695 } 4696 4697 @Override addTransientView(View view, int index)4698 public void addTransientView(View view, int index) { 4699 if (view instanceof ExpandableNotificationRow) { 4700 logAddTransientRow((ExpandableNotificationRow) view, index); 4701 } 4702 super.addTransientView(view, index); 4703 } 4704 logAddTransientRow(ExpandableNotificationRow row, int index)4705 private void logAddTransientRow(ExpandableNotificationRow row, int index) { 4706 if (mLogger == null) { 4707 return; 4708 } 4709 mLogger.logAddTransientRow(row.getLoggingKey(), mLoggingKey, index); 4710 } 4711 4712 @Override removeTransientView(View view)4713 public void removeTransientView(View view) { 4714 if (view instanceof ExpandableNotificationRow) { 4715 logRemoveTransientRow((ExpandableNotificationRow) view); 4716 } 4717 super.removeTransientView(view); 4718 } 4719 logRemoveTransientRow(ExpandableNotificationRow row)4720 private void logRemoveTransientRow(ExpandableNotificationRow row) { 4721 if (mLogger == null) { 4722 return; 4723 } 4724 mLogger.logRemoveTransientRow(row.getLoggingKey(), mLoggingKey); 4725 } 4726 4727 /** Set whether this notification is currently used to animate a launch. */ setLaunchAnimationRunning(boolean launchAnimationRunning)4728 public void setLaunchAnimationRunning(boolean launchAnimationRunning) { 4729 if (NotificationBundleUi.isEnabled()) { 4730 mLaunchAnimationRunning = launchAnimationRunning; 4731 } else { 4732 getEntryLegacy().setExpandAnimationRunning(launchAnimationRunning); 4733 } 4734 } 4735 4736 /** Whether this notification is currently used to animate a launch. */ isLaunchAnimationRunning()4737 public boolean isLaunchAnimationRunning() { 4738 if (NotificationBundleUi.isEnabled()) { 4739 return mLaunchAnimationRunning; 4740 } else { 4741 return getEntryLegacy().isExpandAnimationRunning(); 4742 } 4743 } 4744 4745 @Override usesTransparentBackground()4746 protected boolean usesTransparentBackground() { 4747 // Row background should be opaque when it's displayed as a heads-up notification or 4748 // displayed on keyguard. 4749 return super.usesTransparentBackground() && !mIsHeadsUp && !mOnKeyguard; 4750 } 4751 } 4752