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