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