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