Merge "Remove references to SwipeHelper from NotificationMenuRow."

This commit is contained in:
Aaron Heuckroth
2018-09-21 15:30:30 +00:00
committed by Android (Google) Code Review
6 changed files with 672 additions and 213 deletions

View File

@@ -38,11 +38,12 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem
public interface NotificationMenuRowPlugin extends Plugin {
public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
public static final int VERSION = 4;
public static final int VERSION = 5;
@ProvidesInterface(version = OnMenuEventListener.VERSION)
public interface OnMenuEventListener {
public static final int VERSION = 1;
public void onMenuClicked(View row, int x, int y, MenuItem menu);
public void onMenuReset(View row);
@@ -53,6 +54,7 @@ public interface NotificationMenuRowPlugin extends Plugin {
@ProvidesInterface(version = MenuItem.VERSION)
public interface MenuItem {
public static final int VERSION = 1;
public View getMenuView();
public View getGutsView();
@@ -84,34 +86,136 @@ public interface NotificationMenuRowPlugin extends Plugin {
public void setMenuClickListener(OnMenuEventListener listener);
public void setSwipeActionHelper(NotificationSwipeActionHelper listener);
public void setAppName(String appName);
public void createMenu(ViewGroup parent, StatusBarNotification sbn);
public View getMenuView();
public boolean isMenuVisible();
public void resetMenu();
public void onTranslationUpdate(float translation);
public View getMenuView();
public void onHeightUpdate();
/**
* Get the target position that a notification row should be snapped open to in order to reveal
* the menu. This is generally determined by the number of icons in the notification menu and the
* size of each icon. This method accounts for whether the menu appears on the left or ride side
* of the parent notification row.
*
public void onNotificationUpdated(StatusBarNotification sbn);
* @return an int representing the x-offset in pixels that the notification should snap open to.
* Positive values imply that the notification should be offset to the right to reveal the menu,
* and negative alues imply that the notification should be offset to the right.
*/
public int getMenuSnapTarget();
public boolean onTouchEvent(View view, MotionEvent ev, float velocity);
/**
* Determines whether or not the menu should be shown in response to user input.
* @return true if the menu should be shown, false otherwise.
*/
public boolean shouldShowMenu();
/**
* Determines whether the menu is currently visible.
* @return true if the menu is visible, false otherwise.
*/
public boolean isMenuVisible();
/**
* Determines whether a given movement is towards or away from the current location of the menu.
* @param movement
* @return true if the movement is towards the menu, false otherwise.
*/
public boolean isTowardsMenu(float movement);
/**
* Determines whether the menu should snap closed instead of dismissing the
* parent notification, as a function of its current state.
*
* @return true if the menu should snap closed, false otherwise.
*/
public boolean shouldSnapBack();
/**
* Determines whether the menu was previously snapped open to the same side that it is currently
* being shown on.
* @return true if the menu is snapped open to the same side on which it currently appears,
* false otherwise.
*/
public boolean isSnappedAndOnSameSide();
/**
* Determines whether the notification the menu is attached to is able to be dismissed.
* @return true if the menu's parent notification is dismissable, false otherwise.
*/
public boolean canBeDismissed();
/**
* Determines whether the menu should remain open given its current state, or snap closed.
* @return true if the menu should remain open, false otherwise.
*/
public boolean isWithinSnapMenuThreshold();
/**
* Determines whether the menu has been swiped far enough to snap open.
* @return true if the menu has been swiped far enough to open, false otherwise.
*/
public boolean isSwipedEnoughToShowMenu();
public default boolean onInterceptTouchEvent(View view, MotionEvent ev) {
return false;
}
public default boolean useDefaultMenuItems() {
public default boolean shouldUseDefaultMenuItems() {
return false;
}
public default void onConfigurationChanged() {
}
/**
* Callback used to signal the menu that its parent's translation has changed.
* @param translation The new x-translation of the menu as a position (not an offset).
*/
public void onParentTranslationUpdate(float translation);
/**
* Callback used to signal the menu that its parent's height has changed.
*/
public void onParentHeightUpdate();
/**
* Callback used to signal the menu that its parent notification has been updated.
* @param sbn
*/
public void onNotificationUpdated(StatusBarNotification sbn);
/**
* Callback used to signal the menu that a user is moving the parent notification.
* @param delta The change in the parent notification's position.
*/
public void onTouchMove(float delta);
/**
* Callback used to signal the menu that a user has begun touching its parent notification.
*/
public void onTouchStart();
/**
* Callback used to signal the menu that a user has finished touching its parent notification.
*/
public void onTouchEnd();
/**
* Callback used to signal the menu that it has been snapped closed.
*/
public void onSnapClosed();
/**
* Callback used to signal the menu that it has been snapped open.
*/
public void onSnapOpen();
/**
* Callback used to signal the menu that its parent notification has been dismissed.
*/
public void onDismiss();
public default void onConfigurationChanged() { }
}

View File

@@ -39,7 +39,7 @@ public interface NotificationSwipeActionHelper {
/**
* Call this to snap a notification to provided {@code targetLeft}.
*/
public void snap(View animView, float velocity, float targetLeft);
public void snapOpen(View animView, int targetLeft, float velocity);
/**
* Call this to snooze a notification based on the provided {@link SnoozeOption}.

View File

@@ -1037,7 +1037,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
removeView(mMenuRow.getMenuView());
}
mMenuRow = plugin;
if (mMenuRow.useDefaultMenuItems()) {
if (mMenuRow.shouldUseDefaultMenuItems()) {
ArrayList<MenuItem> items = new ArrayList<>();
items.add(NotificationMenuRow.createInfoItem(mContext));
items.add(NotificationMenuRow.createSnoozeItem(mContext));
@@ -1787,7 +1787,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
getEntry().expandedIcon.setScrollX((int) -translationX);
}
if (mMenuRow.getMenuView() != null) {
mMenuRow.onTranslationUpdate(translationX);
mMenuRow.onParentTranslationUpdate(translationX);
}
}
@@ -2292,7 +2292,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
notifyHeightChanged(true /* needsAnimation */);
}
if (mMenuRow.getMenuView() != null) {
mMenuRow.onHeightUpdate();
mMenuRow.onParentHeightUpdate();
}
updateContentShiftHeight();
if (mLayoutListener != null) {
@@ -2543,7 +2543,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mGuts.setActualHeight(height);
}
if (mMenuRow.getMenuView() != null) {
mMenuRow.onHeightUpdate();
mMenuRow.onParentHeightUpdate();
}
}

View File

@@ -16,14 +16,13 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
import java.util.ArrayList;
import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -38,25 +37,21 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.service.notification.StatusBarNotification;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import com.android.internal.annotations.VisibleForTesting;
public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener,
ExpandableNotificationRow.LayoutListener {
private static final boolean DEBUG = false;
private static final String TAG = "swipe";
private static final int ICON_ALPHA_ANIM_DURATION = 200;
private static final long SHOW_MENU_DELAY = 60;
private static final long SWIPE_MENU_TIMING = 200;
// Notification must be swiped at least this fraction of a single menu item to show menu
private static final float SWIPED_FAR_ENOUGH_MENU_FRACTION = 0.25f;
private static final float SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION = 0.15f;
@@ -65,6 +60,9 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
// menu item to snap back to menu (else it will cover the menu or it'll be dismissed)
private static final float SWIPED_BACK_ENOUGH_TO_COVER_FRACTION = 0.2f;
private static final int ICON_ALPHA_ANIM_DURATION = 200;
private static final long SHOW_MENU_DELAY = 60;
private ExpandableNotificationRow mParent;
private Context mContext;
@@ -89,22 +87,20 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
private int[] mIconLocation = new int[2];
private int[] mParentLocation = new int[2];
private float mHorizSpaceForIcon = -1;
private int mHorizSpaceForIcon = -1;
private int mVertSpaceForIcons = -1;
private int mIconPadding = -1;
private int mSidePadding;
private float mAlpha = 0f;
private float mPrevX;
private CheckForDrag mCheckForDrag;
private Handler mHandler;
private boolean mMenuSnappedTo;
private boolean mMenuSnapped;
private boolean mMenuSnappedOnLeft;
private boolean mShouldShowMenu;
private NotificationSwipeActionHelper mSwipeHelper;
private boolean mIsUserTouching;
public NotificationMenuRow(Context context) {
@@ -134,9 +130,34 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
return mSnoozeItem;
}
@Override
public void setSwipeActionHelper(NotificationSwipeActionHelper helper) {
mSwipeHelper = helper;
@VisibleForTesting
protected ExpandableNotificationRow getParent() {
return mParent;
}
@VisibleForTesting
protected boolean isMenuOnLeft() {
return mOnLeft;
}
@VisibleForTesting
protected boolean isMenuSnappedOnLeft() {
return mMenuSnappedOnLeft;
}
@VisibleForTesting
protected boolean isMenuSnapped() {
return mMenuSnapped;
}
@VisibleForTesting
protected boolean isDismissing() {
return mDismissing;
}
@VisibleForTesting
protected boolean isSnapping() {
return mSnapping;
}
@Override
@@ -155,16 +176,36 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
return mAlpha > 0;
}
@VisibleForTesting
protected boolean isUserTouching() {
return mIsUserTouching;
}
@Override
public boolean shouldShowMenu() {
return mShouldShowMenu;
}
@Override
public View getMenuView() {
return mMenuContainer;
}
@VisibleForTesting
protected float getTranslation() {
return mTranslation;
}
@Override
public void resetMenu() {
resetState(true);
}
@Override
public void onTouchEnd() {
mIsUserTouching = false;
}
@Override
public void onNotificationUpdated(StatusBarNotification sbn) {
if (mMenuContainer == null) {
@@ -222,9 +263,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mIconsPlaced = false;
setMenuLocation();
if (!mIsUserTouching) {
// If the # of items showing changed we need to update the snap position
showMenu(mParent, mOnLeft ? getSpaceForMenu() : -getSpaceForMenu(),
0 /* velocity */);
onSnapOpen();
}
}
}
@@ -236,7 +275,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mAnimating = false;
mSnapping = false;
mDismissing = false;
mMenuSnappedTo = false;
mMenuSnapped = false;
setMenuLocation();
if (mMenuListener != null && notify) {
mMenuListener.onMenuReset(mParent);
@@ -244,185 +283,102 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
@Override
public boolean onTouchEvent(View view, MotionEvent ev, float velocity) {
final int action = ev.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
mSnapping = false;
if (mFadeAnimator != null) {
mFadeAnimator.cancel();
}
mHandler.removeCallbacks(mCheckForDrag);
public void onTouchMove(float delta) {
mSnapping = false;
if (!isTowardsMenu(delta) && isMenuLocationChange()) {
// Don't consider it "snapped" if location has changed.
mMenuSnapped = false;
// Changed directions, make sure we check to fade in icon again.
if (!mHandler.hasCallbacks(mCheckForDrag)) {
// No check scheduled, set null to schedule a new one.
mCheckForDrag = null;
mPrevX = ev.getRawX();
mIsUserTouching = true;
break;
case MotionEvent.ACTION_MOVE:
mSnapping = false;
float diffX = ev.getRawX() - mPrevX;
mPrevX = ev.getRawX();
if (!isTowardsMenu(diffX) && isMenuLocationChange()) {
// Don't consider it "snapped" if location has changed.
mMenuSnappedTo = false;
// Changed directions, make sure we check to fade in icon again.
if (!mHandler.hasCallbacks(mCheckForDrag)) {
// No check scheduled, set null to schedule a new one.
mCheckForDrag = null;
} else {
// Check scheduled, reset alpha and update location; check will fade it in
setMenuAlpha(0f);
setMenuLocation();
}
}
if (mShouldShowMenu
&& !NotificationStackScrollLayout.isPinnedHeadsUp(view)
&& !mParent.areGutsExposed()
&& !mParent.isDark()
&& (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag))) {
// Only show the menu if we're not a heads up view and guts aren't exposed.
mCheckForDrag = new CheckForDrag();
mHandler.postDelayed(mCheckForDrag, SHOW_MENU_DELAY);
}
break;
case MotionEvent.ACTION_UP:
mIsUserTouching = false;
return handleUpEvent(ev, view, velocity);
case MotionEvent.ACTION_CANCEL:
mIsUserTouching = false;
cancelDrag();
return false;
}
return false;
}
private boolean handleUpEvent(MotionEvent ev, View animView, float velocity) {
// If the menu should not be shown, then there is no need to check if the a swipe
// should result in a snapping to the menu. As a result, just check if the swipe
// was enough to dismiss the notification.
if (!mShouldShowMenu) {
if (mSwipeHelper.isDismissGesture(ev)) {
dismiss(animView, velocity);
} else {
snapBack(animView, velocity);
// Check scheduled, reset alpha and update location; check will fade it in
setMenuAlpha(0f);
setMenuLocation();
}
return true;
}
final boolean gestureTowardsMenu = isTowardsMenu(velocity);
final boolean gestureFastEnough =
mSwipeHelper.getMinDismissVelocity() <= Math.abs(velocity);
final boolean gestureFarEnough =
mSwipeHelper.swipedFarEnough(mTranslation, mParent.getWidth());
final double timeForGesture = ev.getEventTime() - ev.getDownTime();
final boolean showMenuForSlowOnGoing = !mParent.canViewBeDismissed()
&& timeForGesture >= SWIPE_MENU_TIMING;
final float menuSnapTarget = mOnLeft ? getSpaceForMenu() : -getSpaceForMenu();
if (DEBUG) {
Log.d(TAG, "mTranslation= " + mTranslation
+ " mAlpha= " + mAlpha
+ " velocity= " + velocity
+ " mMenuSnappedTo= " + mMenuSnappedTo
+ " mMenuSnappedOnLeft= " + mMenuSnappedOnLeft
+ " mOnLeft= " + mOnLeft
+ " minDismissVel= " + mSwipeHelper.getMinDismissVelocity()
+ " isDismissGesture= " + mSwipeHelper.isDismissGesture(ev)
+ " gestureTowardsMenu= " + gestureTowardsMenu
+ " gestureFastEnough= " + gestureFastEnough
+ " gestureFarEnough= " + gestureFarEnough);
if (mShouldShowMenu
&& !NotificationStackScrollLayout.isPinnedHeadsUp(getParent())
&& !mParent.areGutsExposed()
&& !mParent.isDark()
&& (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag))) {
// Only show the menu if we're not a heads up view and guts aren't exposed.
mCheckForDrag = new CheckForDrag();
mHandler.postDelayed(mCheckForDrag, SHOW_MENU_DELAY);
}
if (mMenuSnappedTo && isMenuVisible() && mMenuSnappedOnLeft == mOnLeft) {
// Menu was snapped to previously and we're on the same side, figure out if
// we should stick to the menu, snap back into place, or dismiss
final float maximumSwipeDistance = mHorizSpaceForIcon
* SWIPED_BACK_ENOUGH_TO_COVER_FRACTION;
final float targetLeft = getSpaceForMenu() - maximumSwipeDistance;
final float targetRight = mParent.getWidth() * SWIPED_FAR_ENOUGH_SIZE_FRACTION;
boolean withinSnapMenuThreshold = mOnLeft
? mTranslation > targetLeft && mTranslation < targetRight
: mTranslation < -targetLeft && mTranslation > -targetRight;
boolean shouldSnapTo = mOnLeft ? mTranslation < targetLeft : mTranslation > -targetLeft;
if (DEBUG) {
Log.d(TAG, " withinSnapMenuThreshold= " + withinSnapMenuThreshold
+ " shouldSnapTo= " + shouldSnapTo
+ " targetLeft= " + targetLeft
+ " targetRight= " + targetRight);
}
if (withinSnapMenuThreshold && !mSwipeHelper.isDismissGesture(ev)) {
// Haven't moved enough to unsnap from the menu
showMenu(animView, menuSnapTarget, velocity);
} else if (mSwipeHelper.isDismissGesture(ev) && !shouldSnapTo) {
// Only dismiss if we're not moving towards the menu
dismiss(animView, velocity);
} else {
snapBack(animView, velocity);
}
} else if (!mSwipeHelper.isFalseGesture(ev)
&& (swipedEnoughToShowMenu() && (!gestureFastEnough || showMenuForSlowOnGoing))
|| (gestureTowardsMenu && !mSwipeHelper.isDismissGesture(ev))) {
// Menu has not been snapped to previously and this is menu revealing gesture
showMenu(animView, menuSnapTarget, velocity);
} else if (mSwipeHelper.isDismissGesture(ev) && !gestureTowardsMenu) {
dismiss(animView, velocity);
} else {
snapBack(animView, velocity);
}
return true;
}
private void showMenu(View animView, float targetLeft, float velocity) {
mMenuSnappedTo = true;
mMenuSnappedOnLeft = mOnLeft;
mMenuListener.onMenuShown(animView);
mSwipeHelper.snap(animView, targetLeft, velocity);
@VisibleForTesting
protected void beginDrag() {
mSnapping = false;
if (mFadeAnimator != null) {
mFadeAnimator.cancel();
}
mHandler.removeCallbacks(mCheckForDrag);
mCheckForDrag = null;
mIsUserTouching = true;
}
private void snapBack(View animView, float velocity) {
@Override
public void onTouchStart() {
beginDrag();
}
@Override
public void onSnapOpen() {
mMenuSnapped = true;
mMenuSnappedOnLeft = isMenuOnLeft();
if (mMenuListener != null) {
mMenuListener.onMenuShown(getParent());
}
}
@Override
public void onSnapClosed() {
cancelDrag();
mMenuSnappedTo = false;
mMenuSnapped = false;
mSnapping = true;
mSwipeHelper.snap(animView, 0 /* leftTarget */, velocity);
}
private void dismiss(View animView, float velocity) {
@Override
public void onDismiss() {
cancelDrag();
mMenuSnappedTo = false;
mMenuSnapped = false;
mDismissing = true;
mSwipeHelper.dismiss(animView, velocity);
}
private void cancelDrag() {
@VisibleForTesting
protected void cancelDrag() {
if (mFadeAnimator != null) {
mFadeAnimator.cancel();
}
mHandler.removeCallbacks(mCheckForDrag);
}
/**
* @return whether the notification has been translated enough to show the menu and not enough
* to be dismissed.
*/
private boolean swipedEnoughToShowMenu() {
final float multiplier = mParent.canViewBeDismissed()
@VisibleForTesting
protected float getMinimumSwipeDistance() {
final float multiplier = getParent().canViewBeDismissed()
? SWIPED_FAR_ENOUGH_MENU_FRACTION
: SWIPED_FAR_ENOUGH_MENU_UNCLEARABLE_FRACTION;
final float minimumSwipeDistance = mHorizSpaceForIcon * multiplier;
return !mSwipeHelper.swipedFarEnough(0, 0) && isMenuVisible()
&& (mOnLeft ? mTranslation > minimumSwipeDistance
: mTranslation < -minimumSwipeDistance);
return mHorizSpaceForIcon * multiplier;
}
@VisibleForTesting
protected float getMaximumSwipeDistance() {
return mHorizSpaceForIcon * SWIPED_BACK_ENOUGH_TO_COVER_FRACTION;
}
/**
* Returns whether the gesture is towards the menu location or not.
*/
private boolean isTowardsMenu(float movement) {
@Override
public boolean isTowardsMenu(float movement) {
return isMenuVisible()
&& ((mOnLeft && movement <= 0)
|| (!mOnLeft && movement >= 0));
&& ((isMenuOnLeft() && movement <= 0)
|| (!isMenuOnLeft() && movement >= 0));
}
@Override
@@ -445,7 +401,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
@Override
public void onHeightUpdate() {
public void onParentHeightUpdate() {
if (mParent == null || mMenuItems.size() == 0 || mMenuContainer == null) {
return;
}
@@ -460,7 +416,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
@Override
public void onTranslationUpdate(float translation) {
public void onParentTranslationUpdate(float translation) {
mTranslation = translation;
if (mAnimating || !mMenuFadedIn) {
// Don't adjust when animating, or if the menu hasn't been shown yet.
@@ -492,13 +448,15 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
final int x = mIconLocation[0] - mParentLocation[0] + centerX;
final int y = mIconLocation[1] - mParentLocation[1] + centerY;
final int index = mMenuContainer.indexOfChild(v);
mMenuListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
if (mMenuListener != null) {
mMenuListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
}
}
private boolean isMenuLocationChange() {
boolean onLeft = mTranslation > mIconPadding;
boolean onRight = mTranslation < -mIconPadding;
if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
if ((isMenuOnLeft() && onRight) || (!isMenuOnLeft() && onLeft)) {
return true;
}
return false;
@@ -506,7 +464,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
private void setMenuLocation() {
boolean showOnLeft = mTranslation > 0;
if ((mIconsPlaced && showOnLeft == mOnLeft) || mSnapping || mMenuContainer == null
if ((mIconsPlaced && showOnLeft == isMenuOnLeft()) || isSnapping() || mMenuContainer == null
|| !mMenuContainer.isAttachedToWindow()) {
// Do nothing
return;
@@ -522,7 +480,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mIconsPlaced = true;
}
private void setMenuAlpha(float alpha) {
@VisibleForTesting
protected void setMenuAlpha(float alpha) {
mAlpha = alpha;
if (mMenuContainer == null) {
return;
@@ -542,7 +501,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
/**
* Returns the horizontal space in pixels required to display the menu.
*/
private float getSpaceForMenu() {
@VisibleForTesting
protected int getSpaceForMenu() {
return mHorizSpaceForIcon * mMenuContainer.getChildCount();
}
@@ -646,12 +606,71 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
parent.addView(menuView);
menuView.setOnClickListener(this);
FrameLayout.LayoutParams lp = (LayoutParams) menuView.getLayoutParams();
lp.width = (int) mHorizSpaceForIcon;
lp.height = (int) mHorizSpaceForIcon;
lp.width = mHorizSpaceForIcon;
lp.height = mHorizSpaceForIcon;
menuView.setLayoutParams(lp);
}
}
@VisibleForTesting
/**
* Determine the minimum offset below which the menu should snap back closed.
*/
protected float getSnapBackThreshold() {
return getSpaceForMenu() - getMaximumSwipeDistance();
}
/**
* Determine the maximum offset above which the parent notification should be dismissed.
* @return
*/
@VisibleForTesting
protected float getDismissThreshold() {
return getParent().getWidth() * SWIPED_FAR_ENOUGH_SIZE_FRACTION;
}
@Override
public boolean isWithinSnapMenuThreshold() {
float translation = getTranslation();
float snapBackThreshold = getSnapBackThreshold();
float targetRight = getDismissThreshold();
return isMenuOnLeft()
? translation > snapBackThreshold && translation < targetRight
: translation < -snapBackThreshold && translation > -targetRight;
}
@Override
public boolean isSwipedEnoughToShowMenu() {
final float minimumSwipeDistance = getMinimumSwipeDistance();
final float translation = getTranslation();
return isMenuVisible() && (isMenuOnLeft() ?
translation > minimumSwipeDistance
: translation < -minimumSwipeDistance);
}
@Override
public int getMenuSnapTarget() {
return isMenuOnLeft() ? getSpaceForMenu() : -getSpaceForMenu();
}
@Override
public boolean shouldSnapBack() {
float translation = getTranslation();
float targetLeft = getSnapBackThreshold();
return isMenuOnLeft() ? translation < targetLeft : translation > -targetLeft;
}
@Override
public boolean isSnappedAndOnSameSide() {
return isMenuSnapped() && isMenuVisible()
&& isMenuSnappedOnLeft() == isMenuOnLeft();
}
@Override
public boolean canBeDismissed() {
return getParent().canViewBeDismissed();
}
public static class NotificationMenuItem implements MenuItem {
View mMenuView;
GutsContent mGutsContent;

View File

@@ -26,7 +26,6 @@ import android.animation.PropertyValuesHolder;
import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.FloatRange;
import android.annotation.Nullable;
import android.app.WallpaperManager;
import android.content.Context;
@@ -5315,13 +5314,15 @@ public class NotificationStackScrollLayout extends ViewGroup
void flingTopOverscroll(float velocity, boolean open);
}
@ShadeViewRefactor(RefactorComponent.INPUT)
private class NotificationSwipeHelper extends SwipeHelper
@ShadeViewRefactor(RefactorComponent.INPUT)
private class NotificationSwipeHelper extends SwipeHelper
implements NotificationSwipeActionHelper {
private static final long COVER_MENU_DELAY = 4000;
private Runnable mFalsingCheck;
private Handler mHandler;
private static final long SWIPE_MENU_TIMING = 200;
public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
super(swipeDirection, callback, context);
mHandler = new Handler();
@@ -5337,7 +5338,7 @@ public class NotificationStackScrollLayout extends ViewGroup
public void onDownUpdate(View currView, MotionEvent ev) {
mTranslatingParentView = currView;
if (mCurrMenuRow != null) {
mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */);
mCurrMenuRow.onTouchStart();
}
mCurrMenuRow = null;
mHandler.removeCallbacks(mFalsingCheck);
@@ -5350,18 +5351,21 @@ public class NotificationStackScrollLayout extends ViewGroup
if (row.getEntry().hasFinishedInitialization()) {
mCurrMenuRow = row.createMenu();
mCurrMenuRow.setSwipeActionHelper(NotificationSwipeHelper.this);
mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this);
mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */);
mCurrMenuRow.onTouchStart();
}
}
}
private boolean swipedEnoughToShowMenu(NotificationMenuRowPlugin menuRow) {
return !swipedFarEnough() && menuRow.isSwipedEnoughToShowMenu();
}
@Override
public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) {
mHandler.removeCallbacks(mFalsingCheck);
if (mCurrMenuRow != null) {
mCurrMenuRow.onTouchEvent(view, ev, 0 /* velocity */);
mCurrMenuRow.onTouchMove(delta);
}
}
@@ -5369,11 +5373,91 @@ public class NotificationStackScrollLayout extends ViewGroup
public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
float translation) {
if (mCurrMenuRow != null) {
return mCurrMenuRow.onTouchEvent(animView, ev, velocity);
mCurrMenuRow.onTouchEnd();
handleMenuRowSwipe(ev, animView, velocity, mCurrMenuRow);
return true;
}
return false;
}
@Override
public boolean swipedFarEnough(float translation, float viewSize) {
return swipedFarEnough();
}
private void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
if (!menuRow.shouldShowMenu()) {
// If the menu should not be shown, then there is no need to check if the a swipe
// should result in a snapping to the menu. As a result, just check if the swipe
// was enough to dismiss the notification.
if (isDismissGesture(ev)) {
dismiss(animView, velocity);
} else {
snapBack(animView, velocity);
menuRow.onSnapClosed();
}
return;
}
if (menuRow.isSnappedAndOnSameSide()) {
// Menu was snapped to previously and we're on the same side
handleSwipeFromSnap(ev, animView, velocity, menuRow);
} else {
// Menu has not been snapped, or was snapped previously but is now on
// the opposite side.
handleSwipeFromNonSnap(ev, animView, velocity, menuRow);
}
}
private void handleSwipeFromNonSnap(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
boolean isDismissGesture = isDismissGesture(ev);
final boolean gestureTowardsMenu = menuRow.isTowardsMenu(velocity);
final boolean gestureFastEnough =
mSwipeHelper.getMinDismissVelocity() <= Math.abs(velocity);
final double timeForGesture = ev.getEventTime() - ev.getDownTime();
final boolean showMenuForSlowOnGoing = !menuRow.canBeDismissed()
&& timeForGesture >= SWIPE_MENU_TIMING;
if (!isFalseGesture(ev)
&& (swipedEnoughToShowMenu(menuRow)
&& (!gestureFastEnough || showMenuForSlowOnGoing))
|| (gestureTowardsMenu && !isDismissGesture)) {
// Menu has not been snapped to previously and this is menu revealing gesture
snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
menuRow.onSnapOpen();
} else if (isDismissGesture(ev) && !gestureTowardsMenu) {
dismiss(animView, velocity);
menuRow.onDismiss();
} else {
snapBack(animView, velocity);
menuRow.onSnapClosed();
}
}
private void handleSwipeFromSnap(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
boolean isDismissGesture = isDismissGesture(ev);
final boolean withinSnapMenuThreshold =
menuRow.isWithinSnapMenuThreshold();
if (withinSnapMenuThreshold && !isDismissGesture) {
// Haven't moved enough to unsnap from the menu
menuRow.onSnapOpen();
snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
} else if (isDismissGesture && !menuRow.shouldSnapBack()) {
// Only dismiss if we're not moving towards the menu
dismiss(animView, velocity);
menuRow.onDismiss();
} else {
snapBack(animView, velocity);
menuRow.onSnapClosed();
}
}
@Override
public void dismissChild(final View view, float velocity,
boolean useAccelerateInterpolator) {
@@ -5403,10 +5487,6 @@ public class NotificationStackScrollLayout extends ViewGroup
mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
}
public boolean isFalseGesture(MotionEvent ev) {
return super.isFalseGesture(ev);
}
private void handleMenuCoveredOrDismissed() {
if (mMenuExposedView != null && mMenuExposedView == mTranslatingParentView) {
mMenuExposedView = null;
@@ -5440,13 +5520,12 @@ public class NotificationStackScrollLayout extends ViewGroup
}
@Override
public void snap(View animView, float targetLeft, float velocity) {
public void snapOpen(View animView, int targetLeft, float velocity) {
snapChild(animView, targetLeft, velocity);
}
@Override
public boolean swipedFarEnough(float translation, float viewSize) {
return swipedFarEnough();
private void snapBack(View animView, float velocity) {
snapChild(animView, 0, velocity);
}
@Override

View File

@@ -15,10 +15,15 @@
package com.android.systemui.statusbar.notification.row;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doNothing;
import android.app.Notification;
import android.service.notification.StatusBarNotification;
@@ -27,7 +32,6 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
import android.testing.ViewUtils;
import android.view.ViewGroup;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -36,6 +40,7 @@ import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -72,6 +77,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
row.resetMenu();
}
@Test
public void testNoAppOpsInSlowSwipe() {
NotificationMenuRow row = new NotificationMenuRow(mContext);
@@ -86,4 +92,255 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
// one for snooze and one for noti blocking
assertEquals(2, container.getChildCount());
}
@Test
public void testIsSnappedAndOnSameSide() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuSnapped()).thenReturn(true);
when(row.isMenuOnLeft()).thenReturn(true);
when(row.isMenuSnappedOnLeft()).thenReturn(true);
assertTrue("Showing on left and on left", row.isSnappedAndOnSameSide());
when(row.isMenuOnLeft()).thenReturn(false);
when(row.isMenuSnappedOnLeft()).thenReturn(false);
assertTrue("Snapped to right and on right", row.isSnappedAndOnSameSide());
when(row.isMenuOnLeft()).thenReturn(true);
when(row.isMenuSnapped()).thenReturn(false);
assertFalse("Snapped to right and on left", row.isSnappedAndOnSameSide());
when(row.isMenuOnLeft()).thenReturn(true);
when(row.isMenuSnappedOnLeft()).thenReturn(true);
when(row.isMenuVisible()).thenReturn(false);
assertFalse("Snapped to left and on left, but menu not visible",
row.isSnappedAndOnSameSide());
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuSnapped()).thenReturn(false);
assertFalse("Snapped to left and on left, but not actually snapped to",
row.isSnappedAndOnSameSide());
}
@Test
public void testGetMenuSnapTarget() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
when(row.isMenuOnLeft()).thenReturn(true);
doReturn(30).when(row).getSpaceForMenu();
assertEquals("When on left, snap target is space for menu",
30, row.getMenuSnapTarget());
when(row.isMenuOnLeft()).thenReturn(false);
assertEquals("When on right, snap target is negative space for menu",
-30, row.getMenuSnapTarget());
}
@Test
public void testIsSwipedEnoughToShowMenu() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuOnLeft()).thenReturn(true);
doReturn(40f).when(row).getMinimumSwipeDistance();
when(row.getTranslation()).thenReturn(30f);
assertFalse("on left, translation is less than min", row.isSwipedEnoughToShowMenu());
when(row.getTranslation()).thenReturn(50f);
assertTrue("on left, translation is greater than min", row.isSwipedEnoughToShowMenu());
when(row.isMenuOnLeft()).thenReturn(false);
when(row.getTranslation()).thenReturn(-30f);
assertFalse("on right, translation is greater than -min", row.isSwipedEnoughToShowMenu());
when(row.getTranslation()).thenReturn(-50f);
assertTrue("on right, translation is less than -min", row.isSwipedEnoughToShowMenu());
when(row.isMenuVisible()).thenReturn(false);
when(row.getTranslation()).thenReturn(30f);
assertFalse("on left, translation greater than min, but not visible",
row.isSwipedEnoughToShowMenu());
}
@Test
public void testIsWithinSnapMenuThreshold() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
doReturn(30f).when(row).getSnapBackThreshold();
doReturn(50f).when(row).getDismissThreshold();
when(row.isMenuOnLeft()).thenReturn(true);
when(row.getTranslation()).thenReturn(40f);
assertTrue("When on left, translation is between min and max",
row.isWithinSnapMenuThreshold());
when(row.getTranslation()).thenReturn(20f);
assertFalse("When on left, translation is less than min",
row.isWithinSnapMenuThreshold());
when(row.getTranslation()).thenReturn(60f);
assertFalse("When on left, translation is greater than max",
row.isWithinSnapMenuThreshold());
when(row.isMenuOnLeft()).thenReturn(false);
when(row.getTranslation()).thenReturn(-40f);
assertTrue("When on right, translation is between -min and -max",
row.isWithinSnapMenuThreshold());
when(row.getTranslation()).thenReturn(-20f);
assertFalse("When on right, translation is greater than -min",
row.isWithinSnapMenuThreshold());
when(row.getTranslation()).thenReturn(-60f);
assertFalse("When on right, translation is less than -max",
row.isWithinSnapMenuThreshold());
}
@Test
public void testShouldSnapBack() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
doReturn(40f).when(row).getSnapBackThreshold();
when(row.isMenuVisible()).thenReturn(false);
when(row.isMenuOnLeft()).thenReturn(true);
when(row.getTranslation()).thenReturn(50f);
assertFalse("On left, translation greater than minimum target", row.shouldSnapBack());
when(row.getTranslation()).thenReturn(30f);
assertTrue("On left, translation less than minimum target", row.shouldSnapBack());
when(row.isMenuOnLeft()).thenReturn(false);
when(row.getTranslation()).thenReturn(-50f);
assertFalse("On right, translation less than minimum target", row.shouldSnapBack());
when(row.getTranslation()).thenReturn(-30f);
assertTrue("On right, translation greater than minimum target", row.shouldSnapBack());
}
@Test
public void testCanBeDismissed() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
ExpandableNotificationRow parent = mock(ExpandableNotificationRow.class);
when(row.getParent()).thenReturn(parent);
when(parent.canViewBeDismissed()).thenReturn(true);
assertTrue("Row can be dismissed if parent can be dismissed", row.canBeDismissed());
when(parent.canViewBeDismissed()).thenReturn(false);
assertFalse("Row cannot be dismissed if parent cannot be dismissed",
row.canBeDismissed());
}
@Test
public void testIsTowardsMenu() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuOnLeft()).thenReturn(true);
assertTrue("menu on left, movement is negative", row.isTowardsMenu(-30f));
assertFalse("menu on left, movement is positive", row.isTowardsMenu(30f));
assertTrue("menu on left, movement is 0", row.isTowardsMenu(0f));
when(row.isMenuOnLeft()).thenReturn(false);
assertTrue("menu on right, movement is positive", row.isTowardsMenu(30f));
assertFalse("menu on right, movement is negative", row.isTowardsMenu(-30f));
assertTrue("menu on right, movement is 0", row.isTowardsMenu(0f));
when(row.isMenuVisible()).thenReturn(false);
assertFalse("menu on left, movement is negative, but menu not visible",
row.isTowardsMenu(-30f));
}
@Test
public void onSnapBack() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin
.OnMenuEventListener.class);
row.setMenuClickListener(listener);
ExpandableNotificationRow parent = mock(ExpandableNotificationRow.class);
when(row.getParent()).thenReturn(parent);
doNothing().when(row).cancelDrag();
row.onSnapOpen();
assertTrue("before onSnapClosed, row is snapped to", row.isMenuSnapped());
assertFalse("before onSnapClosed, row is not snapping", row.isSnapping());
row.onSnapClosed();
assertFalse("after onSnapClosed, row is not snapped to", row.isMenuSnapped());
assertTrue("after onSnapClosed, row is snapping", row.isSnapping());
}
@Test
public void testOnSnap() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
when(row.isMenuOnLeft()).thenReturn(true);
NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin
.OnMenuEventListener.class);
row.setMenuClickListener(listener);
ExpandableNotificationRow parent = mock(ExpandableNotificationRow.class);
when(row.getParent()).thenReturn(parent);
assertFalse("before onSnapOpen, row is not snapped to", row.isMenuSnapped());
assertFalse("before onSnapOpen, row is not snapped on left", row.isMenuSnappedOnLeft());
row.onSnapOpen();
assertTrue("after onSnapOpen, row is snapped to", row.isMenuSnapped());
assertTrue("after onSnapOpen, row is snapped on left", row.isMenuSnapped());
verify(listener, times(1)).onMenuShown(parent);
}
@Test
public void testOnDismiss() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
doNothing().when(row).cancelDrag();
row.onSnapOpen();
assertFalse("before onDismiss, row is not dismissing", row.isDismissing());
assertTrue("before onDismiss, row is showing", row.isMenuSnapped());
row.onDismiss();
verify(row, times(1)).cancelDrag();
assertTrue("after onDismiss, row is dismissing", row.isDismissing());
assertFalse("after onDismiss, row is not showing", row.isMenuSnapped());
}
@Test
public void testOnDown() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
doNothing().when(row).beginDrag();
row.onTouchStart();
verify(row, times(1)).beginDrag();
}
@Test
public void testOnUp() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
row.onTouchStart();
assertTrue("before onTouchEnd, isUserTouching is true", row.isUserTouching());
row.onTouchEnd();
assertFalse("after onTouchEnd, isUserTouching is false", row.isUserTouching());
}
@Test
public void testIsMenuVisible() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
row.setMenuAlpha(0);
assertFalse("when alpha is 0, menu is not visible", row.isMenuVisible());
row.setMenuAlpha(0.5f);
assertTrue("when alpha is .5, menu is visible", row.isMenuVisible());
}
}