1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.notification.stack; 18 19 import static android.service.notification.NotificationStats.DISMISSAL_SHADE; 20 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; 21 22 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; 23 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; 24 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; 25 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener; 26 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener; 27 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; 28 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; 29 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY; 30 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows; 31 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; 32 33 import android.content.res.Configuration; 34 import android.content.res.Resources; 35 import android.graphics.Point; 36 import android.os.Trace; 37 import android.os.UserHandle; 38 import android.provider.Settings; 39 import android.service.notification.StatusBarNotification; 40 import android.util.Log; 41 import android.util.Pair; 42 import android.view.Display; 43 import android.view.MotionEvent; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.view.WindowInsets; 47 48 import androidx.annotation.NonNull; 49 import androidx.annotation.Nullable; 50 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.internal.jank.InteractionJankMonitor; 53 import com.android.internal.logging.MetricsLogger; 54 import com.android.internal.logging.UiEvent; 55 import com.android.internal.logging.UiEventLogger; 56 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 57 import com.android.systemui.ExpandHelper; 58 import com.android.systemui.Gefingerpoken; 59 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; 60 import com.android.systemui.classifier.Classifier; 61 import com.android.systemui.classifier.FalsingCollector; 62 import com.android.systemui.dagger.SysUISingleton; 63 import com.android.systemui.dagger.qualifiers.Main; 64 import com.android.systemui.dump.DumpManager; 65 import com.android.systemui.flags.FeatureFlags; 66 import com.android.systemui.flags.Flags; 67 import com.android.systemui.flags.ViewRefactorFlag; 68 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; 69 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; 70 import com.android.systemui.keyguard.shared.model.KeyguardState; 71 import com.android.systemui.keyguard.shared.model.TransitionStep; 72 import com.android.systemui.media.controls.ui.KeyguardMediaController; 73 import com.android.systemui.plugins.ActivityStarter; 74 import com.android.systemui.plugins.FalsingManager; 75 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 76 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; 77 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; 78 import com.android.systemui.plugins.statusbar.StatusBarStateController; 79 import com.android.systemui.shade.ShadeController; 80 import com.android.systemui.shade.ShadeViewController; 81 import com.android.systemui.statusbar.LockscreenShadeTransitionController; 82 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 83 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; 84 import com.android.systemui.statusbar.NotificationRemoteInputManager; 85 import com.android.systemui.statusbar.NotificationShelf; 86 import com.android.systemui.statusbar.NotificationShelfController; 87 import com.android.systemui.statusbar.RemoteInputController; 88 import com.android.systemui.statusbar.StatusBarState; 89 import com.android.systemui.statusbar.SysuiStatusBarStateController; 90 import com.android.systemui.statusbar.notification.DynamicPrivacyController; 91 import com.android.systemui.statusbar.notification.LaunchAnimationParameters; 92 import com.android.systemui.statusbar.notification.NotifPipelineFlags; 93 import com.android.systemui.statusbar.notification.NotificationActivityStarter; 94 import com.android.systemui.statusbar.notification.collection.NotifCollection; 95 import com.android.systemui.statusbar.notification.collection.NotifPipeline; 96 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 97 import com.android.systemui.statusbar.notification.collection.PipelineDumpable; 98 import com.android.systemui.statusbar.notification.collection.PipelineDumper; 99 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; 100 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; 101 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; 102 import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProvider; 103 import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator; 104 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; 105 import com.android.systemui.statusbar.notification.collection.render.NotifStackController; 106 import com.android.systemui.statusbar.notification.collection.render.NotifStats; 107 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; 108 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; 109 import com.android.systemui.statusbar.notification.dagger.SilentHeader; 110 import com.android.systemui.statusbar.notification.init.NotificationsController; 111 import com.android.systemui.statusbar.notification.logging.NotificationLogger; 112 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; 113 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 114 import com.android.systemui.statusbar.notification.row.ExpandableView; 115 import com.android.systemui.statusbar.notification.row.NotificationGuts; 116 import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 117 import com.android.systemui.statusbar.notification.row.NotificationSnooze; 118 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; 119 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; 120 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; 121 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; 122 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; 123 import com.android.systemui.statusbar.phone.KeyguardBypassController; 124 import com.android.systemui.statusbar.phone.NotificationIconAreaController; 125 import com.android.systemui.statusbar.phone.ScrimController; 126 import com.android.systemui.statusbar.policy.ConfigurationController; 127 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; 128 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 129 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; 130 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; 131 import com.android.systemui.statusbar.policy.ZenModeController; 132 import com.android.systemui.tuner.TunerService; 133 import com.android.systemui.util.Compile; 134 import com.android.systemui.util.settings.SecureSettings; 135 136 import java.util.ArrayList; 137 import java.util.List; 138 import java.util.Optional; 139 import java.util.function.BiConsumer; 140 import java.util.function.Consumer; 141 142 import javax.inject.Inject; 143 import javax.inject.Named; 144 145 /** 146 * Controller for {@link NotificationStackScrollLayout}. 147 */ 148 @SysUISingleton 149 public class NotificationStackScrollLayoutController { 150 private static final String TAG = "StackScrollerController"; 151 private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); 152 private static final String HIGH_PRIORITY = "high_priority"; 153 154 private final boolean mAllowLongPress; 155 private final NotificationGutsManager mNotificationGutsManager; 156 private final NotificationsController mNotificationsController; 157 private final NotificationVisibilityProvider mVisibilityProvider; 158 private final HeadsUpManagerPhone mHeadsUpManager; 159 private final NotificationRoundnessManager mNotificationRoundnessManager; 160 private final TunerService mTunerService; 161 private final DeviceProvisionedController mDeviceProvisionedController; 162 private final DynamicPrivacyController mDynamicPrivacyController; 163 private final ConfigurationController mConfigurationController; 164 private final ZenModeController mZenModeController; 165 private final MetricsLogger mMetricsLogger; 166 private final Optional<NotificationListViewModel> mViewModel; 167 168 private final DumpManager mDumpManager; 169 private final FalsingCollector mFalsingCollector; 170 private final FalsingManager mFalsingManager; 171 private final Resources mResources; 172 private final NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder; 173 private final ScrimController mScrimController; 174 private final NotifPipeline mNotifPipeline; 175 private final NotifCollection mNotifCollection; 176 private final UiEventLogger mUiEventLogger; 177 private final NotificationRemoteInputManager mRemoteInputManager; 178 private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; 179 private final ShadeController mShadeController; 180 private final KeyguardMediaController mKeyguardMediaController; 181 private final SysuiStatusBarStateController mStatusBarStateController; 182 private final KeyguardBypassController mKeyguardBypassController; 183 private final KeyguardInteractor mKeyguardInteractor; 184 private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; 185 private final NotificationLockscreenUserManager mLockscreenUserManager; 186 private final SectionHeaderController mSilentHeaderController; 187 private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; 188 private final InteractionJankMonitor mJankMonitor; 189 private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; 190 private final StackStateLogger mStackStateLogger; 191 private final NotificationStackScrollLogger mLogger; 192 private final NotificationIconAreaController mNotifIconAreaController; 193 194 private final GroupExpansionManager mGroupExpansionManager; 195 private final NotifPipelineFlags mNotifPipelineFlags; 196 private final SeenNotificationsProvider mSeenNotificationsProvider; 197 private final KeyguardTransitionRepository mKeyguardTransitionRepo; 198 199 private NotificationStackScrollLayout mView; 200 private NotificationSwipeHelper mSwipeHelper; 201 @Nullable 202 private Boolean mHistoryEnabled; 203 private int mBarState; 204 private boolean mIsBouncerShowingFromCentralSurfaces; 205 private HeadsUpAppearanceController mHeadsUpAppearanceController; 206 private boolean mIsInTransitionToAod = false; 207 208 private final FeatureFlags mFeatureFlags; 209 private final ViewRefactorFlag mShelfRefactor; 210 private final NotificationTargetsHelper mNotificationTargetsHelper; 211 private final SecureSettings mSecureSettings; 212 private final NotificationDismissibilityProvider mDismissibilityProvider; 213 private final ActivityStarter mActivityStarter; 214 215 private View mLongPressedView; 216 217 private final NotificationListContainerImpl mNotificationListContainer = 218 new NotificationListContainerImpl(); 219 private final NotifStackController mNotifStackController = 220 new NotifStackControllerImpl(); 221 222 @Nullable 223 private NotificationActivityStarter mNotificationActivityStarter; 224 225 @VisibleForTesting 226 final View.OnAttachStateChangeListener mOnAttachStateChangeListener = 227 new View.OnAttachStateChangeListener() { 228 @Override 229 public void onViewAttachedToWindow(View v) { 230 mConfigurationController.addCallback(mConfigurationListener); 231 mZenModeController.addCallback(mZenModeControllerCallback); 232 final int newBarState = mStatusBarStateController.getState(); 233 if (newBarState != mBarState) { 234 mStateListener.onStateChanged(newBarState); 235 mStateListener.onStatePostChange(); 236 } 237 mStatusBarStateController.addCallback( 238 mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER); 239 } 240 241 @Override 242 public void onViewDetachedFromWindow(View v) { 243 mConfigurationController.removeCallback(mConfigurationListener); 244 mZenModeController.removeCallback(mZenModeControllerCallback); 245 mStatusBarStateController.removeCallback(mStateListener); 246 } 247 }; 248 249 private final DeviceProvisionedListener mDeviceProvisionedListener = 250 new DeviceProvisionedListener() { 251 @Override 252 public void onDeviceProvisionedChanged() { 253 updateCurrentUserIsSetup(); 254 } 255 256 @Override 257 public void onUserSwitched() { 258 updateCurrentUserIsSetup(); 259 } 260 261 @Override 262 public void onUserSetupChanged() { 263 updateCurrentUserIsSetup(); 264 } 265 266 private void updateCurrentUserIsSetup() { 267 mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup()); 268 } 269 }; 270 271 private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> { 272 if (mView.isExpanded()) { 273 // The bottom might change because we're using the final actual height of the view 274 mView.setAnimateBottomOnLayout(true); 275 } 276 // Let's update the footer once the notifications have been updated (in the next frame) 277 mView.post(this::updateFooter); 278 }; 279 280 @VisibleForTesting 281 final ConfigurationListener mConfigurationListener = new ConfigurationListener() { 282 @Override 283 public void onDensityOrFontScaleChanged() { 284 updateShowEmptyShadeView(); 285 mView.reinflateViews(); 286 } 287 288 @Override 289 public void onUiModeChanged() { 290 mView.updateBgColor(); 291 mView.updateDecorViews(); 292 } 293 294 @Override 295 public void onThemeChanged() { 296 mView.updateCornerRadius(); 297 mView.updateBgColor(); 298 mView.updateDecorViews(); 299 mView.reinflateViews(); 300 updateShowEmptyShadeView(); 301 updateFooter(); 302 } 303 304 @Override 305 public void onConfigChanged(Configuration newConfig) { 306 updateResources(); 307 } 308 }; 309 310 private NotifStats mNotifStats = NotifStats.getEmpty(); 311 updateResources()312 private void updateResources() { 313 mNotificationStackSizeCalculator.updateResources(); 314 } 315 316 private final StatusBarStateController.StateListener mStateListener = 317 new StatusBarStateController.StateListener() { 318 @Override 319 public void onStatePreChange(int oldState, int newState) { 320 if (oldState == StatusBarState.SHADE_LOCKED 321 && newState == KEYGUARD) { 322 mView.requestAnimateEverything(); 323 } 324 } 325 326 @Override 327 public void onStateChanged(int newState) { 328 mBarState = newState; 329 mView.setStatusBarState(mBarState); 330 if (newState == KEYGUARD) { 331 mGroupExpansionManager.collapseGroups(); 332 } 333 } 334 335 @Override 336 public void onUpcomingStateChanged(int newState) { 337 mView.setUpcomingStatusBarState(newState); 338 } 339 340 @Override 341 public void onStatePostChange() { 342 mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(), 343 mLockscreenUserManager.isAnyProfilePublicMode()); 344 mView.onStatePostChange(mStatusBarStateController.fromShadeLocked()); 345 updateImportantForAccessibility(); 346 } 347 }; 348 349 private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() { 350 @Override 351 public void onUserChanged(int userId) { 352 mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode()); 353 mHistoryEnabled = null; 354 updateFooter(); 355 } 356 }; 357 358 /** 359 * Recalculate sensitiveness without animation; called when waking up while keyguard occluded. 360 */ updateSensitivenessForOccludedWakeup()361 public void updateSensitivenessForOccludedWakeup() { 362 mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode()); 363 } 364 365 /** 366 * Set the overexpansion of the panel to be applied to the view. 367 */ setOverExpansion(float overExpansion)368 public void setOverExpansion(float overExpansion) { 369 mView.setOverExpansion(overExpansion); 370 } 371 372 private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() { 373 @Override 374 public void onMenuClicked( 375 View view, int x, int y, NotificationMenuRowPlugin.MenuItem item) { 376 if (!mAllowLongPress) { 377 return; 378 } 379 if (view instanceof ExpandableNotificationRow) { 380 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 381 mMetricsLogger.write(row.getEntry().getSbn().getLogMaker() 382 .setCategory(MetricsEvent.ACTION_TOUCH_GEAR) 383 .setType(MetricsEvent.TYPE_ACTION) 384 ); 385 } 386 mNotificationGutsManager.openGuts(view, x, y, item); 387 } 388 389 @Override 390 public void onMenuReset(View row) { 391 View translatingParentView = mSwipeHelper.getTranslatingParentView(); 392 if (translatingParentView != null && row == translatingParentView) { 393 mSwipeHelper.clearExposedMenuView(); 394 mSwipeHelper.clearTranslatingParentView(); 395 } 396 } 397 398 @Override 399 public void onMenuShown(View row) { 400 if (row instanceof ExpandableNotificationRow) { 401 ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row; 402 mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker() 403 .setCategory(MetricsEvent.ACTION_REVEAL_GEAR) 404 .setType(MetricsEvent.TYPE_ACTION)); 405 mSwipeHelper.onMenuShown(row); 406 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 407 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 408 false /* resetMenu */); 409 410 // Check to see if we want to go directly to the notification guts 411 NotificationMenuRowPlugin provider = notificationRow.getProvider(); 412 if (provider.shouldShowGutsOnSnapOpen()) { 413 NotificationMenuRowPlugin.MenuItem item = provider.menuItemToExposeOnSnap(); 414 if (item != null) { 415 Point origin = provider.getRevealAnimationOrigin(); 416 mNotificationGutsManager.openGuts(row, origin.x, origin.y, item); 417 } else { 418 Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no " 419 + "menu item in menuItemtoExposeOnSnap. Skipping."); 420 } 421 422 // Close the menu row since we went directly to the guts 423 mSwipeHelper.resetExposedMenuView(false, true); 424 } 425 } 426 } 427 }; 428 429 @VisibleForTesting 430 final NotificationSwipeHelper.NotificationCallback mNotificationCallback = 431 new NotificationSwipeHelper.NotificationCallback() { 432 433 @Override 434 public void onDismiss() { 435 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 436 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 437 false /* resetMenu */); 438 } 439 440 @Override 441 public float getTotalTranslationLength(View animView) { 442 return mView.getTotalTranslationLength(animView); 443 } 444 445 @Override 446 public void onSnooze(StatusBarNotification sbn, 447 NotificationSwipeActionHelper.SnoozeOption snoozeOption) { 448 mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); 449 } 450 451 @Override 452 public boolean shouldDismissQuickly() { 453 return mView.isExpanded() && mView.isFullyAwake(); 454 } 455 456 @Override 457 public void onDragCancelled(View v) { 458 mFalsingCollector.onNotificationStopDismissing(); 459 } 460 461 /** 462 * Handles cleanup after the given {@code view} has been fully swiped out (including 463 * re-invoking dismiss logic in case the notification has not made its way out yet). 464 */ 465 @Override 466 public void onChildDismissed(View view) { 467 if (!(view instanceof ActivatableNotificationView)) { 468 return; 469 } 470 ActivatableNotificationView row = (ActivatableNotificationView) view; 471 if (!row.isDismissed()) { 472 handleChildViewDismissed(view); 473 } 474 475 row.removeFromTransientContainer(); 476 if (row instanceof ExpandableNotificationRow) { 477 ((ExpandableNotificationRow) row).removeChildrenWithKeepInParent(); 478 } 479 } 480 481 /** 482 * Starts up notification dismiss and tells the notification, if any, to remove 483 * itself from the layout. 484 * 485 * @param view view (e.g. notification) to dismiss from the layout 486 */ 487 488 public void handleChildViewDismissed(View view) { 489 // The View needs to clean up the Swipe states, e.g. roundness. 490 mView.onSwipeEnd(); 491 if (mView.getClearAllInProgress()) { 492 return; 493 } 494 if (view instanceof ExpandableNotificationRow) { 495 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 496 if (row.isHeadsUp()) { 497 mHeadsUpManager.addSwipedOutNotification( 498 row.getEntry().getSbn().getKey()); 499 } 500 row.performDismiss(false /* fromAccessibility */); 501 } 502 503 mView.addSwipedOutView(view); 504 mFalsingCollector.onNotificationDismissed(); 505 if (mFalsingCollector.shouldEnforceBouncer()) { 506 mActivityStarter.executeRunnableDismissingKeyguard( 507 null, 508 null /* cancelAction */, 509 false /* dismissShade */, 510 true /* afterKeyguardGone */, 511 false /* deferred */); 512 } 513 } 514 515 @Override 516 public boolean isAntiFalsingNeeded() { 517 return mView.onKeyguard(); 518 } 519 520 @Override 521 public View getChildAtPosition(MotionEvent ev) { 522 View child = mView.getChildAtPosition( 523 ev.getX(), 524 ev.getY(), 525 true /* requireMinHeight */, 526 false /* ignoreDecors */); 527 if (child instanceof ExpandableNotificationRow) { 528 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 529 ExpandableNotificationRow parent = row.getNotificationParent(); 530 if (parent != null && parent.areChildrenExpanded() 531 && (parent.areGutsExposed() 532 || mSwipeHelper.getExposedMenuView() == parent 533 || (parent.getAttachedChildren().size() == 1 534 && mDismissibilityProvider.isDismissable(parent.getEntry())))) { 535 // In this case the group is expanded and showing the menu for the 536 // group, further interaction should apply to the group, not any 537 // child notifications so we use the parent of the child. We also do the 538 // same if we only have a single child. 539 child = parent; 540 } 541 } 542 return child; 543 } 544 545 @Override 546 public void onLongPressSent(View v) { 547 mLongPressedView = v; 548 } 549 550 @Override 551 public void onBeginDrag(View v) { 552 mFalsingCollector.onNotificationStartDismissing(); 553 mView.onSwipeBegin(v); 554 } 555 556 @Override 557 public void onChildSnappedBack(View animView, float targetLeft) { 558 mView.onSwipeEnd(); 559 if (animView instanceof ExpandableNotificationRow) { 560 ExpandableNotificationRow row = (ExpandableNotificationRow) animView; 561 if (row.isPinned() && !canChildBeDismissed(row) 562 && row.getEntry().getSbn().getNotification().fullScreenIntent 563 == null) { 564 mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(), 565 true /* removeImmediately */); 566 } 567 } 568 } 569 570 @Override 571 public boolean updateSwipeProgress(View animView, boolean dismissable, 572 float swipeProgress) { 573 // Returning true prevents alpha fading. 574 return false; 575 } 576 577 @Override 578 public float getFalsingThresholdFactor() { 579 return ShadeViewController.getFalsingThresholdFactor( 580 mKeyguardInteractor.getWakefulnessModel().getValue()); 581 } 582 583 @Override 584 public int getConstrainSwipeStartPosition() { 585 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); 586 if (menuRow != null) { 587 return Math.abs(menuRow.getMenuSnapTarget()); 588 } 589 return 0; 590 } 591 592 @Override 593 public boolean canChildBeDismissed(View v) { 594 return NotificationStackScrollLayout.canChildBeDismissed(v); 595 } 596 597 @Override 598 public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) { 599 //TODO: b/131242807 for why this doesn't do anything with direction 600 return canChildBeDismissed(v); 601 } 602 }; 603 604 private final OnHeadsUpChangedListener mOnHeadsUpChangedListener = 605 new OnHeadsUpChangedListener() { 606 @Override 607 public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { 608 mView.setInHeadsUpPinnedMode(inPinnedMode); 609 } 610 611 @Override 612 public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { 613 long numEntries = mHeadsUpManager.getAllEntries().count(); 614 NotificationEntry topEntry = mHeadsUpManager.getTopEntry(); 615 mView.setNumHeadsUp(numEntries); 616 mView.setTopHeadsUpEntry(topEntry); 617 generateHeadsUpAnimation(entry, isHeadsUp); 618 } 619 }; 620 621 private final ZenModeController.Callback mZenModeControllerCallback = 622 new ZenModeController.Callback() { 623 @Override 624 public void onZenChanged(int zen) { 625 updateShowEmptyShadeView(); 626 } 627 }; 628 629 @Inject NotificationStackScrollLayoutController( NotificationStackScrollLayout view, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, NotificationGutsManager notificationGutsManager, NotificationsController notificationsController, NotificationVisibilityProvider visibilityProvider, HeadsUpManagerPhone headsUpManager, NotificationRoundnessManager notificationRoundnessManager, TunerService tunerService, DeviceProvisionedController deviceProvisionedController, DynamicPrivacyController dynamicPrivacyController, ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, KeyguardMediaController keyguardMediaController, KeyguardBypassController keyguardBypassController, KeyguardInteractor keyguardInteractor, PrimaryBouncerInteractor primaryBouncerInteractor, KeyguardTransitionRepository keyguardTransitionRepo, ZenModeController zenModeController, NotificationLockscreenUserManager lockscreenUserManager, Optional<NotificationListViewModel> nsslViewModel, MetricsLogger metricsLogger, DumpManager dumpManager, FalsingCollector falsingCollector, FalsingManager falsingManager, @Main Resources resources, NotificationSwipeHelper.Builder notificationSwipeHelperBuilder, ScrimController scrimController, GroupExpansionManager groupManager, @SilentHeader SectionHeaderController silentHeaderController, NotifPipeline notifPipeline, NotifPipelineFlags notifPipelineFlags, NotifCollection notifCollection, LockscreenShadeTransitionController lockscreenShadeTransitionController, UiEventLogger uiEventLogger, NotificationRemoteInputManager remoteInputManager, VisibilityLocationProviderDelegator visibilityLocationProviderDelegator, SeenNotificationsProvider seenNotificationsProvider, ShadeController shadeController, InteractionJankMonitor jankMonitor, StackStateLogger stackLogger, NotificationStackScrollLogger logger, NotificationStackSizeCalculator notificationStackSizeCalculator, NotificationIconAreaController notifIconAreaController, FeatureFlags featureFlags, NotificationTargetsHelper notificationTargetsHelper, SecureSettings secureSettings, NotificationDismissibilityProvider dismissibilityProvider, ActivityStarter activityStarter)630 public NotificationStackScrollLayoutController( 631 NotificationStackScrollLayout view, 632 @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, 633 NotificationGutsManager notificationGutsManager, 634 NotificationsController notificationsController, 635 NotificationVisibilityProvider visibilityProvider, 636 HeadsUpManagerPhone headsUpManager, 637 NotificationRoundnessManager notificationRoundnessManager, 638 TunerService tunerService, 639 DeviceProvisionedController deviceProvisionedController, 640 DynamicPrivacyController dynamicPrivacyController, 641 ConfigurationController configurationController, 642 SysuiStatusBarStateController statusBarStateController, 643 KeyguardMediaController keyguardMediaController, 644 KeyguardBypassController keyguardBypassController, 645 KeyguardInteractor keyguardInteractor, 646 PrimaryBouncerInteractor primaryBouncerInteractor, 647 KeyguardTransitionRepository keyguardTransitionRepo, 648 ZenModeController zenModeController, 649 NotificationLockscreenUserManager lockscreenUserManager, 650 Optional<NotificationListViewModel> nsslViewModel, 651 MetricsLogger metricsLogger, 652 DumpManager dumpManager, 653 FalsingCollector falsingCollector, 654 FalsingManager falsingManager, 655 @Main Resources resources, 656 NotificationSwipeHelper.Builder notificationSwipeHelperBuilder, 657 ScrimController scrimController, 658 GroupExpansionManager groupManager, 659 @SilentHeader SectionHeaderController silentHeaderController, 660 NotifPipeline notifPipeline, 661 NotifPipelineFlags notifPipelineFlags, 662 NotifCollection notifCollection, 663 LockscreenShadeTransitionController lockscreenShadeTransitionController, 664 UiEventLogger uiEventLogger, 665 NotificationRemoteInputManager remoteInputManager, 666 VisibilityLocationProviderDelegator visibilityLocationProviderDelegator, 667 SeenNotificationsProvider seenNotificationsProvider, 668 ShadeController shadeController, 669 InteractionJankMonitor jankMonitor, 670 StackStateLogger stackLogger, 671 NotificationStackScrollLogger logger, 672 NotificationStackSizeCalculator notificationStackSizeCalculator, 673 NotificationIconAreaController notifIconAreaController, 674 FeatureFlags featureFlags, 675 NotificationTargetsHelper notificationTargetsHelper, 676 SecureSettings secureSettings, 677 NotificationDismissibilityProvider dismissibilityProvider, 678 ActivityStarter activityStarter) { 679 mView = view; 680 mKeyguardTransitionRepo = keyguardTransitionRepo; 681 mStackStateLogger = stackLogger; 682 mLogger = logger; 683 mAllowLongPress = allowLongPress; 684 mNotificationGutsManager = notificationGutsManager; 685 mNotificationsController = notificationsController; 686 mVisibilityProvider = visibilityProvider; 687 mHeadsUpManager = headsUpManager; 688 mNotificationRoundnessManager = notificationRoundnessManager; 689 mTunerService = tunerService; 690 mDeviceProvisionedController = deviceProvisionedController; 691 mDynamicPrivacyController = dynamicPrivacyController; 692 mConfigurationController = configurationController; 693 mStatusBarStateController = statusBarStateController; 694 mKeyguardMediaController = keyguardMediaController; 695 mKeyguardBypassController = keyguardBypassController; 696 mKeyguardInteractor = keyguardInteractor; 697 mPrimaryBouncerInteractor = primaryBouncerInteractor; 698 mZenModeController = zenModeController; 699 mLockscreenUserManager = lockscreenUserManager; 700 mViewModel = nsslViewModel; 701 mMetricsLogger = metricsLogger; 702 mDumpManager = dumpManager; 703 mLockscreenShadeTransitionController = lockscreenShadeTransitionController; 704 mFalsingCollector = falsingCollector; 705 mFalsingManager = falsingManager; 706 mResources = resources; 707 mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder; 708 mScrimController = scrimController; 709 mJankMonitor = jankMonitor; 710 mNotificationStackSizeCalculator = notificationStackSizeCalculator; 711 mGroupExpansionManager = groupManager; 712 mSilentHeaderController = silentHeaderController; 713 mNotifPipeline = notifPipeline; 714 mNotifPipelineFlags = notifPipelineFlags; 715 mNotifCollection = notifCollection; 716 mUiEventLogger = uiEventLogger; 717 mRemoteInputManager = remoteInputManager; 718 mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator; 719 mSeenNotificationsProvider = seenNotificationsProvider; 720 mShadeController = shadeController; 721 mNotifIconAreaController = notifIconAreaController; 722 mFeatureFlags = featureFlags; 723 mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); 724 mNotificationTargetsHelper = notificationTargetsHelper; 725 mSecureSettings = secureSettings; 726 mDismissibilityProvider = dismissibilityProvider; 727 mActivityStarter = activityStarter; 728 updateResources(); 729 setUpView(); 730 } 731 setUpView()732 private void setUpView() { 733 mView.setLogger(mStackStateLogger); 734 mView.setController(this); 735 mView.setLogger(mLogger); 736 mView.setTouchHandler(new TouchHandler()); 737 mView.setNotificationsController(mNotificationsController); 738 mView.setActivityStarter(mActivityStarter); 739 mView.setClearAllAnimationListener(this::onAnimationEnd); 740 mView.setClearAllListener((selection) -> mUiEventLogger.log( 741 NotificationPanelEvent.fromSelection(selection))); 742 mView.setFooterClearAllListener(() -> 743 mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); 744 mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive()); 745 mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() { 746 @Override 747 public void onRemoteInputActive(boolean active) { 748 mView.setIsRemoteInputActive(active); 749 } 750 }); 751 mView.setShadeController(mShadeController); 752 mDumpManager.registerDumpable(mView); 753 754 mKeyguardBypassController.registerOnBypassStateChangedListener( 755 isEnabled -> mNotificationRoundnessManager.setShouldRoundPulsingViews(!isEnabled)); 756 mNotificationRoundnessManager.setShouldRoundPulsingViews( 757 !mKeyguardBypassController.getBypassEnabled()); 758 759 mSwipeHelper = mNotificationSwipeHelperBuilder 760 .setNotificationCallback(mNotificationCallback) 761 .setOnMenuEventListener(mMenuEventListener) 762 .build(); 763 764 mNotifPipeline.addCollectionListener(new NotifCollectionListener() { 765 @Override 766 public void onEntryUpdated(NotificationEntry entry) { 767 mView.onEntryUpdated(entry); 768 } 769 }); 770 771 mView.initView(mView.getContext(), mSwipeHelper, mNotificationStackSizeCalculator); 772 mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled()); 773 mKeyguardBypassController 774 .registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled); 775 mView.setManageButtonClickListener(v -> { 776 if (mNotificationActivityStarter != null) { 777 mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown()); 778 } 779 }); 780 781 mHeadsUpManager.addListener(mOnHeadsUpChangedListener); 782 mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed); 783 mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener); 784 785 mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming); 786 787 mLockscreenShadeTransitionController.setStackScroller(this); 788 789 mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); 790 791 mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation); 792 793 mTunerService.addTunable( 794 (key, newValue) -> { 795 switch (key) { 796 case Settings.Secure.NOTIFICATION_HISTORY_ENABLED: 797 mHistoryEnabled = null; // invalidate 798 updateFooter(); 799 break; 800 case HIGH_PRIORITY: 801 mView.setHighPriorityBeforeSpeedBump("1".equals(newValue)); 802 break; 803 } 804 }, 805 HIGH_PRIORITY, 806 Settings.Secure.NOTIFICATION_HISTORY_ENABLED); 807 808 mKeyguardMediaController.setVisibilityChangedListener(visible -> { 809 if (visible) { 810 mView.generateAddAnimation( 811 mKeyguardMediaController.getSinglePaneContainer(), 812 false /*fromMoreCard */); 813 } else { 814 mView.generateRemoveAnimation(mKeyguardMediaController.getSinglePaneContainer()); 815 } 816 mView.requestChildrenUpdate(); 817 return kotlin.Unit.INSTANCE; 818 }); 819 820 // attach callback, and then call it to update mView immediately 821 mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); 822 mDeviceProvisionedListener.onDeviceProvisionedChanged(); 823 824 if (mView.isAttachedToWindow()) { 825 mOnAttachStateChangeListener.onViewAttachedToWindow(mView); 826 } 827 mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener); 828 mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications()); 829 830 mGroupExpansionManager.registerGroupExpansionChangeListener( 831 (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded)); 832 833 mViewModel.ifPresent( 834 vm -> NotificationListViewBinder 835 .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController)); 836 837 collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), 838 this::onKeyguardTransitionChanged); 839 } 840 isInVisibleLocation(NotificationEntry entry)841 private boolean isInVisibleLocation(NotificationEntry entry) { 842 ExpandableNotificationRow row = entry.getRow(); 843 if (row == null) { 844 return false; 845 } 846 847 ExpandableViewState childViewState = row.getViewState(); 848 if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) { 849 return false; 850 } 851 852 return row.getVisibility() == View.VISIBLE; 853 } 854 isViewAffectedBySwipe(ExpandableView expandableView)855 public boolean isViewAffectedBySwipe(ExpandableView expandableView) { 856 return mNotificationRoundnessManager.isViewAffectedBySwipe(expandableView); 857 } 858 addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener)859 public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { 860 mView.addOnExpandedHeightChangedListener(listener); 861 } 862 removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener)863 public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { 864 mView.removeOnExpandedHeightChangedListener(listener); 865 } 866 addOnLayoutChangeListener(View.OnLayoutChangeListener listener)867 public void addOnLayoutChangeListener(View.OnLayoutChangeListener listener) { 868 mView.addOnLayoutChangeListener(listener); 869 } 870 removeOnLayoutChangeListener(View.OnLayoutChangeListener listener)871 public void removeOnLayoutChangeListener(View.OnLayoutChangeListener listener) { 872 mView.removeOnLayoutChangeListener(listener); 873 } 874 setHeadsUpAppearanceController(HeadsUpAppearanceController controller)875 public void setHeadsUpAppearanceController(HeadsUpAppearanceController controller) { 876 mHeadsUpAppearanceController = controller; 877 mView.setHeadsUpAppearanceController(controller); 878 } 879 getAppearFraction()880 public float getAppearFraction() { 881 return mView.getAppearFraction(); 882 } 883 getExpandedHeight()884 public float getExpandedHeight() { 885 return mView.getExpandedHeight(); 886 } 887 requestLayout()888 public void requestLayout() { 889 mView.requestLayout(); 890 } 891 getDisplay()892 public Display getDisplay() { 893 return mView.getDisplay(); 894 } 895 getRootWindowInsets()896 public WindowInsets getRootWindowInsets() { 897 return mView.getRootWindowInsets(); 898 } 899 getRight()900 public int getRight() { 901 return mView.getRight(); 902 } 903 isLayoutRtl()904 public boolean isLayoutRtl() { 905 return mView.isLayoutRtl(); 906 } 907 908 /** 909 * @return the left of the view. 910 */ getLeft()911 public int getLeft() { 912 return mView.getLeft(); 913 } 914 915 /** 916 * @return the top of the view. 917 */ getTop()918 public int getTop() { 919 return mView.getTop(); 920 } 921 922 /** 923 * @return the bottom of the view. 924 */ getBottom()925 public int getBottom() { 926 return mView.getBottom(); 927 } 928 getTranslationX()929 public float getTranslationX() { 930 return mView.getTranslationX(); 931 } 932 933 /** Set view y-translation */ setTranslationY(float translationY)934 public void setTranslationY(float translationY) { 935 mView.setTranslationY(translationY); 936 } 937 indexOfChild(View view)938 public int indexOfChild(View view) { 939 return mView.indexOfChild(view); 940 } 941 setOnHeightChangedListener( ExpandableView.OnHeightChangedListener listener)942 public void setOnHeightChangedListener( 943 ExpandableView.OnHeightChangedListener listener) { 944 mView.setOnHeightChangedListener(listener); 945 } 946 setOverscrollTopChangedListener( OnOverscrollTopChangedListener listener)947 public void setOverscrollTopChangedListener( 948 OnOverscrollTopChangedListener listener) { 949 mView.setOverscrollTopChangedListener(listener); 950 } 951 setOnEmptySpaceClickListener( OnEmptySpaceClickListener listener)952 public void setOnEmptySpaceClickListener( 953 OnEmptySpaceClickListener listener) { 954 mView.setOnEmptySpaceClickListener(listener); 955 } 956 setTrackingHeadsUp(ExpandableNotificationRow expandableNotificationRow)957 public void setTrackingHeadsUp(ExpandableNotificationRow expandableNotificationRow) { 958 mView.setTrackingHeadsUp(expandableNotificationRow); 959 } 960 wakeUpFromPulse()961 public void wakeUpFromPulse() { 962 mView.wakeUpFromPulse(); 963 } 964 isPulseExpanding()965 public boolean isPulseExpanding() { 966 return mView.isPulseExpanding(); 967 } 968 setOnPulseHeightChangedListener(Runnable listener)969 public void setOnPulseHeightChangedListener(Runnable listener) { 970 mView.setOnPulseHeightChangedListener(listener); 971 } 972 setDozeAmount(float amount)973 public void setDozeAmount(float amount) { 974 mView.setDozeAmount(amount); 975 } 976 getSpeedBumpIndex()977 public int getSpeedBumpIndex() { 978 return mView.getSpeedBumpIndex(); 979 } 980 setHideAmount(float linearAmount, float amount)981 public void setHideAmount(float linearAmount, float amount) { 982 mView.setHideAmount(linearAmount, amount); 983 } 984 notifyHideAnimationStart(boolean hide)985 public void notifyHideAnimationStart(boolean hide) { 986 mView.notifyHideAnimationStart(hide); 987 } 988 setPulseHeight(float height)989 public float setPulseHeight(float height) { 990 return mView.setPulseHeight(height); 991 } 992 getLocationOnScreen(int[] outLocation)993 public void getLocationOnScreen(int[] outLocation) { 994 mView.getLocationOnScreen(outLocation); 995 } 996 getChildAtRawPosition(float x, float y)997 public ExpandableView getChildAtRawPosition(float x, float y) { 998 return mView.getChildAtRawPosition(x, y); 999 } 1000 getLayoutParams()1001 public ViewGroup.LayoutParams getLayoutParams() { 1002 return mView.getLayoutParams(); 1003 } 1004 1005 /** 1006 * Updates layout parameters on the root view 1007 */ setLayoutParams(ViewGroup.LayoutParams lp)1008 public void setLayoutParams(ViewGroup.LayoutParams lp) { 1009 mView.setLayoutParams(lp); 1010 } 1011 setIsFullWidth(boolean isFullWidth)1012 public void setIsFullWidth(boolean isFullWidth) { 1013 mView.setIsFullWidth(isFullWidth); 1014 } 1015 isAddOrRemoveAnimationPending()1016 public boolean isAddOrRemoveAnimationPending() { 1017 return mView != null && mView.isAddOrRemoveAnimationPending(); 1018 } 1019 getVisibleNotificationCount()1020 public int getVisibleNotificationCount() { 1021 return mNotifStats.getNumActiveNotifs(); 1022 } 1023 isHistoryEnabled()1024 public boolean isHistoryEnabled() { 1025 Boolean historyEnabled = mHistoryEnabled; 1026 if (historyEnabled == null) { 1027 if (mView == null || mView.getContext() == null) { 1028 Log.wtf(TAG, "isHistoryEnabled failed to initialize its value"); 1029 return false; 1030 } 1031 mHistoryEnabled = historyEnabled = mSecureSettings.getIntForUser( 1032 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1033 0, 1034 UserHandle.USER_CURRENT) == 1; 1035 } 1036 return historyEnabled; 1037 } 1038 getIntrinsicContentHeight()1039 public int getIntrinsicContentHeight() { 1040 return mView.getIntrinsicContentHeight(); 1041 } 1042 setIntrinsicPadding(int intrinsicPadding)1043 public void setIntrinsicPadding(int intrinsicPadding) { 1044 mView.setIntrinsicPadding(intrinsicPadding); 1045 } 1046 getHeight()1047 public int getHeight() { 1048 return mView.getHeight(); 1049 } 1050 getChildCount()1051 public int getChildCount() { 1052 return mView.getChildCount(); 1053 } 1054 getChildAt(int i)1055 public ExpandableView getChildAt(int i) { 1056 return (ExpandableView) mView.getChildAt(i); 1057 } 1058 goToFullShade(long delay)1059 public void goToFullShade(long delay) { 1060 mView.goToFullShade(delay); 1061 } 1062 setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators)1063 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1064 boolean cancelAnimators) { 1065 mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators); 1066 } 1067 setOverScrollAmount(float amount, boolean onTop, boolean animate)1068 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { 1069 mView.setOverScrollAmount(amount, onTop, animate); 1070 } 1071 resetScrollPosition()1072 public void resetScrollPosition() { 1073 mView.resetScrollPosition(); 1074 } 1075 setShouldShowShelfOnly(boolean shouldShowShelfOnly)1076 public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) { 1077 mView.setShouldShowShelfOnly(shouldShowShelfOnly); 1078 } 1079 cancelLongPress()1080 public void cancelLongPress() { 1081 mView.cancelLongPress(); 1082 } 1083 getX()1084 public float getX() { 1085 return mView.getX(); 1086 } 1087 isBelowLastNotification(float x, float y)1088 public boolean isBelowLastNotification(float x, float y) { 1089 return mView.isBelowLastNotification(x, y); 1090 } 1091 getWidth()1092 public float getWidth() { 1093 return mView.getWidth(); 1094 } 1095 getOpeningHeight()1096 public float getOpeningHeight() { 1097 return mView.getOpeningHeight(); 1098 } 1099 getBottomMostNotificationBottom()1100 public float getBottomMostNotificationBottom() { 1101 return mView.getBottomMostNotificationBottom(); 1102 } 1103 checkSnoozeLeavebehind()1104 public void checkSnoozeLeavebehind() { 1105 if (mView.getCheckSnoozeLeaveBehind()) { 1106 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 1107 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 1108 false /* resetMenu */); 1109 mView.setCheckForLeaveBehind(false); 1110 } 1111 } 1112 setQsFullScreen(boolean fullScreen)1113 public void setQsFullScreen(boolean fullScreen) { 1114 mView.setQsFullScreen(fullScreen); 1115 updateShowEmptyShadeView(); 1116 } 1117 setScrollingEnabled(boolean enabled)1118 public void setScrollingEnabled(boolean enabled) { 1119 mView.setScrollingEnabled(enabled); 1120 } 1121 setQsExpansionFraction(float expansionFraction)1122 public void setQsExpansionFraction(float expansionFraction) { 1123 mView.setQsExpansionFraction(expansionFraction); 1124 } 1125 setOnStackYChanged(Consumer<Boolean> onStackYChanged)1126 public void setOnStackYChanged(Consumer<Boolean> onStackYChanged) { 1127 mView.setOnStackYChanged(onStackYChanged); 1128 } 1129 getNotificationSquishinessFraction()1130 public float getNotificationSquishinessFraction() { 1131 return mView.getNotificationSquishinessFraction(); 1132 } 1133 calculateAppearFractionBypass()1134 public float calculateAppearFractionBypass() { 1135 return mView.calculateAppearFractionBypass(); 1136 } 1137 updateTopPadding(float qsHeight, boolean animate)1138 public void updateTopPadding(float qsHeight, boolean animate) { 1139 mView.updateTopPadding(qsHeight, animate); 1140 } 1141 isScrolledToBottom()1142 public boolean isScrolledToBottom() { 1143 return mView.isScrolledToBottom(); 1144 } 1145 getNotGoneChildCount()1146 public int getNotGoneChildCount() { 1147 return mView.getNotGoneChildCount(); 1148 } 1149 getIntrinsicPadding()1150 public float getIntrinsicPadding() { 1151 return mView.getIntrinsicPadding(); 1152 } 1153 getLayoutMinHeight()1154 public float getLayoutMinHeight() { 1155 return mView.getLayoutMinHeight(); 1156 } 1157 getEmptyBottomMargin()1158 public int getEmptyBottomMargin() { 1159 return mView.getEmptyBottomMargin(); 1160 } 1161 getTopPaddingOverflow()1162 public float getTopPaddingOverflow() { 1163 return mView.getTopPaddingOverflow(); 1164 } 1165 getTopPadding()1166 public int getTopPadding() { 1167 return mView.getTopPadding(); 1168 } 1169 getEmptyShadeViewHeight()1170 public float getEmptyShadeViewHeight() { 1171 return mView.getEmptyShadeViewHeight(); 1172 } 1173 setAlpha(float alpha)1174 public void setAlpha(float alpha) { 1175 if (mView != null) { 1176 mView.setAlpha(alpha); 1177 } 1178 } 1179 calculateAppearFraction(float height)1180 public float calculateAppearFraction(float height) { 1181 return mView.calculateAppearFraction(height); 1182 } 1183 onExpansionStarted()1184 public void onExpansionStarted() { 1185 mView.onExpansionStarted(); 1186 checkSnoozeLeavebehind(); 1187 } 1188 onExpansionStopped()1189 public void onExpansionStopped() { 1190 mView.setCheckForLeaveBehind(false); 1191 mView.onExpansionStopped(); 1192 } 1193 onPanelTrackingStarted()1194 public void onPanelTrackingStarted() { 1195 mView.onPanelTrackingStarted(); 1196 } 1197 onPanelTrackingStopped()1198 public void onPanelTrackingStopped() { 1199 mView.onPanelTrackingStopped(); 1200 } 1201 setHeadsUpBoundaries(int height, int bottomBarHeight)1202 public void setHeadsUpBoundaries(int height, int bottomBarHeight) { 1203 mView.setHeadsUpBoundaries(height, bottomBarHeight); 1204 } 1205 setUnlockHintRunning(boolean running)1206 public void setUnlockHintRunning(boolean running) { 1207 mView.setUnlockHintRunning(running); 1208 } 1209 setPanelFlinging(boolean flinging)1210 public void setPanelFlinging(boolean flinging) { 1211 mView.setPanelFlinging(flinging); 1212 } 1213 isFooterViewNotGone()1214 public boolean isFooterViewNotGone() { 1215 return mView.isFooterViewNotGone(); 1216 } 1217 isFooterViewContentVisible()1218 public boolean isFooterViewContentVisible() { 1219 return mView.isFooterViewContentVisible(); 1220 } 1221 getFooterViewHeightWithPadding()1222 public int getFooterViewHeightWithPadding() { 1223 return mView.getFooterViewHeightWithPadding(); 1224 } 1225 1226 /** 1227 * Sets whether the bouncer is currently showing. Should only be called from 1228 * {@link CentralSurfaces}. 1229 */ setBouncerShowingFromCentralSurfaces(boolean bouncerShowing)1230 public void setBouncerShowingFromCentralSurfaces(boolean bouncerShowing) { 1231 mIsBouncerShowingFromCentralSurfaces = bouncerShowing; 1232 } 1233 1234 /** 1235 * Set the visibility of the view, and propagate it to specific children. 1236 * 1237 * @param visible either the view is visible or not. 1238 */ updateVisibility(boolean visible)1239 public void updateVisibility(boolean visible) { 1240 mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1241 1242 if (mView.getVisibility() == View.VISIBLE) { 1243 // Synchronize EmptyShadeView visibility with the parent container. 1244 updateShowEmptyShadeView(); 1245 updateImportantForAccessibility(); 1246 } 1247 } 1248 1249 /** 1250 * Update whether we should show the empty shade view ("no notifications" in the shade). 1251 * <p> 1252 * When in split mode, notifications are always visible regardless of the state of the 1253 * QuickSettings panel. That being the case, empty view is always shown if the other conditions 1254 * are true. 1255 */ updateShowEmptyShadeView()1256 public void updateShowEmptyShadeView() { 1257 Trace.beginSection("NSSLC.updateShowEmptyShadeView"); 1258 1259 final boolean shouldShow = getVisibleNotificationCount() == 0 1260 && !mView.isQsFullScreen() 1261 // Hide empty shade view when in transition to AOD. 1262 // That avoids "No Notifications" to blink when transitioning to AOD. 1263 // For more details, see: b/228790482 1264 && !mIsInTransitionToAod 1265 // Don't show any notification content if the bouncer is showing. See b/267060171. 1266 && !isBouncerShowing(); 1267 1268 mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); 1269 1270 Trace.endSection(); 1271 } 1272 1273 /** 1274 * Returns whether the bouncer is currently showing. 1275 * 1276 * There's a possible timing difference between when CentralSurfaces marks the bouncer as not 1277 * showing and when PrimaryBouncerInteractor marks the bouncer as not showing. (CentralSurfaces 1278 * appears to mark the bouncer as showing for 10-200ms longer than PrimaryBouncerInteractor.) 1279 * 1280 * This timing difference could be load bearing, which is why we have a feature flag protecting 1281 * where we fetch the value from. This flag is intended to be short-lived. 1282 */ isBouncerShowing()1283 private boolean isBouncerShowing() { 1284 if (mFeatureFlags.isEnabled(Flags.USE_REPOS_FOR_BOUNCER_SHOWING)) { 1285 return mPrimaryBouncerInteractor.isBouncerShowing(); 1286 } else { 1287 return mIsBouncerShowingFromCentralSurfaces; 1288 } 1289 } 1290 1291 /** 1292 * Update the importantForAccessibility of NotificationStackScrollLayout. 1293 * <p> 1294 * We want the NSSL to be unimportant for accessibility when there's no 1295 * notifications in it while the device is on lock screen, to avoid unlablel NSSL view. 1296 * Otherwise, we want it to be important for accessibility to enable accessibility 1297 * auto-scrolling in NSSL. 1298 */ updateImportantForAccessibility()1299 public void updateImportantForAccessibility() { 1300 if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) { 1301 mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 1302 } else { 1303 mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 1304 } 1305 } 1306 1307 /** 1308 * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD 1309 * and false otherwise. 1310 */ isInTransitionToKeyguard()1311 private boolean isInTransitionToKeyguard() { 1312 final int currentState = mStatusBarStateController.getState(); 1313 final int upcomingState = mStatusBarStateController.getCurrentOrUpcomingState(); 1314 return (currentState != upcomingState && upcomingState == KEYGUARD); 1315 } 1316 isShowingEmptyShadeView()1317 public boolean isShowingEmptyShadeView() { 1318 return mView.isEmptyShadeViewVisible(); 1319 } 1320 setHeadsUpAnimatingAway(boolean headsUpAnimatingAway)1321 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { 1322 mView.setHeadsUpAnimatingAway(headsUpAnimatingAway); 1323 } 1324 getHeadsUpCallback()1325 public HeadsUpTouchHelper.Callback getHeadsUpCallback() { 1326 return mView.getHeadsUpCallback(); 1327 } 1328 forceNoOverlappingRendering(boolean force)1329 public void forceNoOverlappingRendering(boolean force) { 1330 mView.forceNoOverlappingRendering(force); 1331 } 1332 setExpandingVelocity(float velocity)1333 public void setExpandingVelocity(float velocity) { 1334 mView.setExpandingVelocity(velocity); 1335 } 1336 setExpandedHeight(float expandedHeight)1337 public void setExpandedHeight(float expandedHeight) { 1338 mView.setExpandedHeight(expandedHeight); 1339 } 1340 1341 /** 1342 * Sets the QS header. Used to check if a touch is within its bounds. 1343 */ setQsHeader(ViewGroup view)1344 public void setQsHeader(ViewGroup view) { 1345 mView.setQsHeader(view); 1346 } 1347 setAnimationsEnabled(boolean enabled)1348 public void setAnimationsEnabled(boolean enabled) { 1349 mView.setAnimationsEnabled(enabled); 1350 } 1351 setDozing(boolean dozing, boolean animate)1352 public void setDozing(boolean dozing, boolean animate) { 1353 mView.setDozing(dozing, animate); 1354 } 1355 setPulsing(boolean pulsing, boolean animatePulse)1356 public void setPulsing(boolean pulsing, boolean animatePulse) { 1357 mView.setPulsing(pulsing, animatePulse); 1358 } 1359 1360 /** 1361 * Return whether there are any clearable notifications 1362 */ hasActiveClearableNotifications(@electedRows int selection)1363 public boolean hasActiveClearableNotifications(@SelectedRows int selection) { 1364 return hasNotifications(selection, true /* clearable */); 1365 } 1366 hasNotifications(@electedRows int selection, boolean isClearable)1367 public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { 1368 boolean hasAlertingMatchingClearable = isClearable 1369 ? mNotifStats.getHasClearableAlertingNotifs() 1370 : mNotifStats.getHasNonClearableAlertingNotifs(); 1371 boolean hasSilentMatchingClearable = isClearable 1372 ? mNotifStats.getHasClearableSilentNotifs() 1373 : mNotifStats.getHasNonClearableSilentNotifs(); 1374 switch (selection) { 1375 case ROWS_GENTLE: 1376 return hasSilentMatchingClearable; 1377 case ROWS_HIGH_PRIORITY: 1378 return hasAlertingMatchingClearable; 1379 case ROWS_ALL: 1380 return hasSilentMatchingClearable || hasAlertingMatchingClearable; 1381 default: 1382 throw new IllegalStateException("Bad selection: " + selection); 1383 } 1384 } 1385 1386 /** 1387 * Set the maximum number of notifications that can currently be displayed 1388 */ setMaxDisplayedNotifications(int maxNotifications)1389 public void setMaxDisplayedNotifications(int maxNotifications) { 1390 mNotificationListContainer.setMaxDisplayedNotifications(maxNotifications); 1391 } 1392 1393 /** 1394 * This is used for debugging only; it will be used to draw the otherwise invisible line which 1395 * NotificationPanelViewController treats as the bottom when calculating how many notifications 1396 * appear on the keyguard. 1397 * Setting a negative number will disable rendering this line. 1398 */ setKeyguardBottomPaddingForDebug(float keyguardBottomPadding)1399 public void setKeyguardBottomPaddingForDebug(float keyguardBottomPadding) { 1400 mView.setKeyguardBottomPadding(keyguardBottomPadding); 1401 } 1402 createDelegate()1403 public RemoteInputController.Delegate createDelegate() { 1404 return new RemoteInputController.Delegate() { 1405 public void setRemoteInputActive(NotificationEntry entry, 1406 boolean remoteInputActive) { 1407 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); 1408 entry.notifyHeightChanged(true /* needsAnimation */); 1409 updateFooter(); 1410 } 1411 1412 public void lockScrollTo(NotificationEntry entry) { 1413 mView.lockScrollTo(entry.getRow()); 1414 } 1415 1416 public void requestDisallowLongPressAndDismiss() { 1417 mView.requestDisallowLongPress(); 1418 mView.requestDisallowDismiss(); 1419 } 1420 }; 1421 } 1422 1423 public void updateFooter() { 1424 Trace.beginSection("NSSLC.updateFooter"); 1425 mView.updateFooter(); 1426 Trace.endSection(); 1427 } 1428 1429 public void onUpdateRowStates() { 1430 mView.onUpdateRowStates(); 1431 } 1432 1433 public void runAfterAnimationFinished(Runnable r) { 1434 mView.runAfterAnimationFinished(r); 1435 } 1436 1437 public void setShelfController(NotificationShelfController notificationShelfController) { 1438 mShelfRefactor.assertDisabled(); 1439 mView.setShelfController(notificationShelfController); 1440 } 1441 1442 public ExpandableView getFirstChildNotGone() { 1443 return mView.getFirstChildNotGone(); 1444 } 1445 1446 private void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) { 1447 mView.generateHeadsUpAnimation(entry, isHeadsUp); 1448 } 1449 1450 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { 1451 mView.generateHeadsUpAnimation(row, isHeadsUp); 1452 } 1453 1454 public void setMaxTopPadding(int padding) { 1455 mView.setMaxTopPadding(padding); 1456 } 1457 1458 public int getTransientViewCount() { 1459 return mView.getTransientViewCount(); 1460 } 1461 1462 public View getTransientView(int i) { 1463 return mView.getTransientView(i); 1464 } 1465 1466 public int getPositionInLinearLayout(ExpandableView row) { 1467 return mView.getPositionInLinearLayout(row); 1468 } 1469 1470 public NotificationStackScrollLayout getView() { 1471 return mView; 1472 } 1473 1474 public float calculateGapHeight(ExpandableView previousView, ExpandableView child, int count) { 1475 return mView.calculateGapHeight(previousView, child, count); 1476 } 1477 1478 NotificationRoundnessManager getNotificationRoundnessManager() { 1479 return mNotificationRoundnessManager; 1480 } 1481 1482 public NotificationListContainer getNotificationListContainer() { 1483 return mNotificationListContainer; 1484 } 1485 1486 public NotifStackController getNotifStackController() { 1487 return mNotifStackController; 1488 } 1489 1490 public void resetCheckSnoozeLeavebehind() { 1491 mView.resetCheckSnoozeLeavebehind(); 1492 } 1493 1494 private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) { 1495 return new DismissedByUserStats( 1496 DISMISSAL_SHADE, 1497 DISMISS_SENTIMENT_NEUTRAL, 1498 mVisibilityProvider.obtain(entry, true)); 1499 } 1500 1501 public void closeControlsIfOutsideTouch(MotionEvent ev) { 1502 NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); 1503 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); 1504 View translatingParentView = mSwipeHelper.getTranslatingParentView(); 1505 View view = null; 1506 if (guts != null && !guts.getGutsContent().isLeavebehind()) { 1507 // Only close visible guts if they're not a leavebehind. 1508 view = guts; 1509 } else if (menuRow != null && menuRow.isMenuVisible() 1510 && translatingParentView != null) { 1511 // Checking menu 1512 view = translatingParentView; 1513 } 1514 if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) { 1515 // Touch was outside visible guts / menu notification, close what's visible 1516 mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, 1517 false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, 1518 false /* resetMenu */); 1519 mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */); 1520 } 1521 } 1522 1523 public void clearSilentNotifications() { 1524 // Leave the shade open if there will be other notifs left over to clear 1525 final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY); 1526 mView.clearNotifications(ROWS_GENTLE, closeShade); 1527 } 1528 1529 private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, 1530 @SelectedRows int selectedRows) { 1531 if (selectedRows == ROWS_ALL) { 1532 mNotifCollection.dismissAllNotifications( 1533 mLockscreenUserManager.getCurrentUserId()); 1534 } else { 1535 final List<Pair<NotificationEntry, DismissedByUserStats>> 1536 entriesWithRowsDismissedFromShade = new ArrayList<>(); 1537 for (ExpandableNotificationRow row : viewsToRemove) { 1538 final NotificationEntry entry = row.getEntry(); 1539 entriesWithRowsDismissedFromShade.add( 1540 new Pair<>(entry, getDismissedByUserStats(entry))); 1541 } 1542 mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade); 1543 } 1544 } 1545 1546 /** 1547 * @return the expand helper callback. 1548 */ 1549 public ExpandHelper.Callback getExpandHelperCallback() { 1550 return mView.getExpandHelperCallback(); 1551 } 1552 1553 /** 1554 * @return If the shade is in the locked down shade. 1555 */ 1556 public boolean isInLockedDownShade() { 1557 return mDynamicPrivacyController.isInLockedDownShade(); 1558 } 1559 1560 public boolean isLongPressInProgress() { 1561 return mLongPressedView != null; 1562 } 1563 1564 /** 1565 * Set the dimmed state for all of the notification views. 1566 */ 1567 public void setDimmed(boolean dimmed, boolean animate) { 1568 mView.setDimmed(dimmed, animate); 1569 } 1570 1571 /** 1572 * @return the inset during the full shade transition, that needs to be added to the position 1573 * of the quick settings edge. This is relevant for media, that is transitioning 1574 * from the keyguard host to the quick settings one. 1575 */ 1576 public int getFullShadeTransitionInset() { 1577 MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer(); 1578 if (view == null || view.getHeight() == 0 1579 || mStatusBarStateController.getState() != KEYGUARD) { 1580 return 0; 1581 } 1582 return view.getHeight() + mView.getPaddingAfterMedia(); 1583 } 1584 1585 /** 1586 * @param fraction The fraction of lockscreen to shade transition. 1587 * 0f for all other states. 1588 * <p> 1589 * Once the lockscreen to shade transition completes and the shade is 100% open, 1590 * LockscreenShadeTransitionController resets amount and fraction to 0, where 1591 * they remain until the next lockscreen-to-shade transition. 1592 */ 1593 public void setTransitionToFullShadeAmount(float fraction) { 1594 mView.setFractionToShade(fraction); 1595 } 1596 1597 /** 1598 * Sets the amount of vertical over scroll that should be performed on NSSL. 1599 */ 1600 public void setOverScrollAmount(int overScrollAmount) { 1601 mView.setExtraTopInsetForFullShadeTransition(overScrollAmount); 1602 } 1603 1604 /** 1605 * 1606 */ 1607 public void setWillExpand(boolean willExpand) { 1608 mView.setWillExpand(willExpand); 1609 } 1610 1611 /** 1612 * Set a listener to when scrolling changes. 1613 */ 1614 public void setOnScrollListener(Consumer<Integer> listener) { 1615 mView.setOnScrollListener(listener); 1616 } 1617 1618 /** 1619 * Set rounded rect clipping bounds on this view. 1620 */ 1621 public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, 1622 int bottomRadius) { 1623 mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius); 1624 } 1625 1626 /** 1627 * Request an animation whenever the toppadding changes next 1628 */ 1629 public void animateNextTopPaddingChange() { 1630 mView.animateNextTopPaddingChange(); 1631 } 1632 1633 public void setNotificationActivityStarter(NotificationActivityStarter activityStarter) { 1634 mNotificationActivityStarter = activityStarter; 1635 } 1636 1637 public NotificationTargetsHelper getNotificationTargetsHelper() { 1638 return mNotificationTargetsHelper; 1639 } 1640 1641 /** 1642 * Set the remove notification listener 1643 * @param listener callback for notification removed 1644 */ 1645 public void setOnNotificationRemovedListener( 1646 NotificationStackScrollLayout.OnNotificationRemovedListener listener) { 1647 mView.setOnNotificationRemovedListener(listener); 1648 } 1649 1650 public void setShelf(NotificationShelf shelf) { 1651 if (!mShelfRefactor.expectEnabled()) return; 1652 mView.setShelf(shelf); 1653 } 1654 1655 public int getShelfHeight() { 1656 if (!mShelfRefactor.expectEnabled()) { 1657 return 0; 1658 } 1659 ExpandableView shelf = mView.getShelf(); 1660 return shelf == null ? 0 : shelf.getIntrinsicHeight(); 1661 } 1662 1663 @VisibleForTesting 1664 void onKeyguardTransitionChanged(TransitionStep transitionStep) { 1665 boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD) 1666 && (transitionStep.getFrom().equals(KeyguardState.GONE) 1667 || transitionStep.getFrom().equals(KeyguardState.OCCLUDED)); 1668 if (mIsInTransitionToAod != isTransitionToAod) { 1669 mIsInTransitionToAod = isTransitionToAod; 1670 updateShowEmptyShadeView(); 1671 } 1672 } 1673 1674 /** 1675 * Enum for UiEvent logged from this class 1676 */ 1677 enum NotificationPanelEvent implements UiEventLogger.UiEventEnum { 1678 INVALID(0), 1679 @UiEvent(doc = "User dismissed all notifications from notification panel.") 1680 DISMISS_ALL_NOTIFICATIONS_PANEL(312), 1681 @UiEvent(doc = "User dismissed all silent notifications from notification panel.") 1682 DISMISS_SILENT_NOTIFICATIONS_PANEL(314); 1683 private final int mId; 1684 1685 NotificationPanelEvent(int id) { 1686 mId = id; 1687 } 1688 1689 @Override 1690 public int getId() { 1691 return mId; 1692 } 1693 1694 public static UiEventLogger.UiEventEnum fromSelection(@SelectedRows int selection) { 1695 if (selection == ROWS_ALL) { 1696 return DISMISS_ALL_NOTIFICATIONS_PANEL; 1697 } 1698 if (selection == NotificationStackScrollLayout.ROWS_GENTLE) { 1699 return DISMISS_SILENT_NOTIFICATIONS_PANEL; 1700 } 1701 if (NotificationStackScrollLayoutController.DEBUG) { 1702 throw new IllegalArgumentException("Unexpected selection" + selection); 1703 } 1704 return INVALID; 1705 } 1706 } 1707 1708 private class NotificationListContainerImpl implements NotificationListContainer, 1709 PipelineDumpable { 1710 1711 @Override 1712 public void setChildTransferInProgress(boolean childTransferInProgress) { 1713 mView.setChildTransferInProgress(childTransferInProgress); 1714 } 1715 1716 @Override 1717 public void changeViewPosition(ExpandableView child, int newIndex) { 1718 mView.changeViewPosition(child, newIndex); 1719 } 1720 1721 @Override 1722 public void notifyGroupChildAdded(ExpandableView row) { 1723 mView.notifyGroupChildAdded(row); 1724 } 1725 1726 @Override 1727 public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) { 1728 mView.notifyGroupChildRemoved(row, childrenContainer); 1729 } 1730 1731 @Override 1732 public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) { 1733 mView.generateAddAnimation(child, fromMoreCard); 1734 } 1735 1736 @Override 1737 public void generateChildOrderChangedEvent() { 1738 mView.generateChildOrderChangedEvent(); 1739 } 1740 1741 @Override 1742 public int getContainerChildCount() { 1743 return mView.getContainerChildCount(); 1744 } 1745 1746 @Override 1747 public void setNotificationActivityStarter( 1748 NotificationActivityStarter notificationActivityStarter) { 1749 NotificationStackScrollLayoutController.this 1750 .setNotificationActivityStarter(notificationActivityStarter); 1751 } 1752 1753 @Override 1754 public int getTopClippingStartLocation() { 1755 return mView.getTopClippingStartLocation(); 1756 } 1757 1758 @Override 1759 public View getContainerChildAt(int i) { 1760 return mView.getContainerChildAt(i); 1761 } 1762 1763 @Override 1764 public void removeContainerView(View v) { 1765 mView.removeContainerView(v); 1766 } 1767 1768 @Override 1769 public void addContainerView(View v) { 1770 mView.addContainerView(v); 1771 } 1772 1773 @Override 1774 public void addContainerViewAt(View v, int index) { 1775 mView.addContainerViewAt(v, index); 1776 } 1777 1778 @Override 1779 public void setMaxDisplayedNotifications(int maxNotifications) { 1780 mView.setMaxDisplayedNotifications(maxNotifications); 1781 } 1782 1783 @Override 1784 public ViewGroup getViewParentForNotification(NotificationEntry entry) { 1785 return mView.getViewParentForNotification(entry); 1786 } 1787 1788 @Override 1789 public void resetExposedMenuView(boolean animate, boolean force) { 1790 mSwipeHelper.resetExposedMenuView(animate, force); 1791 } 1792 1793 @Override 1794 public NotificationSwipeActionHelper getSwipeActionHelper() { 1795 return mSwipeHelper; 1796 } 1797 1798 @Override 1799 public void cleanUpViewStateForEntry(NotificationEntry entry) { 1800 mView.cleanUpViewStateForEntry(entry); 1801 } 1802 1803 @Override 1804 public void setChildLocationsChangedListener( 1805 NotificationLogger.OnChildLocationsChangedListener listener) { 1806 mView.setChildLocationsChangedListener(listener); 1807 } 1808 1809 public boolean hasPulsingNotifications() { 1810 return mView.hasPulsingNotifications(); 1811 } 1812 1813 @Override 1814 public boolean isInVisibleLocation(NotificationEntry entry) { 1815 return NotificationStackScrollLayoutController.this.isInVisibleLocation(entry); 1816 } 1817 1818 @Override 1819 public void onHeightChanged(ExpandableView view, boolean needsAnimation) { 1820 mView.onChildHeightChanged(view, needsAnimation); 1821 } 1822 1823 @Override 1824 public void onReset(ExpandableView view) { 1825 mView.onChildHeightReset(view); 1826 } 1827 1828 @Override 1829 public void bindRow(ExpandableNotificationRow row) { 1830 row.setHeadsUpAnimatingAwayListener(animatingAway -> { 1831 NotificationEntry entry = row.getEntry(); 1832 mHeadsUpAppearanceController.updateHeader(entry); 1833 mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(entry); 1834 }); 1835 } 1836 1837 @Override 1838 public void applyLaunchAnimationParams(LaunchAnimationParameters params) { 1839 mView.applyLaunchAnimationParams(params); 1840 } 1841 1842 @Override 1843 public void setExpandingNotification(ExpandableNotificationRow row) { 1844 mView.setExpandingNotification(row); 1845 } 1846 1847 @Override 1848 public boolean containsView(View v) { 1849 return mView.containsView(v); 1850 } 1851 1852 @Override 1853 public void setWillExpand(boolean willExpand) { 1854 mView.setWillExpand(willExpand); 1855 } 1856 1857 @Override 1858 public void dumpPipeline(@NonNull PipelineDumper d) { 1859 d.dump("NotificationStackScrollLayoutController.this", 1860 NotificationStackScrollLayoutController.this); 1861 } 1862 } 1863 1864 class TouchHandler implements Gefingerpoken { 1865 @Override 1866 public boolean onInterceptTouchEvent(MotionEvent ev) { 1867 mView.initDownStates(ev); 1868 mView.handleEmptySpaceClick(ev); 1869 1870 NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); 1871 1872 boolean longPressWantsIt = false; 1873 if (mLongPressedView != null) { 1874 longPressWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 1875 } 1876 boolean expandWantsIt = false; 1877 if (mLongPressedView == null && !mSwipeHelper.isSwiping() 1878 && !mView.getOnlyScrollingInThisMotion() && guts == null) { 1879 expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev); 1880 } 1881 boolean scrollWantsIt = false; 1882 if (mLongPressedView == null && !mSwipeHelper.isSwiping() 1883 && !mView.isExpandingNotification()) { 1884 scrollWantsIt = mView.onInterceptTouchEventScroll(ev); 1885 } 1886 boolean swipeWantsIt = false; 1887 if (mLongPressedView == null && !mView.isBeingDragged() 1888 && !mView.isExpandingNotification() 1889 && !mView.getExpandedInThisMotion() 1890 && !mView.getOnlyScrollingInThisMotion() 1891 && !mView.getDisallowDismissInThisMotion()) { 1892 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 1893 } 1894 // Check if we need to clear any snooze leavebehinds 1895 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; 1896 if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt && 1897 !expandWantsIt && !scrollWantsIt) { 1898 mView.setCheckForLeaveBehind(false); 1899 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, 1900 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, 1901 false /* resetMenu */); 1902 } 1903 if (ev.getActionMasked() == MotionEvent.ACTION_UP) { 1904 mView.setCheckForLeaveBehind(true); 1905 } 1906 1907 // When swiping directly on the NSSL, this would only get an onTouchEvent. 1908 // We log any touches other than down, which will be captured by onTouchEvent. 1909 // In the intercept we only start tracing when it's not a down (otherwise that down 1910 // would be duplicated when intercepted). 1911 if (mJankMonitor != null && scrollWantsIt 1912 && ev.getActionMasked() != MotionEvent.ACTION_DOWN) { 1913 mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING); 1914 } 1915 return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt; 1916 } 1917 1918 @Override 1919 public boolean onTouchEvent(MotionEvent ev) { 1920 NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); 1921 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL 1922 || ev.getActionMasked() == MotionEvent.ACTION_UP; 1923 mView.handleEmptySpaceClick(ev); 1924 boolean longPressWantsIt = false; 1925 if (guts != null && mLongPressedView != null) { 1926 longPressWantsIt = mSwipeHelper.onTouchEvent(ev); 1927 } 1928 boolean expandWantsIt = false; 1929 boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion(); 1930 boolean expandingNotification = mView.isExpandingNotification(); 1931 if (mLongPressedView == null && mView.getIsExpanded() 1932 && !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion && guts == null) { 1933 ExpandHelper expandHelper = mView.getExpandHelper(); 1934 if (isCancelOrUp) { 1935 expandHelper.onlyObserveMovements(false); 1936 } 1937 boolean wasExpandingBefore = expandingNotification; 1938 expandWantsIt = expandHelper.onTouchEvent(ev); 1939 expandingNotification = mView.isExpandingNotification(); 1940 if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore 1941 && !mView.getDisallowScrollingInThisMotion()) { 1942 mView.dispatchDownEventToScroller(ev); 1943 } 1944 } 1945 boolean scrollerWantsIt = false; 1946 if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping() 1947 && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) { 1948 scrollerWantsIt = mView.onScrollTouch(ev); 1949 } 1950 boolean horizontalSwipeWantsIt = false; 1951 if (mLongPressedView == null && !mView.isBeingDragged() 1952 && !expandingNotification 1953 && !mView.getExpandedInThisMotion() 1954 && !onlyScrollingInThisMotion 1955 && !mView.getDisallowDismissInThisMotion()) { 1956 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); 1957 } 1958 1959 // Check if we need to clear any snooze leavebehinds 1960 if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts) 1961 && guts.getGutsContent() instanceof NotificationSnooze) { 1962 NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); 1963 if ((ns.isExpanded() && isCancelOrUp) 1964 || (!horizontalSwipeWantsIt && scrollerWantsIt)) { 1965 // If the leavebehind is expanded we clear it on the next up event, otherwise we 1966 // clear it on the next non-horizontal swipe or expand event. 1967 checkSnoozeLeavebehind(); 1968 } 1969 } 1970 if (ev.getActionMasked() == MotionEvent.ACTION_UP) { 1971 // Ensure the falsing manager records the touch. we don't do anything with it 1972 // at the moment, but it may trigger a global falsing event. 1973 if (!horizontalSwipeWantsIt) { 1974 mFalsingManager.isFalseTouch(Classifier.SHADE_DRAG); 1975 } 1976 mView.setCheckForLeaveBehind(true); 1977 } 1978 traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt); 1979 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt; 1980 } 1981 1982 private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) { 1983 if (mJankMonitor == null) { 1984 Log.w(TAG, "traceJankOnTouchEvent, mJankMonitor is null"); 1985 return; 1986 } 1987 // Handle interaction jank monitor cases. 1988 switch (action) { 1989 case MotionEvent.ACTION_DOWN: 1990 if (scrollerWantsIt) { 1991 mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING); 1992 } 1993 break; 1994 case MotionEvent.ACTION_UP: 1995 if (scrollerWantsIt && !mView.isFlingAfterUpEvent()) { 1996 mJankMonitor.end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); 1997 } 1998 break; 1999 case MotionEvent.ACTION_CANCEL: 2000 if (scrollerWantsIt) { 2001 mJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); 2002 } 2003 break; 2004 } 2005 } 2006 } 2007 2008 private class NotifStackControllerImpl implements NotifStackController { 2009 @Override 2010 public void setNotifStats(@NonNull NotifStats notifStats) { 2011 mNotifStats = notifStats; 2012 mView.setHasFilteredOutSeenNotifications( 2013 mSeenNotificationsProvider.getHasFilteredOutSeenNotifications()); 2014 updateFooter(); 2015 updateShowEmptyShadeView(); 2016 } 2017 } 2018 } 2019