Loading packages/SystemUI/src/com/android/systemui/SwipeHelper.java +48 −27 Original line number Diff line number Diff line Loading @@ -517,35 +517,16 @@ public class SwipeHelper implements Gefingerpoken { break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mCurrView != null) { float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale; mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity); float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale; float velocity = getVelocity(mVelocityTracker); float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); float translation = getTranslation(mCurrView); // Decide whether to dismiss the current view boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView); boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) && (Math.abs(velocity) > Math.abs(perpendicularVelocity)) && (velocity > 0) == (translation > 0); boolean falsingDetected = mCallback.isAntiFalsingNeeded(); if (mFalsingManager.isClassiferEnabled()) { falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(); } else { falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold; if (mCurrView == null) { break; } mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity()); float velocity = getVelocity(mVelocityTracker); boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) && !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough) && ev.getActionMasked() == MotionEvent.ACTION_UP; if (dismissChild) { if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) { if (isDismissGesture(ev)) { // flingadingy dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f); dismissChild(mCurrView, swipedFastEnough() ? velocity : 0f); } else { // snappity mCallback.onDragCancelled(mCurrView); Loading @@ -562,6 +543,46 @@ public class SwipeHelper implements Gefingerpoken { return (int) (mFalsingThreshold * factor); } private float getMaxVelocity() { return MAX_DISMISS_VELOCITY * mDensityScale; } protected float getEscapeVelocity() { return SWIPE_ESCAPE_VELOCITY * mDensityScale; } protected boolean swipedFarEnough() { float translation = getTranslation(mCurrView); return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView); } protected boolean isDismissGesture(MotionEvent ev) { boolean falsingDetected = mCallback.isAntiFalsingNeeded(); if (mFalsingManager.isClassiferEnabled()) { falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(); } else { falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold; } return !falsingDetected && (swipedFastEnough() || swipedFarEnough()) && ev.getActionMasked() == MotionEvent.ACTION_UP && mCallback.canChildBeDismissed(mCurrView); } protected boolean swipedFastEnough() { float velocity = getVelocity(mVelocityTracker); float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); float translation = getTranslation(mCurrView); boolean ret = (Math.abs(velocity) > getEscapeVelocity()) && (Math.abs(velocity) > Math.abs(perpendicularVelocity)) && (velocity > 0) == (translation > 0); return ret; } protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity, float translation) { return false; } public interface Callback { View getChildAtPosition(MotionEvent ev); Loading packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java +62 −22 Original line number Diff line number Diff line Loading @@ -29,11 +29,18 @@ import com.android.systemui.R; public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener { private static final int GEAR_ALPHA_ANIM_DURATION = 200; public interface SettingsIconRowListener { /** * Called when the gear behind a notification is touched. */ public void onGearTouched(ExpandableNotificationRow row, int x, int y); /** * Called when a notification is slid back over the gear. */ public void onSettingsIconRowReset(NotificationSettingsIconRow row); } private ExpandableNotificationRow mParent; Loading @@ -45,6 +52,8 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC private boolean mSettingsFadedIn = false; private boolean mAnimating = false; private boolean mOnLeft = true; private boolean mDismissing = false; private boolean mSnapping = false; private int[] mGearLocation = new int[2]; private int[] mParentLocation = new int[2]; Loading Loading @@ -78,8 +87,14 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC public void resetState() { setGearAlpha(0f); mSettingsFadedIn = false; mAnimating = false; mSnapping = false; mDismissing = false; setIconLocation(true /* on left */); if (mListener != null) { mListener.onSettingsIconRowReset(this); } } public void setGearListener(SettingsIconRowListener listener) { Loading @@ -94,19 +109,23 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC return mParent; } private void setGearAlpha(float alpha) { public void setGearAlpha(float alpha) { if (alpha == 0) { mSettingsFadedIn = false; // Can fade in again once it's gone. setVisibility(View.INVISIBLE); } else { if (alpha == 1) { mSettingsFadedIn = true; } setVisibility(View.VISIBLE); } mGearIcon.setAlpha(alpha); } /** * Returns whether the icon is on the left side of the view or not. */ public boolean isIconOnLeft() { return mOnLeft; } /** * Returns the horizontal space in pixels required to display the gear behind a notification. */ Loading @@ -119,7 +138,7 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC * if entire view is visible. */ public boolean isVisible() { return mSettingsFadedIn; return mGearIcon.getAlpha() > 0; } public void cancelFadeAnimator() { Loading @@ -129,16 +148,18 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC } public void updateSettingsIcons(final float transX, final float size) { if (mAnimating || (mGearIcon.getAlpha() == 0)) { // Don't adjust when animating or settings aren't visible if (mAnimating || !mSettingsFadedIn) { // Don't adjust when animating, or if the gear hasn't been shown yet. return; } setIconLocation(transX > 0 /* fromLeft */); final float fadeThreshold = size * 0.3f; final float absTrans = Math.abs(transX); float desiredAlpha = 0; if (absTrans <= fadeThreshold) { if (absTrans == 0) { desiredAlpha = 0; } else if (absTrans <= fadeThreshold) { desiredAlpha = 1; } else { desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold)); Loading @@ -148,6 +169,12 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC public void fadeInSettings(final boolean fromLeft, final float transX, final float notiThreshold) { if (mDismissing || mAnimating) { return; } if (isIconLocationChange(transX)) { setGearAlpha(0f); } setIconLocation(transX > 0 /* fromLeft */); mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1); mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { Loading @@ -164,40 +191,53 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC }); mFadeAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); mAnimating = false; mSettingsFadedIn = false; public void onAnimationStart(Animator animation) { mAnimating = true; } @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); mAnimating = true; public void onAnimationCancel(Animator animation) { // TODO should animate back to 0f from current alpha mGearIcon.setAlpha(0f); } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mAnimating = false; mSettingsFadedIn = true; mSettingsFadedIn = mGearIcon.getAlpha() == 1; } }); mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN); mFadeAnimator.setDuration(200); mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION); mFadeAnimator.start(); } private void setIconLocation(boolean onLeft) { if (onLeft == mOnLeft) { public void setIconLocation(boolean onLeft) { if (onLeft == mOnLeft || mSnapping) { // Same side? Do nothing. return; } setTranslationX(onLeft ? 0 : (mParent.getWidth() - mHorizSpaceForGear)); mOnLeft = onLeft; } public boolean isIconLocationChange(float translation) { boolean onLeft = translation > mGearIcon.getPaddingStart(); boolean onRight = translation < -mGearIcon.getPaddingStart(); if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) { return true; } return false; } public void setDismissing() { mDismissing = true; } public void setSnapping(boolean snapping) { mSnapping = snapping; } @Override public void onClick(View v) { if (v.getId() == R.id.gear_icon) { Loading packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +148 −43 Original line number Diff line number Diff line Loading @@ -371,6 +371,11 @@ public class NotificationStackScrollLayout extends ViewGroup } } @Override public void onSettingsIconRowReset(NotificationSettingsIconRow row) { mSwipeHelper.setSnappedToGear(false); } @Override protected void onDraw(Canvas canvas) { canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint); Loading Loading @@ -717,12 +722,16 @@ public class NotificationStackScrollLayout extends ViewGroup mDragAnimPendingChildren.remove(animView); } if (targetLeft == 0 && mCurrIconRow != null) { if (mCurrIconRow != null) { if (targetLeft == 0) { mCurrIconRow.resetState(); mCurrIconRow = null; if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) { mGearExposedView = null; } } else { mSwipeHelper.setSnappedToGear(true); } } } Loading Loading @@ -3379,15 +3388,11 @@ public class NotificationStackScrollLayout extends ViewGroup } private class NotificationSwipeHelper extends SwipeHelper { private static final int MOVE_STATE_LEFT = -1; private static final int MOVE_STATE_UNDEFINED = 0; private static final int MOVE_STATE_RIGHT = 1; private static final long GEAR_SHOW_DELAY = 60; private CheckForDrag mCheckForDrag; private Handler mHandler; private int mMoveState = MOVE_STATE_UNDEFINED; private boolean mGearSnappedTo; private boolean mGearSnappedOnLeft; public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) { super(swipeDirection, callback, context); Loading @@ -3400,6 +3405,10 @@ public class NotificationStackScrollLayout extends ViewGroup mTranslatingParentView = currView; // Reset check for drag gesture cancelCheckForDrag(); if (mCurrIconRow != null) { mCurrIconRow.setSnapping(false); } mCheckForDrag = null; mCurrIconRow = null; Loading @@ -3411,17 +3420,32 @@ public class NotificationStackScrollLayout extends ViewGroup mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow(); mCurrIconRow.setGearListener(NotificationStackScrollLayout.this); } mMoveState = MOVE_STATE_UNDEFINED; } @Override public void onMoveUpdate(View view, float translation, float delta) { final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT; if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) { // Changed directions, make sure we check for drag again. if (mCurrIconRow != null) { mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping. // If the gear is visible and the movement is towards it it's not a location change. boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft(); boolean locationChange = isTowardsGear(translation, onLeft) ? false : mCurrIconRow.isIconLocationChange(translation); if (locationChange) { // Don't consider it "snapped" if location has changed. setSnappedToGear(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 mCurrIconRow.setGearAlpha(0f); mCurrIconRow.setIconLocation(translation > 0 /* onLeft */); } } } mMoveState = newMoveState; final boolean gutsExposed = (view instanceof ExpandableNotificationRow) && ((ExpandableNotificationRow) view).areGutsExposed(); Loading @@ -3434,35 +3458,99 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void dismissChild(final View view, float velocity) { cancelCheckForDrag(); super.dismissChild(view, velocity); cancelCheckForDrag(); setSnappedToGear(false); } @Override public void snapChild(final View animView, final float targetLeft, float velocity) { final float snapBackThreshold = getSpaceForGear(animView); final float translation = getTranslation(animView); final boolean fromLeft = translation > 0; final float absTrans = Math.abs(translation); final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; super.snapChild(animView, targetLeft, velocity); if (targetLeft == 0) { cancelCheckForDrag(); setSnappedToGear(false); } } boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f && translation <= notiThreshold) || (!fromLeft && absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold); if (pastGear && !isPinnedHeadsUp(animView) && (animView instanceof ExpandableNotificationRow)) { // bouncity final float target = fromLeft ? snapBackThreshold : -snapBackThreshold; @Override public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, float translation) { if (mCurrIconRow == null) { cancelCheckForDrag(); return false; // Let SwipeHelper handle it. } boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft()); boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity(); if (mGearSnappedTo && mCurrIconRow.isVisible()) { if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) { boolean coveringGear = Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f; if (gestureTowardsGear || coveringGear) { // Gesture is towards or covering the gear snapChild(animView, 0 /* leftTarget */, velocity); } else if (isDismissGesture(ev)) { // Gesture is a dismiss that's not towards the gear dismissChild(animView, swipedFastEnough() ? velocity : 0f); } else { // Didn't move enough to dismiss or cover, snap to the gear snapToGear(animView, velocity); } } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) || (gestureTowardsGear && !swipedFarEnough())) { // The gear has been snapped to previously, however, the gear is now on the // other side. If gesture is towards gear and not too far snap to the gear. snapToGear(animView, velocity); } else { dismissOrSnapBack(animView, velocity, ev); } } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) || gestureTowardsGear) { // Gear has not been snapped to previously and this is gear revealing gesture snapToGear(animView, velocity); } else { dismissOrSnapBack(animView, velocity, ev); } return true; } private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) { if (isDismissGesture(ev)) { dismissChild(animView, swipedFastEnough() ? velocity : 0f); } else { snapChild(animView, 0 /* leftTarget */, velocity); } } private void snapToGear(View animView, float velocity) { final float snapBackThreshold = getSpaceForGear(animView); final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold : -snapBackThreshold; mGearExposedView = mTranslatingParentView; if (mGearDisplayedListener != null) { if (mGearDisplayedListener != null && (animView instanceof ExpandableNotificationRow)) { mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView); } if (mCurrIconRow != null) { mCurrIconRow.setSnapping(true); setSnappedToGear(true); } super.snapChild(animView, target, velocity); } else { super.snapChild(animView, 0, velocity); } private boolean swipedEnoughToShowGear(View animView) { final float snapBackThreshold = getSpaceForGear(animView); final float translation = getTranslation(animView); final boolean fromLeft = translation > 0; final float absTrans = Math.abs(translation); final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; // If the notification can't be dismissed then how far it can move is // restricted -- reduce the distance it needs to move in this case. final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f; return absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold; } @Override Loading Loading @@ -3498,6 +3586,25 @@ public class NotificationStackScrollLayout extends ViewGroup } } /** * Returns whether the gesture is towards the gear location or not. */ private boolean isTowardsGear(float velocity, boolean onLeft) { if (mCurrIconRow == null) { return false; } return mCurrIconRow.isVisible() && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0)); } /** * Indicates the the gear has been snapped to. */ private void setSnappedToGear(boolean snapped) { mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false; mGearSnappedTo = snapped && mCurrIconRow != null; } /** * Returns the horizontal space in pixels required to display the gear behind a * notification. Loading @@ -3510,7 +3617,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void checkForDrag() { if (mCheckForDrag == null) { if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) { mCheckForDrag = new CheckForDrag(); mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY); } Loading @@ -3521,7 +3628,6 @@ public class NotificationStackScrollLayout extends ViewGroup mCurrIconRow.cancelFadeAnimator(); } mHandler.removeCallbacks(mCheckForDrag); mCheckForDrag = null; } private final class CheckForDrag implements Runnable { Loading @@ -3531,14 +3637,13 @@ public class NotificationStackScrollLayout extends ViewGroup final float absTransX = Math.abs(translation); final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView); final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible() || mCurrIconRow.isIconLocationChange(translation))) && absTransX >= bounceBackToGearWidth * 0.4 && absTransX < notiThreshold) { // Show icon // Fade in the gear mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation, notiThreshold); } else { // Allow more to be posted if this wasn't a drag. mCheckForDrag = null; } } } Loading @@ -3551,7 +3656,7 @@ public class NotificationStackScrollLayout extends ViewGroup final View prevGearExposedView = mGearExposedView; mGearExposedView = null; mGearSnappedTo = false; Animator anim = getViewTranslationAnimator(prevGearExposedView, 0 /* leftTarget */, null /* updateListener */); if (anim != null) { Loading Loading
packages/SystemUI/src/com/android/systemui/SwipeHelper.java +48 −27 Original line number Diff line number Diff line Loading @@ -517,35 +517,16 @@ public class SwipeHelper implements Gefingerpoken { break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mCurrView != null) { float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale; mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity); float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale; float velocity = getVelocity(mVelocityTracker); float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); float translation = getTranslation(mCurrView); // Decide whether to dismiss the current view boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView); boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) && (Math.abs(velocity) > Math.abs(perpendicularVelocity)) && (velocity > 0) == (translation > 0); boolean falsingDetected = mCallback.isAntiFalsingNeeded(); if (mFalsingManager.isClassiferEnabled()) { falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(); } else { falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold; if (mCurrView == null) { break; } mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity()); float velocity = getVelocity(mVelocityTracker); boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) && !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough) && ev.getActionMasked() == MotionEvent.ACTION_UP; if (dismissChild) { if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) { if (isDismissGesture(ev)) { // flingadingy dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f); dismissChild(mCurrView, swipedFastEnough() ? velocity : 0f); } else { // snappity mCallback.onDragCancelled(mCurrView); Loading @@ -562,6 +543,46 @@ public class SwipeHelper implements Gefingerpoken { return (int) (mFalsingThreshold * factor); } private float getMaxVelocity() { return MAX_DISMISS_VELOCITY * mDensityScale; } protected float getEscapeVelocity() { return SWIPE_ESCAPE_VELOCITY * mDensityScale; } protected boolean swipedFarEnough() { float translation = getTranslation(mCurrView); return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView); } protected boolean isDismissGesture(MotionEvent ev) { boolean falsingDetected = mCallback.isAntiFalsingNeeded(); if (mFalsingManager.isClassiferEnabled()) { falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(); } else { falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold; } return !falsingDetected && (swipedFastEnough() || swipedFarEnough()) && ev.getActionMasked() == MotionEvent.ACTION_UP && mCallback.canChildBeDismissed(mCurrView); } protected boolean swipedFastEnough() { float velocity = getVelocity(mVelocityTracker); float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); float translation = getTranslation(mCurrView); boolean ret = (Math.abs(velocity) > getEscapeVelocity()) && (Math.abs(velocity) > Math.abs(perpendicularVelocity)) && (velocity > 0) == (translation > 0); return ret; } protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity, float translation) { return false; } public interface Callback { View getChildAtPosition(MotionEvent ev); Loading
packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java +62 −22 Original line number Diff line number Diff line Loading @@ -29,11 +29,18 @@ import com.android.systemui.R; public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener { private static final int GEAR_ALPHA_ANIM_DURATION = 200; public interface SettingsIconRowListener { /** * Called when the gear behind a notification is touched. */ public void onGearTouched(ExpandableNotificationRow row, int x, int y); /** * Called when a notification is slid back over the gear. */ public void onSettingsIconRowReset(NotificationSettingsIconRow row); } private ExpandableNotificationRow mParent; Loading @@ -45,6 +52,8 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC private boolean mSettingsFadedIn = false; private boolean mAnimating = false; private boolean mOnLeft = true; private boolean mDismissing = false; private boolean mSnapping = false; private int[] mGearLocation = new int[2]; private int[] mParentLocation = new int[2]; Loading Loading @@ -78,8 +87,14 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC public void resetState() { setGearAlpha(0f); mSettingsFadedIn = false; mAnimating = false; mSnapping = false; mDismissing = false; setIconLocation(true /* on left */); if (mListener != null) { mListener.onSettingsIconRowReset(this); } } public void setGearListener(SettingsIconRowListener listener) { Loading @@ -94,19 +109,23 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC return mParent; } private void setGearAlpha(float alpha) { public void setGearAlpha(float alpha) { if (alpha == 0) { mSettingsFadedIn = false; // Can fade in again once it's gone. setVisibility(View.INVISIBLE); } else { if (alpha == 1) { mSettingsFadedIn = true; } setVisibility(View.VISIBLE); } mGearIcon.setAlpha(alpha); } /** * Returns whether the icon is on the left side of the view or not. */ public boolean isIconOnLeft() { return mOnLeft; } /** * Returns the horizontal space in pixels required to display the gear behind a notification. */ Loading @@ -119,7 +138,7 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC * if entire view is visible. */ public boolean isVisible() { return mSettingsFadedIn; return mGearIcon.getAlpha() > 0; } public void cancelFadeAnimator() { Loading @@ -129,16 +148,18 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC } public void updateSettingsIcons(final float transX, final float size) { if (mAnimating || (mGearIcon.getAlpha() == 0)) { // Don't adjust when animating or settings aren't visible if (mAnimating || !mSettingsFadedIn) { // Don't adjust when animating, or if the gear hasn't been shown yet. return; } setIconLocation(transX > 0 /* fromLeft */); final float fadeThreshold = size * 0.3f; final float absTrans = Math.abs(transX); float desiredAlpha = 0; if (absTrans <= fadeThreshold) { if (absTrans == 0) { desiredAlpha = 0; } else if (absTrans <= fadeThreshold) { desiredAlpha = 1; } else { desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold)); Loading @@ -148,6 +169,12 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC public void fadeInSettings(final boolean fromLeft, final float transX, final float notiThreshold) { if (mDismissing || mAnimating) { return; } if (isIconLocationChange(transX)) { setGearAlpha(0f); } setIconLocation(transX > 0 /* fromLeft */); mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1); mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { Loading @@ -164,40 +191,53 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC }); mFadeAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); mAnimating = false; mSettingsFadedIn = false; public void onAnimationStart(Animator animation) { mAnimating = true; } @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); mAnimating = true; public void onAnimationCancel(Animator animation) { // TODO should animate back to 0f from current alpha mGearIcon.setAlpha(0f); } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mAnimating = false; mSettingsFadedIn = true; mSettingsFadedIn = mGearIcon.getAlpha() == 1; } }); mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN); mFadeAnimator.setDuration(200); mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION); mFadeAnimator.start(); } private void setIconLocation(boolean onLeft) { if (onLeft == mOnLeft) { public void setIconLocation(boolean onLeft) { if (onLeft == mOnLeft || mSnapping) { // Same side? Do nothing. return; } setTranslationX(onLeft ? 0 : (mParent.getWidth() - mHorizSpaceForGear)); mOnLeft = onLeft; } public boolean isIconLocationChange(float translation) { boolean onLeft = translation > mGearIcon.getPaddingStart(); boolean onRight = translation < -mGearIcon.getPaddingStart(); if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) { return true; } return false; } public void setDismissing() { mDismissing = true; } public void setSnapping(boolean snapping) { mSnapping = snapping; } @Override public void onClick(View v) { if (v.getId() == R.id.gear_icon) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +148 −43 Original line number Diff line number Diff line Loading @@ -371,6 +371,11 @@ public class NotificationStackScrollLayout extends ViewGroup } } @Override public void onSettingsIconRowReset(NotificationSettingsIconRow row) { mSwipeHelper.setSnappedToGear(false); } @Override protected void onDraw(Canvas canvas) { canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint); Loading Loading @@ -717,12 +722,16 @@ public class NotificationStackScrollLayout extends ViewGroup mDragAnimPendingChildren.remove(animView); } if (targetLeft == 0 && mCurrIconRow != null) { if (mCurrIconRow != null) { if (targetLeft == 0) { mCurrIconRow.resetState(); mCurrIconRow = null; if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) { mGearExposedView = null; } } else { mSwipeHelper.setSnappedToGear(true); } } } Loading Loading @@ -3379,15 +3388,11 @@ public class NotificationStackScrollLayout extends ViewGroup } private class NotificationSwipeHelper extends SwipeHelper { private static final int MOVE_STATE_LEFT = -1; private static final int MOVE_STATE_UNDEFINED = 0; private static final int MOVE_STATE_RIGHT = 1; private static final long GEAR_SHOW_DELAY = 60; private CheckForDrag mCheckForDrag; private Handler mHandler; private int mMoveState = MOVE_STATE_UNDEFINED; private boolean mGearSnappedTo; private boolean mGearSnappedOnLeft; public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) { super(swipeDirection, callback, context); Loading @@ -3400,6 +3405,10 @@ public class NotificationStackScrollLayout extends ViewGroup mTranslatingParentView = currView; // Reset check for drag gesture cancelCheckForDrag(); if (mCurrIconRow != null) { mCurrIconRow.setSnapping(false); } mCheckForDrag = null; mCurrIconRow = null; Loading @@ -3411,17 +3420,32 @@ public class NotificationStackScrollLayout extends ViewGroup mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow(); mCurrIconRow.setGearListener(NotificationStackScrollLayout.this); } mMoveState = MOVE_STATE_UNDEFINED; } @Override public void onMoveUpdate(View view, float translation, float delta) { final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT; if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) { // Changed directions, make sure we check for drag again. if (mCurrIconRow != null) { mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping. // If the gear is visible and the movement is towards it it's not a location change. boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft(); boolean locationChange = isTowardsGear(translation, onLeft) ? false : mCurrIconRow.isIconLocationChange(translation); if (locationChange) { // Don't consider it "snapped" if location has changed. setSnappedToGear(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 mCurrIconRow.setGearAlpha(0f); mCurrIconRow.setIconLocation(translation > 0 /* onLeft */); } } } mMoveState = newMoveState; final boolean gutsExposed = (view instanceof ExpandableNotificationRow) && ((ExpandableNotificationRow) view).areGutsExposed(); Loading @@ -3434,35 +3458,99 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void dismissChild(final View view, float velocity) { cancelCheckForDrag(); super.dismissChild(view, velocity); cancelCheckForDrag(); setSnappedToGear(false); } @Override public void snapChild(final View animView, final float targetLeft, float velocity) { final float snapBackThreshold = getSpaceForGear(animView); final float translation = getTranslation(animView); final boolean fromLeft = translation > 0; final float absTrans = Math.abs(translation); final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; super.snapChild(animView, targetLeft, velocity); if (targetLeft == 0) { cancelCheckForDrag(); setSnappedToGear(false); } } boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f && translation <= notiThreshold) || (!fromLeft && absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold); if (pastGear && !isPinnedHeadsUp(animView) && (animView instanceof ExpandableNotificationRow)) { // bouncity final float target = fromLeft ? snapBackThreshold : -snapBackThreshold; @Override public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, float translation) { if (mCurrIconRow == null) { cancelCheckForDrag(); return false; // Let SwipeHelper handle it. } boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft()); boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity(); if (mGearSnappedTo && mCurrIconRow.isVisible()) { if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) { boolean coveringGear = Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f; if (gestureTowardsGear || coveringGear) { // Gesture is towards or covering the gear snapChild(animView, 0 /* leftTarget */, velocity); } else if (isDismissGesture(ev)) { // Gesture is a dismiss that's not towards the gear dismissChild(animView, swipedFastEnough() ? velocity : 0f); } else { // Didn't move enough to dismiss or cover, snap to the gear snapToGear(animView, velocity); } } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) || (gestureTowardsGear && !swipedFarEnough())) { // The gear has been snapped to previously, however, the gear is now on the // other side. If gesture is towards gear and not too far snap to the gear. snapToGear(animView, velocity); } else { dismissOrSnapBack(animView, velocity, ev); } } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) || gestureTowardsGear) { // Gear has not been snapped to previously and this is gear revealing gesture snapToGear(animView, velocity); } else { dismissOrSnapBack(animView, velocity, ev); } return true; } private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) { if (isDismissGesture(ev)) { dismissChild(animView, swipedFastEnough() ? velocity : 0f); } else { snapChild(animView, 0 /* leftTarget */, velocity); } } private void snapToGear(View animView, float velocity) { final float snapBackThreshold = getSpaceForGear(animView); final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold : -snapBackThreshold; mGearExposedView = mTranslatingParentView; if (mGearDisplayedListener != null) { if (mGearDisplayedListener != null && (animView instanceof ExpandableNotificationRow)) { mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView); } if (mCurrIconRow != null) { mCurrIconRow.setSnapping(true); setSnappedToGear(true); } super.snapChild(animView, target, velocity); } else { super.snapChild(animView, 0, velocity); } private boolean swipedEnoughToShowGear(View animView) { final float snapBackThreshold = getSpaceForGear(animView); final float translation = getTranslation(animView); final boolean fromLeft = translation > 0; final float absTrans = Math.abs(translation); final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; // If the notification can't be dismissed then how far it can move is // restricted -- reduce the distance it needs to move in this case. final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f; return absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold; } @Override Loading Loading @@ -3498,6 +3586,25 @@ public class NotificationStackScrollLayout extends ViewGroup } } /** * Returns whether the gesture is towards the gear location or not. */ private boolean isTowardsGear(float velocity, boolean onLeft) { if (mCurrIconRow == null) { return false; } return mCurrIconRow.isVisible() && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0)); } /** * Indicates the the gear has been snapped to. */ private void setSnappedToGear(boolean snapped) { mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false; mGearSnappedTo = snapped && mCurrIconRow != null; } /** * Returns the horizontal space in pixels required to display the gear behind a * notification. Loading @@ -3510,7 +3617,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void checkForDrag() { if (mCheckForDrag == null) { if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) { mCheckForDrag = new CheckForDrag(); mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY); } Loading @@ -3521,7 +3628,6 @@ public class NotificationStackScrollLayout extends ViewGroup mCurrIconRow.cancelFadeAnimator(); } mHandler.removeCallbacks(mCheckForDrag); mCheckForDrag = null; } private final class CheckForDrag implements Runnable { Loading @@ -3531,14 +3637,13 @@ public class NotificationStackScrollLayout extends ViewGroup final float absTransX = Math.abs(translation); final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView); final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible() || mCurrIconRow.isIconLocationChange(translation))) && absTransX >= bounceBackToGearWidth * 0.4 && absTransX < notiThreshold) { // Show icon // Fade in the gear mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation, notiThreshold); } else { // Allow more to be posted if this wasn't a drag. mCheckForDrag = null; } } } Loading @@ -3551,7 +3656,7 @@ public class NotificationStackScrollLayout extends ViewGroup final View prevGearExposedView = mGearExposedView; mGearExposedView = null; mGearSnappedTo = false; Animator anim = getViewTranslationAnimator(prevGearExposedView, 0 /* leftTarget */, null /* updateListener */); if (anim != null) { Loading