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