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