• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 Licen
15  */
16 
17 
18 package com.android.systemui.statusbar.notification.stack;
19 
20 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
21 import static com.android.systemui.Flags.ignoreTouchesNextToNotificationShelf;
22 
23 import android.animation.Animator;
24 import android.animation.AnimatorListenerAdapter;
25 import android.animation.ValueAnimator;
26 import android.content.res.Resources;
27 import android.graphics.Rect;
28 import android.os.Handler;
29 import android.service.notification.StatusBarNotification;
30 import android.view.MotionEvent;
31 import android.view.View;
32 import android.view.ViewConfiguration;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.jank.InteractionJankMonitor;
36 import com.android.systemui.SwipeHelper;
37 import com.android.systemui.dump.DumpManager;
38 import com.android.systemui.flags.FeatureFlags;
39 import com.android.systemui.plugins.FalsingManager;
40 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
41 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
42 import com.android.systemui.shade.ShadeDisplayAware;
43 import com.android.systemui.statusbar.NotificationShelf;
44 import com.android.systemui.statusbar.notification.SourceType;
45 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
46 import com.android.systemui.statusbar.notification.row.ExpandableView;
47 
48 import java.lang.ref.WeakReference;
49 
50 import javax.inject.Inject;
51 
52 class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeActionHelper {
53 
54     @VisibleForTesting
55     protected static final long COVER_MENU_DELAY = 4000;
56     private static final String TAG = "NotificationSwipeHelper";
57     private static final SourceType SWIPE_DISMISS = SourceType.from("SwipeDismiss");
58     private final Runnable mFalsingCheck;
59     private View mTranslatingParentView;
60     private View mMenuExposedView;
61     private final NotificationCallback mCallback;
62     private final NotificationMenuRowPlugin.OnMenuEventListener mMenuListener;
63 
64     private static final long SWIPE_MENU_TIMING = 200;
65 
66     // Hold a weak ref to the menu row so that it isn't accidentally retained in memory. The
67     // lifetime of the row should be the same as the ActivatableView, which is owned by the
68     // NotificationStackScrollLayout. If the notification isn't in the notification shade, then it
69     // isn't possible to swipe it and, so, this class doesn't need to "help."
70     private WeakReference<NotificationMenuRowPlugin> mCurrMenuRowRef;
71     private boolean mIsExpanded;
72     private boolean mPulsing;
73     private final NotificationRoundnessManager mNotificationRoundnessManager;
74 
NotificationSwipeHelper( Resources resources, ViewConfiguration viewConfiguration, FalsingManager falsingManager, FeatureFlags featureFlags, NotificationCallback callback, NotificationMenuRowPlugin.OnMenuEventListener menuListener, NotificationRoundnessManager notificationRoundnessManager)75     NotificationSwipeHelper(
76             Resources resources,
77             ViewConfiguration viewConfiguration,
78             FalsingManager falsingManager,
79             FeatureFlags featureFlags,
80             NotificationCallback callback,
81             NotificationMenuRowPlugin.OnMenuEventListener menuListener,
82             NotificationRoundnessManager notificationRoundnessManager) {
83         super(callback, resources, viewConfiguration, falsingManager, featureFlags);
84         mNotificationRoundnessManager = notificationRoundnessManager;
85         mMenuListener = menuListener;
86         mCallback = callback;
87         mFalsingCheck = () -> resetExposedMenuView(true /* animate */, true /* force */);
88     }
89 
getTranslatingParentView()90     public View getTranslatingParentView() {
91         return mTranslatingParentView;
92     }
93 
clearTranslatingParentView()94     public void clearTranslatingParentView() { setTranslatingParentView(null); }
95 
96     @VisibleForTesting
setTranslatingParentView(View view)97     protected void setTranslatingParentView(View view) { mTranslatingParentView = view; }
98 
setExposedMenuView(View view)99     public void setExposedMenuView(View view) {
100         mMenuExposedView = view;
101     }
102 
clearExposedMenuView()103     public void clearExposedMenuView() { setExposedMenuView(null); }
104 
clearCurrentMenuRow()105     public void clearCurrentMenuRow() { setCurrentMenuRow(null); }
106 
getExposedMenuView()107     public View getExposedMenuView() {
108         return mMenuExposedView;
109     }
110 
111     @VisibleForTesting
setCurrentMenuRow(NotificationMenuRowPlugin menuRow)112     void setCurrentMenuRow(NotificationMenuRowPlugin menuRow) {
113         mCurrMenuRowRef = menuRow != null ? new WeakReference<>(menuRow) : null;
114     }
115 
getCurrentMenuRow()116     public NotificationMenuRowPlugin getCurrentMenuRow() {
117         if (mCurrMenuRowRef == null) {
118             return null;
119         }
120         return mCurrMenuRowRef.get();
121     }
122 
123     @VisibleForTesting
getHandler()124     protected Handler getHandler() { return mHandler; }
125 
126     @VisibleForTesting
getFalsingCheck()127     protected Runnable getFalsingCheck() {
128         return mFalsingCheck;
129     }
130 
setIsExpanded(boolean isExpanded)131     public void setIsExpanded(boolean isExpanded) {
132         mIsExpanded = isExpanded;
133     }
134 
135     @Override
onChildSnappedBack(View animView, float targetLeft)136     protected void onChildSnappedBack(View animView, float targetLeft) {
137         super.onChildSnappedBack(animView, targetLeft);
138 
139         final NotificationMenuRowPlugin menuRow = getCurrentMenuRow();
140         if (menuRow != null && targetLeft == 0) {
141             menuRow.resetMenu();
142             clearCurrentMenuRow();
143         }
144 
145         InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_ROW_SWIPE);
146     }
147 
148     @Override
onDownUpdate(View currView, MotionEvent ev)149     public void onDownUpdate(View currView, MotionEvent ev) {
150         mTranslatingParentView = currView;
151         NotificationMenuRowPlugin menuRow = getCurrentMenuRow();
152         if (menuRow != null) {
153             menuRow.onTouchStart();
154         }
155         clearCurrentMenuRow();
156         getHandler().removeCallbacks(getFalsingCheck());
157 
158         // Slide back any notifications that might be showing a menu
159         resetExposedMenuView(true /* animate */, false /* force */);
160 
161         if (currView instanceof SwipeableView) {
162             initializeRow((SwipeableView) currView);
163         }
164     }
165 
166     @VisibleForTesting
initializeRow(SwipeableView row)167     protected void initializeRow(SwipeableView row) {
168         if (row.hasFinishedInitialization()) {
169             final NotificationMenuRowPlugin menuRow = row.createMenu();
170             setCurrentMenuRow(menuRow);
171             if (menuRow != null) {
172                 menuRow.setMenuClickListener(mMenuListener);
173                 menuRow.onTouchStart();
174             }
175         }
176     }
177 
swipedEnoughToShowMenu(NotificationMenuRowPlugin menuRow)178     private boolean swipedEnoughToShowMenu(NotificationMenuRowPlugin menuRow) {
179         return !swipedFarEnough() && menuRow.isSwipedEnoughToShowMenu();
180     }
181 
182     @Override
onMoveUpdate(View view, MotionEvent ev, float translation, float delta)183     public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) {
184         getHandler().removeCallbacks(getFalsingCheck());
185         NotificationMenuRowPlugin menuRow = getCurrentMenuRow();
186         if (menuRow != null) {
187             menuRow.onTouchMove(delta);
188         }
189     }
190 
191     @Override
handleUpEvent(MotionEvent ev, View animView, float velocity, float translation)192     public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
193             float translation) {
194         NotificationMenuRowPlugin menuRow = getCurrentMenuRow();
195         if (menuRow != null) {
196             menuRow.onTouchEnd();
197             handleMenuRowSwipe(ev, animView, velocity, menuRow);
198             return true;
199         }
200         return false;
201     }
202 
203     @Override
updateSwipeProgressAlpha(View animView, float alpha)204     protected void updateSwipeProgressAlpha(View animView, float alpha) {
205         if (animView instanceof ExpandableNotificationRow) {
206             ((ExpandableNotificationRow) animView).setContentAlpha(alpha);
207         }
208     }
209 
210     @VisibleForTesting
handleMenuRowSwipe(MotionEvent ev, View animView, float velocity, NotificationMenuRowPlugin menuRow)211     protected void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity,
212             NotificationMenuRowPlugin menuRow) {
213         if (!menuRow.shouldShowMenu()) {
214             // If the menu should not be shown, then there is no need to check if the a swipe
215             // should result in a snapping to the menu. As a result, just check if the swipe
216             // was enough to dismiss the notification.
217             if (isDismissGesture(ev)) {
218                 dismiss(animView, velocity);
219             } else {
220                 snapClosed(animView, velocity);
221                 menuRow.onSnapClosed();
222             }
223             return;
224         }
225 
226         if (menuRow.isSnappedAndOnSameSide()) {
227             // Menu was snapped to previously and we're on the same side
228             handleSwipeFromOpenState(ev, animView, velocity, menuRow);
229         } else {
230             // Menu has not been snapped, or was snapped previously but is now on
231             // the opposite side.
232             handleSwipeFromClosedState(ev, animView, velocity, menuRow);
233         }
234     }
235 
handleSwipeFromClosedState(MotionEvent ev, View animView, float velocity, NotificationMenuRowPlugin menuRow)236     private void handleSwipeFromClosedState(MotionEvent ev, View animView, float velocity,
237             NotificationMenuRowPlugin menuRow) {
238         boolean isDismissGesture = isDismissGesture(ev);
239         final boolean gestureTowardsMenu = menuRow.isTowardsMenu(velocity);
240         final boolean gestureFastEnough = getEscapeVelocity() <= Math.abs(velocity);
241 
242         final double timeForGesture = ev.getEventTime() - ev.getDownTime();
243         final boolean showMenuForSlowOnGoing = !menuRow.canBeDismissed()
244                 && timeForGesture >= SWIPE_MENU_TIMING;
245 
246         boolean isNonDismissGestureTowardsMenu = gestureTowardsMenu && !isDismissGesture;
247         boolean isSlowSwipe = !gestureFastEnough || showMenuForSlowOnGoing;
248         boolean slowSwipedFarEnough = swipedEnoughToShowMenu(menuRow) && isSlowSwipe;
249         boolean isFastNonDismissGesture =
250                 gestureFastEnough && !gestureTowardsMenu && !isDismissGesture;
251         boolean isAbleToShowMenu = menuRow.shouldShowGutsOnSnapOpen()
252                 || mIsExpanded && !mPulsing;
253         boolean isMenuRevealingGestureAwayFromMenu = slowSwipedFarEnough
254                 || (isFastNonDismissGesture && isAbleToShowMenu);
255         int menuSnapTarget = menuRow.getMenuSnapTarget();
256         boolean isNonFalseMenuRevealingGesture =
257                 isMenuRevealingGestureAwayFromMenu && !isFalseGesture();
258         if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture)
259                 && menuSnapTarget != 0) {
260             // Menu has not been snapped to previously and this is menu revealing gesture
261             snapOpen(animView, menuSnapTarget, velocity);
262             menuRow.onSnapOpen();
263         } else if (isDismissGesture && (!gestureTowardsMenu || isSwipeDismissible())) {
264             dismiss(animView, velocity);
265             menuRow.onDismiss();
266         } else {
267             snapClosed(animView, velocity);
268             menuRow.onSnapClosed();
269         }
270     }
271 
handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity, NotificationMenuRowPlugin menuRow)272     private void handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity,
273             NotificationMenuRowPlugin menuRow) {
274         boolean isDismissGesture = isDismissGesture(ev);
275 
276         final boolean withinSnapMenuThreshold =
277                 menuRow.isWithinSnapMenuThreshold();
278 
279         if (withinSnapMenuThreshold && !isDismissGesture) {
280             // Haven't moved enough to unsnap from the menu
281             menuRow.onSnapOpen();
282             snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
283         } else if (isDismissGesture && (!menuRow.shouldSnapBack() || isSwipeDismissible())) {
284             // Only dismiss if we're not moving towards the menu
285             dismiss(animView, velocity);
286             menuRow.onDismiss();
287         } else {
288             snapClosed(animView, velocity);
289             menuRow.onSnapClosed();
290         }
291     }
292 
293     @Override
onInterceptTouchEvent(MotionEvent ev)294     public boolean onInterceptTouchEvent(MotionEvent ev) {
295         final boolean previousIsSwiping = isSwiping();
296         boolean ret = super.onInterceptTouchEvent(ev);
297         final View swipedView = getSwipedView();
298         if (!previousIsSwiping && swipedView != null) {
299             InteractionJankMonitor.getInstance().begin(swipedView,
300                     CUJ_NOTIFICATION_SHADE_ROW_SWIPE);
301         }
302         return ret;
303     }
304 
onDismissChildWithAnimationFinished()305     protected void onDismissChildWithAnimationFinished() {
306         InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_ROW_SWIPE);
307     }
308 
309     @Override
dismissChild(final View view, float velocity, boolean useAccelerateInterpolator)310     public void dismissChild(final View view, float velocity,
311             boolean useAccelerateInterpolator) {
312         superDismissChild(view, velocity, useAccelerateInterpolator);
313         if (mCallback.shouldDismissQuickly()) {
314             // We don't want to quick-dismiss when it's a heads up as this might lead to closing
315             // of the panel early.
316             mCallback.handleChildViewDismissed(view);
317         }
318         mCallback.onDismiss();
319         handleMenuCoveredOrDismissed();
320     }
321 
322     @Override
prepareDismissAnimation(View view, Animator anim)323     protected void prepareDismissAnimation(View view, Animator anim) {
324         super.prepareDismissAnimation(view, anim);
325 
326         if (view instanceof ExpandableNotificationRow
327                 && mNotificationRoundnessManager.isClearAllInProgress()) {
328             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
329             anim.addListener(new AnimatorListenerAdapter() {
330                 @Override
331                 public void onAnimationStart(Animator animation) {
332                     row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, SWIPE_DISMISS);
333                 }
334 
335                 @Override
336                 public void onAnimationCancel(Animator animation) {
337                     row.requestRoundnessReset(SWIPE_DISMISS);
338                 }
339 
340                 @Override
341                 public void onAnimationEnd(Animator animation) {
342                     row.requestRoundnessReset(SWIPE_DISMISS);
343                 }
344             });
345         }
346     }
347 
348     @VisibleForTesting
superDismissChild(final View view, float velocity, boolean useAccelerateInterpolator)349     protected void superDismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
350         super.dismissChild(view, velocity, useAccelerateInterpolator);
351     }
352 
353     @VisibleForTesting
superSnapChild(final View animView, final float targetLeft, float velocity)354     protected void superSnapChild(final View animView, final float targetLeft, float velocity) {
355         super.snapChild(animView, targetLeft, velocity);
356     }
357 
358     @Override
snapChild(final View animView, final float targetLeft, float velocity)359     protected void snapChild(final View animView, final float targetLeft, float velocity) {
360         if (animView instanceof SwipeableView) {
361             // only perform the snapback animation on views that are swipeable inside the shade.
362             superSnapChild(animView, targetLeft, velocity);
363         }
364 
365         mCallback.onMagneticInteractionEnd(animView, velocity);
366         mCallback.onDragCancelled(animView);
367         if (targetLeft == 0) {
368             handleMenuCoveredOrDismissed();
369         }
370     }
371 
372     @Override
snooze(StatusBarNotification sbn, SnoozeOption snoozeOption)373     public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption) {
374         mCallback.onSnooze(sbn, snoozeOption);
375     }
376 
377     @VisibleForTesting
handleMenuCoveredOrDismissed()378     protected void handleMenuCoveredOrDismissed() {
379         View exposedMenuView = getExposedMenuView();
380         if (exposedMenuView != null && exposedMenuView == mTranslatingParentView) {
381             clearExposedMenuView();
382         }
383     }
384 
385     @Override
386     @VisibleForTesting
getViewTranslationAnimator(View view, float target, ValueAnimator.AnimatorUpdateListener listener)387     protected Animator getViewTranslationAnimator(View view, float target,
388             ValueAnimator.AnimatorUpdateListener listener) {
389         return super.getViewTranslationAnimator(view, target, listener);
390     }
391 
392     @Override
393     @VisibleForTesting
createTranslationAnimation(View view, float newPos, ValueAnimator.AnimatorUpdateListener listener)394     protected Animator createTranslationAnimation(View view, float newPos,
395             ValueAnimator.AnimatorUpdateListener listener) {
396         return super.createTranslationAnimation(view, newPos, listener);
397     }
398 
399     @Override
getTotalTranslationLength(View animView)400     protected float getTotalTranslationLength(View animView) {
401         return mCallback.getTotalTranslationLength(animView);
402     }
403 
404     @Override
setTranslation(View v, float translate)405     public void setTranslation(View v, float translate) {
406         if (v instanceof SwipeableView) {
407             boolean setTranslationHandled =
408                     mCallback.handleSwipeableViewTranslation((SwipeableView) v, translate);
409             if (!setTranslationHandled) {
410                 ((SwipeableView) v).setTranslation(translate);
411             }
412         }
413     }
414 
415     @Override
getTranslation(View v)416     public float getTranslation(View v) {
417         if (v instanceof SwipeableView) {
418             return ((SwipeableView) v).getTranslation();
419         }
420         else {
421             return 0f;
422         }
423     }
424 
425     @Override
426     @VisibleForTesting
swipedFastEnough()427     protected boolean swipedFastEnough() {
428         return super.swipedFastEnough();
429     }
430 
431     @Override
432     @VisibleForTesting
swipedFarEnough()433     protected boolean swipedFarEnough() {
434         return super.swipedFarEnough();
435     }
436 
437     @Override
dismiss(View animView, float velocity)438     public void dismiss(View animView, float velocity) {
439         dismissChild(animView, velocity,
440                 !swipedFastEnough() /* useAccelerateInterpolator */);
441     }
442 
443     @Override
snapOpen(View animView, int targetLeft, float velocity)444     public void snapOpen(View animView, int targetLeft, float velocity) {
445         snapChild(animView, targetLeft, velocity);
446     }
447 
448     @VisibleForTesting
snapClosed(View animView, float velocity)449     protected void snapClosed(View animView, float velocity) {
450         snapChild(animView, 0, velocity);
451     }
452 
453     @Override
454     @VisibleForTesting
getEscapeVelocity()455     protected float getEscapeVelocity() {
456         return super.getEscapeVelocity();
457     }
458 
459     @Override
getMinDismissVelocity()460     public float getMinDismissVelocity() {
461         return getEscapeVelocity();
462     }
463 
onMenuShown(View animView)464     public void onMenuShown(View animView) {
465         setExposedMenuView(getTranslatingParentView());
466         mCallback.onDragCancelled(animView);
467         Handler handler = getHandler();
468 
469         // If we're on the lockscreen we want to false this.
470         if (mCallback.isAntiFalsingNeeded()) {
471             handler.removeCallbacks(getFalsingCheck());
472             handler.postDelayed(getFalsingCheck(), COVER_MENU_DELAY);
473         }
474     }
475 
476     @VisibleForTesting
shouldResetMenu(boolean force)477     protected boolean shouldResetMenu(boolean force) {
478         if (mMenuExposedView == null
479                 || (!force && mMenuExposedView == mTranslatingParentView)) {
480             // If no menu is showing or it's showing for this view we do nothing.
481             return false;
482         }
483         return true;
484     }
485 
resetExposedMenuView(boolean animate, boolean force)486     public void resetExposedMenuView(boolean animate, boolean force) {
487         if (!shouldResetMenu(force)) {
488             return;
489         }
490         final View prevMenuExposedView = getExposedMenuView();
491         if (animate) {
492             Animator anim = getViewTranslationAnimator(prevMenuExposedView,
493                     0 /* leftTarget */, null /* updateListener */);
494             if (anim != null) {
495                 anim.start();
496             }
497         } else if (prevMenuExposedView instanceof SwipeableView) {
498             SwipeableView row = (SwipeableView) prevMenuExposedView;
499             if (!row.isRemoved()) {
500                 row.resetTranslation();
501             }
502         }
503         clearExposedMenuView();
504     }
505 
isTouchInView(MotionEvent ev, View view)506     public static boolean isTouchInView(MotionEvent ev, View view) {
507         if (view == null) {
508             return false;
509         }
510         final int height = (view instanceof ExpandableView)
511                 ? ((ExpandableView) view).getActualHeight()
512                 : view.getHeight();
513         final int width;
514         if (ignoreTouchesNextToNotificationShelf()) {
515             width = (view instanceof NotificationShelf)
516                 ? ((NotificationShelf) view).getActualWidth()
517                 : view.getWidth();
518         } else {
519             width = view.getWidth();
520         }
521         final int rx = (int) ev.getRawX();
522         final int ry = (int) ev.getRawY();
523         int[] temp = new int[2];
524         view.getLocationOnScreen(temp);
525         final int x = temp[0];
526         final int y = temp[1];
527         Rect rect = new Rect(x, y, x + width, y + height);
528         boolean ret = rect.contains(rx, ry);
529         return ret;
530     }
531 
setPulsing(boolean pulsing)532     public void setPulsing(boolean pulsing) {
533         mPulsing = pulsing;
534     }
535 
536     @Override
resetTouchState()537     public void resetTouchState() {
538         super.resetTouchState();
539         mCallback.resetMagneticStates();
540     }
541 
542     public interface NotificationCallback extends SwipeHelper.Callback{
543         /**
544          * @return if the view should be dismissed as soon as the touch is released, otherwise its
545          *         removed when the animation finishes.
546          */
shouldDismissQuickly()547         boolean shouldDismissQuickly();
548 
handleChildViewDismissed(View view)549         void handleChildViewDismissed(View view);
550 
onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption)551         void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
552 
onDismiss()553         void onDismiss();
554 
555         /**
556          * Get the total translation length where we want to swipe to when dismissing the view. By
557          * default this is the size of the view, but can also be larger.
558          * @param animView the view to ask about
559          */
getTotalTranslationLength(View animView)560         float getTotalTranslationLength(View animView);
561 
handleSwipeableViewTranslation(SwipeableView view, float translate)562         boolean handleSwipeableViewTranslation(SwipeableView view, float translate);
563 
564         // Reset any ongoing magnetic interactions
resetMagneticStates()565         void resetMagneticStates();
566     }
567 
568     static class Builder {
569         private final Resources mResources;
570         private final ViewConfiguration mViewConfiguration;
571         private final FalsingManager mFalsingManager;
572         private final FeatureFlags mFeatureFlags;
573         private NotificationCallback mNotificationCallback;
574         private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
575         private DumpManager mDumpManager;
576         private NotificationRoundnessManager mNotificationRoundnessManager;
577 
578         @Inject
Builder(@hadeDisplayAware Resources resources, ViewConfiguration viewConfiguration, DumpManager dumpManager, FalsingManager falsingManager, FeatureFlags featureFlags, NotificationRoundnessManager notificationRoundnessManager)579         Builder(@ShadeDisplayAware Resources resources, ViewConfiguration viewConfiguration,
580                 DumpManager dumpManager,
581                 FalsingManager falsingManager, FeatureFlags featureFlags,
582                 NotificationRoundnessManager notificationRoundnessManager) {
583             mResources = resources;
584             mViewConfiguration = viewConfiguration;
585             mDumpManager = dumpManager;
586             mFalsingManager = falsingManager;
587             mFeatureFlags = featureFlags;
588             mNotificationRoundnessManager = notificationRoundnessManager;
589         }
590 
setNotificationCallback(NotificationCallback notificationCallback)591         Builder setNotificationCallback(NotificationCallback notificationCallback) {
592             mNotificationCallback = notificationCallback;
593             return this;
594         }
595 
setOnMenuEventListener( NotificationMenuRowPlugin.OnMenuEventListener onMenuEventListener)596         Builder setOnMenuEventListener(
597                 NotificationMenuRowPlugin.OnMenuEventListener onMenuEventListener) {
598             mOnMenuEventListener = onMenuEventListener;
599             return this;
600         }
601 
build()602         NotificationSwipeHelper build() {
603             NotificationSwipeHelper swipeHelper = new NotificationSwipeHelper(
604                     mResources, mViewConfiguration, mFalsingManager,
605                     mFeatureFlags, mNotificationCallback, mOnMenuEventListener,
606                     mNotificationRoundnessManager);
607             mDumpManager.registerDumpable(swipeHelper);
608             return swipeHelper;
609         }
610     }
611 }
612