import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
import android.view.DisplayListCanvas;
import android.view.RenderNodeAnimator;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import com.android.systemui.Interpolators;
private float mGlowAlpha = 0f;
private float mGlowScale = 1f;
private boolean mPressed;
- private boolean mVisible;
private boolean mDrawingHardwareGlow;
private int mMaxWidth;
private boolean mLastDark;
private boolean mDark;
- private boolean mDelayTouchFeedback;
private final Interpolator mInterpolator = new LogInterpolator();
private boolean mSupportHardware;
private final View mTargetView;
- private final Handler mHandler = new Handler();
private final HashSet<Animator> mRunningAnimations = new HashSet<>();
private final ArrayList<Animator> mTmpArray = new ArrayList<>();
mDark = darkIntensity >= 0.5f;
}
- public void setDelayTouchFeedback(boolean delay) {
- mDelayTouchFeedback = delay;
- }
-
private Paint getRipplePaint() {
if (mRipplePaint == null) {
mRipplePaint = new Paint();
}
}
- /**
- * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch
- * is enabled.
- */
- public void abortDelayedRipple() {
- mHandler.removeCallbacksAndMessages(null);
- }
-
private void cancelAnimations() {
- mVisible = false;
mTmpArray.addAll(mRunningAnimations);
int size = mTmpArray.size();
for (int i = 0; i < size; i++) {
}
mTmpArray.clear();
mRunningAnimations.clear();
- mHandler.removeCallbacksAndMessages(null);
}
private void setPressedSoftware(boolean pressed) {
if (pressed) {
- if (mDelayTouchFeedback) {
- if (mRunningAnimations.isEmpty()) {
- mHandler.removeCallbacksAndMessages(null);
- mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout());
- } else if (mVisible) {
- enterSoftware();
- }
- } else {
- enterSoftware();
- }
+ enterSoftware();
} else {
exitSoftware();
}
private void enterSoftware() {
cancelAnimations();
- mVisible = true;
mGlowAlpha = getMaxGlowAlpha();
ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
0f, GLOW_MAX_SCALE_FACTOR);
scaleAnimator.addListener(mAnimatorListener);
scaleAnimator.start();
mRunningAnimations.add(scaleAnimator);
-
- // With the delay, it could eventually animate the enter animation with no pressed state,
- // then immediately show the exit animation. If this is skipped there will be no ripple.
- if (mDelayTouchFeedback && !mPressed) {
- exitSoftware();
- }
}
private void exitSoftware() {
private void setPressedHardware(boolean pressed) {
if (pressed) {
- if (mDelayTouchFeedback) {
- if (mRunningAnimations.isEmpty()) {
- mHandler.removeCallbacksAndMessages(null);
- mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout());
- } else if (mVisible) {
- enterHardware();
- }
- } else {
- enterHardware();
- }
+ enterHardware();
} else {
exitHardware();
}
private void enterHardware() {
cancelAnimations();
- mVisible = true;
mDrawingHardwareGlow = true;
setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
mRunningAnimations.add(endAnim);
invalidateSelf();
-
- // With the delay, it could eventually animate the enter animation with no pressed state,
- // then immediately show the exit animation. If this is skipped there will be no ripple.
- if (mDelayTouchFeedback && !mPressed) {
- exitHardware();
- }
}
private void exitHardware() {
public void onAnimationEnd(Animator animation) {
mRunningAnimations.remove(animation);
if (mRunningAnimations.isEmpty() && !mPressed) {
- mVisible = false;
mDrawingHardwareGlow = false;
invalidateSelf();
}
private int mTouchSlop;
private int mTouchDownX;
private int mTouchDownY;
+ private boolean mIsPressed;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
private boolean mGestureAborted;
private final Runnable mCheckLongPress = new Runnable() {
public void run() {
- if (isPressed()) {
+ if (mIsPressed) {
// Log.d("KeyButtonView", "longpressed: " + this);
if (isLongClickable()) {
// Just an old-fashioned ImageView
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
mLongClicked = true;
}
+
+ // Only when quick step is enabled, ripple will not be shown on touch down, then
+ // show the ripple on touch up or on long press
+ if (mLongClicked && mOverviewProxyService.getProxy() != null) {
+ setPressed(true);
+ }
}
}
};
case MotionEvent.ACTION_DOWN:
mDownTime = SystemClock.uptimeMillis();
mLongClicked = false;
- setPressed(true);
mTouchDownX = (int) ev.getX();
mTouchDownY = (int) ev.getY();
if (mCode != 0) {
// Provide the same haptic feedback that the system offers for virtual keys.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
+ mIsPressed = true;
if (isProxyConnected) {
// Provide small vibration for quick step or immediate down feedback
AsyncTask.execute(() ->
.get(VibrationEffect.EFFECT_TICK, false)));
} else {
playSoundEffect(SoundEffectConstants.CLICK);
+ setPressed(mIsPressed);
}
removeCallbacks(mCheckLongPress);
postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
boolean exceededTouchSlopX = Math.abs(x - mTouchDownX) > mTouchSlop;
boolean exceededTouchSlopY = Math.abs(y - mTouchDownY) > mTouchSlop;
if (exceededTouchSlopX || exceededTouchSlopY) {
- setPressed(false);
+ // When quick step is enabled, prevent animating the ripple triggered by
+ // setPressed and decide to run it on touch up
+ mIsPressed = false;
+ if (!isProxyConnected) {
+ setPressed(mIsPressed);
+ }
removeCallbacks(mCheckLongPress);
}
break;
removeCallbacks(mCheckLongPress);
break;
case MotionEvent.ACTION_UP:
- final boolean doIt = isPressed() && !mLongClicked;
- setPressed(false);
+ final boolean doIt = mIsPressed && !mLongClicked;
if (isProxyConnected) {
if (doIt) {
+ // Animate the ripple in on touch up with setPressed and then out later
+ setPressed(true);
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
playSoundEffect(SoundEffectConstants.CLICK);
}
// and it feels weird to sometimes get a release haptic and other times not.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
}
+ setPressed(false);
if (mCode != 0) {
if (doIt) {
// If there was a pending remote recents animation, then we need to
mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser());
}
+ @Override
+ public void setPressed(boolean pressed) {
+ mIsPressed = pressed;
+ super.setPressed(pressed);
+ }
+
public void sendEvent(int action, int flags) {
sendEvent(action, flags, SystemClock.uptimeMillis());
}
@Override
public void abortCurrentGesture() {
setPressed(false);
- mRipple.abortDelayedRipple();
mGestureAborted = true;
}
@Override
public void setDelayTouchFeedback(boolean shouldDelay) {
- mRipple.setDelayTouchFeedback(shouldDelay);
}
@Override