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