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