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