| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.widget; |
| |
| import java.util.ArrayList; |
| |
| import android.animation.ValueAnimator; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Canvas; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.os.UserHandle; |
| import android.os.Vibrator; |
| import android.provider.Settings; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.accessibility.AccessibilityManager; |
| |
| import com.android.internal.R; |
| |
| /** |
| * A special widget containing a center and outer ring. Moving the center ring to the outer ring |
| * causes an event that can be caught by implementing OnTriggerListener. |
| */ |
| public class WaveView extends View implements ValueAnimator.AnimatorUpdateListener { |
| private static final String TAG = "WaveView"; |
| private static final boolean DBG = false; |
| private static final int WAVE_COUNT = 20; // default wave count |
| private static final long VIBRATE_SHORT = 20; // msec |
| private static final long VIBRATE_LONG = 20; // msec |
| |
| // Lock state machine states |
| private static final int STATE_RESET_LOCK = 0; |
| private static final int STATE_READY = 1; |
| private static final int STATE_START_ATTEMPT = 2; |
| private static final int STATE_ATTEMPTING = 3; |
| private static final int STATE_UNLOCK_ATTEMPT = 4; |
| private static final int STATE_UNLOCK_SUCCESS = 5; |
| |
| // Animation properties. |
| private static final long DURATION = 300; // duration of transitional animations |
| private static final long FINAL_DURATION = 200; // duration of final animations when unlocking |
| private static final long RING_DELAY = 1300; // when to start fading animated rings |
| private static final long FINAL_DELAY = 200; // delay for unlock success animation |
| private static final long SHORT_DELAY = 100; // for starting one animation after another. |
| private static final long WAVE_DURATION = 2000; // amount of time for way to expand/decay |
| private static final long RESET_TIMEOUT = 3000; // elapsed time of inactivity before we reset |
| private static final long DELAY_INCREMENT = 15; // increment per wave while tracking motion |
| private static final long DELAY_INCREMENT2 = 12; // increment per wave while not tracking |
| private static final long WAVE_DELAY = WAVE_DURATION / WAVE_COUNT; // initial propagation delay |
| |
| /** |
| * The scale by which to multiply the unlock handle width to compute the radius |
| * in which it can be grabbed when accessibility is disabled. |
| */ |
| private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED = 0.5f; |
| |
| /** |
| * The scale by which to multiply the unlock handle width to compute the radius |
| * in which it can be grabbed when accessibility is enabled (more generous). |
| */ |
| private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.0f; |
| |
| private Vibrator mVibrator; |
| private OnTriggerListener mOnTriggerListener; |
| private ArrayList<DrawableHolder> mDrawables = new ArrayList<DrawableHolder>(3); |
| private ArrayList<DrawableHolder> mLightWaves = new ArrayList<DrawableHolder>(WAVE_COUNT); |
| private boolean mFingerDown = false; |
| private float mRingRadius = 182.0f; // Radius of bitmap ring. Used to snap halo to it |
| private int mSnapRadius = 136; // minimum threshold for drag unlock |
| private int mWaveCount = WAVE_COUNT; // number of waves |
| private long mWaveTimerDelay = WAVE_DELAY; |
| private int mCurrentWave = 0; |
| private float mLockCenterX; // center of widget as dictated by widget size |
| private float mLockCenterY; |
| private float mMouseX; // current mouse position as of last touch event |
| private float mMouseY; |
| private DrawableHolder mUnlockRing; |
| private DrawableHolder mUnlockDefault; |
| private DrawableHolder mUnlockHalo; |
| private int mLockState = STATE_RESET_LOCK; |
| private int mGrabbedState = OnTriggerListener.NO_HANDLE; |
| private boolean mWavesRunning; |
| private boolean mFinishWaves; |
| |
| public WaveView(Context context) { |
| this(context, null); |
| } |
| |
| public WaveView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| |
| // TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveView); |
| // mOrientation = a.getInt(R.styleable.WaveView_orientation, HORIZONTAL); |
| // a.recycle(); |
| |
| initDrawables(); |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| mLockCenterX = 0.5f * w; |
| mLockCenterY = 0.5f * h; |
| super.onSizeChanged(w, h, oldw, oldh); |
| } |
| |
| @Override |
| protected int getSuggestedMinimumWidth() { |
| // View should be large enough to contain the unlock ring + halo |
| return mUnlockRing.getWidth() + mUnlockHalo.getWidth(); |
| } |
| |
| @Override |
| protected int getSuggestedMinimumHeight() { |
| // View should be large enough to contain the unlock ring + halo |
| return mUnlockRing.getHeight() + mUnlockHalo.getHeight(); |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); |
| int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); |
| int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); |
| int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); |
| int width; |
| int height; |
| |
| if (widthSpecMode == MeasureSpec.AT_MOST) { |
| width = Math.min(widthSpecSize, getSuggestedMinimumWidth()); |
| } else if (widthSpecMode == MeasureSpec.EXACTLY) { |
| width = widthSpecSize; |
| } else { |
| width = getSuggestedMinimumWidth(); |
| } |
| |
| if (heightSpecMode == MeasureSpec.AT_MOST) { |
| height = Math.min(heightSpecSize, getSuggestedMinimumWidth()); |
| } else if (heightSpecMode == MeasureSpec.EXACTLY) { |
| height = heightSpecSize; |
| } else { |
| height = getSuggestedMinimumHeight(); |
| } |
| |
| setMeasuredDimension(width, height); |
| } |
| |
| private void initDrawables() { |
| mUnlockRing = new DrawableHolder(createDrawable(R.drawable.unlock_ring)); |
| mUnlockRing.setX(mLockCenterX); |
| mUnlockRing.setY(mLockCenterY); |
| mUnlockRing.setScaleX(0.1f); |
| mUnlockRing.setScaleY(0.1f); |
| mUnlockRing.setAlpha(0.0f); |
| mDrawables.add(mUnlockRing); |
| |
| mUnlockDefault = new DrawableHolder(createDrawable(R.drawable.unlock_default)); |
| mUnlockDefault.setX(mLockCenterX); |
| mUnlockDefault.setY(mLockCenterY); |
| mUnlockDefault.setScaleX(0.1f); |
| mUnlockDefault.setScaleY(0.1f); |
| mUnlockDefault.setAlpha(0.0f); |
| mDrawables.add(mUnlockDefault); |
| |
| mUnlockHalo = new DrawableHolder(createDrawable(R.drawable.unlock_halo)); |
| mUnlockHalo.setX(mLockCenterX); |
| mUnlockHalo.setY(mLockCenterY); |
| mUnlockHalo.setScaleX(0.1f); |
| mUnlockHalo.setScaleY(0.1f); |
| mUnlockHalo.setAlpha(0.0f); |
| mDrawables.add(mUnlockHalo); |
| |
| BitmapDrawable wave = createDrawable(R.drawable.unlock_wave); |
| for (int i = 0; i < mWaveCount; i++) { |
| DrawableHolder holder = new DrawableHolder(wave); |
| mLightWaves.add(holder); |
| holder.setAlpha(0.0f); |
| } |
| } |
| |
| private void waveUpdateFrame(float mouseX, float mouseY, boolean fingerDown) { |
| double distX = mouseX - mLockCenterX; |
| double distY = mouseY - mLockCenterY; |
| int dragDistance = (int) Math.ceil(Math.hypot(distX, distY)); |
| double touchA = Math.atan2(distX, distY); |
| float ringX = (float) (mLockCenterX + mRingRadius * Math.sin(touchA)); |
| float ringY = (float) (mLockCenterY + mRingRadius * Math.cos(touchA)); |
| |
| switch (mLockState) { |
| case STATE_RESET_LOCK: |
| if (DBG) Log.v(TAG, "State RESET_LOCK"); |
| mWaveTimerDelay = WAVE_DELAY; |
| for (int i = 0; i < mLightWaves.size(); i++) { |
| DrawableHolder holder = mLightWaves.get(i); |
| holder.addAnimTo(300, 0, "alpha", 0.0f, false); |
| } |
| for (int i = 0; i < mLightWaves.size(); i++) { |
| mLightWaves.get(i).startAnimations(this); |
| } |
| |
| mUnlockRing.addAnimTo(DURATION, 0, "x", mLockCenterX, true); |
| mUnlockRing.addAnimTo(DURATION, 0, "y", mLockCenterY, true); |
| mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 0.1f, true); |
| mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 0.1f, true); |
| mUnlockRing.addAnimTo(DURATION, 0, "alpha", 0.0f, true); |
| |
| mUnlockDefault.removeAnimationFor("x"); |
| mUnlockDefault.removeAnimationFor("y"); |
| mUnlockDefault.removeAnimationFor("scaleX"); |
| mUnlockDefault.removeAnimationFor("scaleY"); |
| mUnlockDefault.removeAnimationFor("alpha"); |
| mUnlockDefault.setX(mLockCenterX); |
| mUnlockDefault.setY(mLockCenterY); |
| mUnlockDefault.setScaleX(0.1f); |
| mUnlockDefault.setScaleY(0.1f); |
| mUnlockDefault.setAlpha(0.0f); |
| mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true); |
| mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true); |
| mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true); |
| |
| mUnlockHalo.removeAnimationFor("x"); |
| mUnlockHalo.removeAnimationFor("y"); |
| mUnlockHalo.removeAnimationFor("scaleX"); |
| mUnlockHalo.removeAnimationFor("scaleY"); |
| mUnlockHalo.removeAnimationFor("alpha"); |
| mUnlockHalo.setX(mLockCenterX); |
| mUnlockHalo.setY(mLockCenterY); |
| mUnlockHalo.setScaleX(0.1f); |
| mUnlockHalo.setScaleY(0.1f); |
| mUnlockHalo.setAlpha(0.0f); |
| mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "x", mLockCenterX, true); |
| mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "y", mLockCenterY, true); |
| mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true); |
| mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true); |
| mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true); |
| |
| removeCallbacks(mLockTimerActions); |
| |
| mLockState = STATE_READY; |
| break; |
| |
| case STATE_READY: |
| if (DBG) Log.v(TAG, "State READY"); |
| mWaveTimerDelay = WAVE_DELAY; |
| break; |
| |
| case STATE_START_ATTEMPT: |
| if (DBG) Log.v(TAG, "State START_ATTEMPT"); |
| mUnlockDefault.removeAnimationFor("x"); |
| mUnlockDefault.removeAnimationFor("y"); |
| mUnlockDefault.removeAnimationFor("scaleX"); |
| mUnlockDefault.removeAnimationFor("scaleY"); |
| mUnlockDefault.removeAnimationFor("alpha"); |
| mUnlockDefault.setX(mLockCenterX + 182); |
| mUnlockDefault.setY(mLockCenterY); |
| mUnlockDefault.setScaleX(0.1f); |
| mUnlockDefault.setScaleY(0.1f); |
| mUnlockDefault.setAlpha(0.0f); |
| |
| mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, false); |
| mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, false); |
| mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, false); |
| |
| mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 1.0f, true); |
| mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 1.0f, true); |
| mUnlockRing.addAnimTo(DURATION, 0, "alpha", 1.0f, true); |
| |
| mLockState = STATE_ATTEMPTING; |
| break; |
| |
| case STATE_ATTEMPTING: |
| if (DBG) Log.v(TAG, "State ATTEMPTING (fingerDown = " + fingerDown + ")"); |
| if (dragDistance > mSnapRadius) { |
| mFinishWaves = true; // don't start any more waves. |
| if (fingerDown) { |
| mUnlockHalo.addAnimTo(0, 0, "x", ringX, true); |
| mUnlockHalo.addAnimTo(0, 0, "y", ringY, true); |
| mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true); |
| mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true); |
| mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true); |
| } else { |
| if (DBG) Log.v(TAG, "up detected, moving to STATE_UNLOCK_ATTEMPT"); |
| mLockState = STATE_UNLOCK_ATTEMPT; |
| } |
| } else { |
| // If waves have stopped, we need to kick them off again... |
| if (!mWavesRunning) { |
| mWavesRunning = true; |
| mFinishWaves = false; |
| // mWaveTimerDelay = WAVE_DELAY; |
| postDelayed(mAddWaveAction, mWaveTimerDelay); |
| } |
| mUnlockHalo.addAnimTo(0, 0, "x", mouseX, true); |
| mUnlockHalo.addAnimTo(0, 0, "y", mouseY, true); |
| mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true); |
| mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true); |
| mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true); |
| } |
| break; |
| |
| case STATE_UNLOCK_ATTEMPT: |
| if (DBG) Log.v(TAG, "State UNLOCK_ATTEMPT"); |
| if (dragDistance > mSnapRadius) { |
| for (int n = 0; n < mLightWaves.size(); n++) { |
| DrawableHolder wave = mLightWaves.get(n); |
| long delay = 1000L*(6 + n - mCurrentWave)/10L; |
| wave.addAnimTo(FINAL_DURATION, delay, "x", ringX, true); |
| wave.addAnimTo(FINAL_DURATION, delay, "y", ringY, true); |
| wave.addAnimTo(FINAL_DURATION, delay, "scaleX", 0.1f, true); |
| wave.addAnimTo(FINAL_DURATION, delay, "scaleY", 0.1f, true); |
| wave.addAnimTo(FINAL_DURATION, delay, "alpha", 0.0f, true); |
| } |
| for (int i = 0; i < mLightWaves.size(); i++) { |
| mLightWaves.get(i).startAnimations(this); |
| } |
| |
| mUnlockRing.addAnimTo(FINAL_DURATION, 0, "x", ringX, false); |
| mUnlockRing.addAnimTo(FINAL_DURATION, 0, "y", ringY, false); |
| mUnlockRing.addAnimTo(FINAL_DURATION, 0, "scaleX", 0.1f, false); |
| mUnlockRing.addAnimTo(FINAL_DURATION, 0, "scaleY", 0.1f, false); |
| mUnlockRing.addAnimTo(FINAL_DURATION, 0, "alpha", 0.0f, false); |
| |
| mUnlockRing.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false); |
| |
| mUnlockDefault.removeAnimationFor("x"); |
| mUnlockDefault.removeAnimationFor("y"); |
| mUnlockDefault.removeAnimationFor("scaleX"); |
| mUnlockDefault.removeAnimationFor("scaleY"); |
| mUnlockDefault.removeAnimationFor("alpha"); |
| mUnlockDefault.setX(ringX); |
| mUnlockDefault.setY(ringY); |
| mUnlockDefault.setScaleX(0.1f); |
| mUnlockDefault.setScaleY(0.1f); |
| mUnlockDefault.setAlpha(0.0f); |
| |
| mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "x", ringX, true); |
| mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "y", ringY, true); |
| mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "scaleX", 1.0f, true); |
| mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "scaleY", 1.0f, true); |
| mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "alpha", 1.0f, true); |
| |
| mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleX", 3.0f, false); |
| mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleY", 3.0f, false); |
| mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false); |
| |
| mUnlockHalo.addAnimTo(FINAL_DURATION, 0, "x", ringX, false); |
| mUnlockHalo.addAnimTo(FINAL_DURATION, 0, "y", ringY, false); |
| |
| mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleX", 3.0f, false); |
| mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleY", 3.0f, false); |
| mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false); |
| |
| removeCallbacks(mLockTimerActions); |
| |
| postDelayed(mLockTimerActions, RESET_TIMEOUT); |
| |
| dispatchTriggerEvent(OnTriggerListener.CENTER_HANDLE); |
| mLockState = STATE_UNLOCK_SUCCESS; |
| } else { |
| mLockState = STATE_RESET_LOCK; |
| } |
| break; |
| |
| case STATE_UNLOCK_SUCCESS: |
| if (DBG) Log.v(TAG, "State UNLOCK_SUCCESS"); |
| removeCallbacks(mAddWaveAction); |
| break; |
| |
| default: |
| if (DBG) Log.v(TAG, "Unknown state " + mLockState); |
| break; |
| } |
| mUnlockDefault.startAnimations(this); |
| mUnlockHalo.startAnimations(this); |
| mUnlockRing.startAnimations(this); |
| } |
| |
| BitmapDrawable createDrawable(int resId) { |
| Resources res = getResources(); |
| Bitmap bitmap = BitmapFactory.decodeResource(res, resId); |
| return new BitmapDrawable(res, bitmap); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| waveUpdateFrame(mMouseX, mMouseY, mFingerDown); |
| for (int i = 0; i < mDrawables.size(); ++i) { |
| mDrawables.get(i).draw(canvas); |
| } |
| for (int i = 0; i < mLightWaves.size(); ++i) { |
| mLightWaves.get(i).draw(canvas); |
| } |
| } |
| |
| private final Runnable mLockTimerActions = new Runnable() { |
| public void run() { |
| if (DBG) Log.v(TAG, "LockTimerActions"); |
| // reset lock after inactivity |
| if (mLockState == STATE_ATTEMPTING) { |
| if (DBG) Log.v(TAG, "Timer resets to STATE_RESET_LOCK"); |
| mLockState = STATE_RESET_LOCK; |
| } |
| // for prototype, reset after successful unlock |
| if (mLockState == STATE_UNLOCK_SUCCESS) { |
| if (DBG) Log.v(TAG, "Timer resets to STATE_RESET_LOCK after success"); |
| mLockState = STATE_RESET_LOCK; |
| } |
| invalidate(); |
| } |
| }; |
| |
| private final Runnable mAddWaveAction = new Runnable() { |
| public void run() { |
| double distX = mMouseX - mLockCenterX; |
| double distY = mMouseY - mLockCenterY; |
| int dragDistance = (int) Math.ceil(Math.hypot(distX, distY)); |
| if (mLockState == STATE_ATTEMPTING && dragDistance < mSnapRadius |
| && mWaveTimerDelay >= WAVE_DELAY) { |
| mWaveTimerDelay = Math.min(WAVE_DURATION, mWaveTimerDelay + DELAY_INCREMENT); |
| |
| DrawableHolder wave = mLightWaves.get(mCurrentWave); |
| wave.setAlpha(0.0f); |
| wave.setScaleX(0.2f); |
| wave.setScaleY(0.2f); |
| wave.setX(mMouseX); |
| wave.setY(mMouseY); |
| |
| wave.addAnimTo(WAVE_DURATION, 0, "x", mLockCenterX, true); |
| wave.addAnimTo(WAVE_DURATION, 0, "y", mLockCenterY, true); |
| wave.addAnimTo(WAVE_DURATION*2/3, 0, "alpha", 1.0f, true); |
| wave.addAnimTo(WAVE_DURATION, 0, "scaleX", 1.0f, true); |
| wave.addAnimTo(WAVE_DURATION, 0, "scaleY", 1.0f, true); |
| |
| wave.addAnimTo(1000, RING_DELAY, "alpha", 0.0f, false); |
| wave.startAnimations(WaveView.this); |
| |
| mCurrentWave = (mCurrentWave+1) % mWaveCount; |
| if (DBG) Log.v(TAG, "WaveTimerDelay: start new wave in " + mWaveTimerDelay); |
| } else { |
| mWaveTimerDelay += DELAY_INCREMENT2; |
| } |
| if (mFinishWaves) { |
| // sentinel used to restart the waves after they've stopped |
| mWavesRunning = false; |
| } else { |
| postDelayed(mAddWaveAction, mWaveTimerDelay); |
| } |
| } |
| }; |
| |
| @Override |
| public boolean onHoverEvent(MotionEvent event) { |
| if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { |
| final int action = event.getAction(); |
| switch (action) { |
| case MotionEvent.ACTION_HOVER_ENTER: |
| event.setAction(MotionEvent.ACTION_DOWN); |
| break; |
| case MotionEvent.ACTION_HOVER_MOVE: |
| event.setAction(MotionEvent.ACTION_MOVE); |
| break; |
| case MotionEvent.ACTION_HOVER_EXIT: |
| event.setAction(MotionEvent.ACTION_UP); |
| break; |
| } |
| onTouchEvent(event); |
| event.setAction(action); |
| } |
| return super.onHoverEvent(event); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| final int action = event.getAction(); |
| mMouseX = event.getX(); |
| mMouseY = event.getY(); |
| boolean handled = false; |
| switch (action) { |
| case MotionEvent.ACTION_DOWN: |
| removeCallbacks(mLockTimerActions); |
| mFingerDown = true; |
| tryTransitionToStartAttemptState(event); |
| handled = true; |
| break; |
| |
| case MotionEvent.ACTION_MOVE: |
| tryTransitionToStartAttemptState(event); |
| handled = true; |
| break; |
| |
| case MotionEvent.ACTION_UP: |
| if (DBG) Log.v(TAG, "ACTION_UP"); |
| mFingerDown = false; |
| postDelayed(mLockTimerActions, RESET_TIMEOUT); |
| setGrabbedState(OnTriggerListener.NO_HANDLE); |
| // Normally the state machine is driven by user interaction causing redraws. |
| // However, when there's no more user interaction and no running animations, |
| // the state machine stops advancing because onDraw() never gets called. |
| // The following ensures we advance to the next state in this case, |
| // either STATE_UNLOCK_ATTEMPT or STATE_RESET_LOCK. |
| waveUpdateFrame(mMouseX, mMouseY, mFingerDown); |
| handled = true; |
| break; |
| |
| case MotionEvent.ACTION_CANCEL: |
| mFingerDown = false; |
| handled = true; |
| break; |
| } |
| invalidate(); |
| return handled ? true : super.onTouchEvent(event); |
| } |
| |
| /** |
| * Tries to transition to start attempt state. |
| * |
| * @param event A motion event. |
| */ |
| private void tryTransitionToStartAttemptState(MotionEvent event) { |
| final float dx = event.getX() - mUnlockHalo.getX(); |
| final float dy = event.getY() - mUnlockHalo.getY(); |
| float dist = (float) Math.hypot(dx, dy); |
| if (dist <= getScaledGrabHandleRadius()) { |
| setGrabbedState(OnTriggerListener.CENTER_HANDLE); |
| if (mLockState == STATE_READY) { |
| mLockState = STATE_START_ATTEMPT; |
| if (AccessibilityManager.getInstance(mContext).isEnabled()) { |
| announceUnlockHandle(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @return The radius in which the handle is grabbed scaled based on |
| * whether accessibility is enabled. |
| */ |
| private float getScaledGrabHandleRadius() { |
| if (AccessibilityManager.getInstance(mContext).isEnabled()) { |
| return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mUnlockHalo.getWidth(); |
| } else { |
| return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED * mUnlockHalo.getWidth(); |
| } |
| } |
| |
| /** |
| * Announces the unlock handle if accessibility is enabled. |
| */ |
| private void announceUnlockHandle() { |
| setContentDescription(mContext.getString(R.string.description_target_unlock_tablet)); |
| sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); |
| setContentDescription(null); |
| } |
| |
| /** |
| * Triggers haptic feedback. |
| */ |
| private synchronized void vibrate(long duration) { |
| final boolean hapticEnabled = Settings.System.getIntForUser( |
| mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, |
| UserHandle.USER_CURRENT) != 0; |
| if (hapticEnabled) { |
| if (mVibrator == null) { |
| mVibrator = (android.os.Vibrator) getContext() |
| .getSystemService(Context.VIBRATOR_SERVICE); |
| } |
| mVibrator.vibrate(duration); |
| } |
| } |
| |
| /** |
| * Registers a callback to be invoked when the user triggers an event. |
| * |
| * @param listener the OnDialTriggerListener to attach to this view |
| */ |
| public void setOnTriggerListener(OnTriggerListener listener) { |
| mOnTriggerListener = listener; |
| } |
| |
| /** |
| * Dispatches a trigger event to listener. Ignored if a listener is not set. |
| * @param whichHandle the handle that triggered the event. |
| */ |
| private void dispatchTriggerEvent(int whichHandle) { |
| vibrate(VIBRATE_LONG); |
| if (mOnTriggerListener != null) { |
| mOnTriggerListener.onTrigger(this, whichHandle); |
| } |
| } |
| |
| /** |
| * Sets the current grabbed state, and dispatches a grabbed state change |
| * event to our listener. |
| */ |
| private void setGrabbedState(int newState) { |
| if (newState != mGrabbedState) { |
| mGrabbedState = newState; |
| if (mOnTriggerListener != null) { |
| mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState); |
| } |
| } |
| } |
| |
| public interface OnTriggerListener { |
| /** |
| * Sent when the user releases the handle. |
| */ |
| public static final int NO_HANDLE = 0; |
| |
| /** |
| * Sent when the user grabs the center handle |
| */ |
| public static final int CENTER_HANDLE = 10; |
| |
| /** |
| * Called when the user drags the center ring beyond a threshold. |
| */ |
| void onTrigger(View v, int whichHandle); |
| |
| /** |
| * Called when the "grabbed state" changes (i.e. when the user either grabs or releases |
| * one of the handles.) |
| * |
| * @param v the view that was triggered |
| * @param grabbedState the new state: {@link #NO_HANDLE}, {@link #CENTER_HANDLE}, |
| */ |
| void onGrabbedStateChange(View v, int grabbedState); |
| } |
| |
| public void onAnimationUpdate(ValueAnimator animation) { |
| invalidate(); |
| } |
| |
| public void reset() { |
| if (DBG) Log.v(TAG, "reset() : resets state to STATE_RESET_LOCK"); |
| mLockState = STATE_RESET_LOCK; |
| invalidate(); |
| } |
| } |