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