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