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