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