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