FileDocCategorySizeDatePackage
WaveView.javaAPI DocAndroid 5.1 API28314Thu Mar 12 22:22:10 GMT 2015com.android.internal.widget

WaveView

public class WaveView extends android.view.View implements ValueAnimator.AnimatorUpdateListener
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.

Fields Summary
private static final String
TAG
private static final boolean
DBG
private static final int
WAVE_COUNT
private static final long
VIBRATE_SHORT
private static final long
VIBRATE_LONG
private static final int
STATE_RESET_LOCK
private static final int
STATE_READY
private static final int
STATE_START_ATTEMPT
private static final int
STATE_ATTEMPTING
private static final int
STATE_UNLOCK_ATTEMPT
private static final int
STATE_UNLOCK_SUCCESS
private static final long
DURATION
private static final long
FINAL_DURATION
private static final long
RING_DELAY
private static final long
FINAL_DELAY
private static final long
SHORT_DELAY
private static final long
WAVE_DURATION
private static final long
RESET_TIMEOUT
private static final long
DELAY_INCREMENT
private static final long
DELAY_INCREMENT2
private static final long
WAVE_DELAY
private static final float
GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED
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_ENABLED
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 android.media.AudioAttributes
VIBRATION_ATTRIBUTES
private android.os.Vibrator
mVibrator
private OnTriggerListener
mOnTriggerListener
private ArrayList
mDrawables
private ArrayList
mLightWaves
private boolean
mFingerDown
private float
mRingRadius
private int
mSnapRadius
private int
mWaveCount
private long
mWaveTimerDelay
private int
mCurrentWave
private float
mLockCenterX
private float
mLockCenterY
private float
mMouseX
private float
mMouseY
private DrawableHolder
mUnlockRing
private DrawableHolder
mUnlockDefault
private DrawableHolder
mUnlockHalo
private int
mLockState
private int
mGrabbedState
private boolean
mWavesRunning
private boolean
mFinishWaves
private final Runnable
mLockTimerActions
private final Runnable
mAddWaveAction
Constructors Summary
public WaveView(android.content.Context context)


       
        this(context, null);
    
public WaveView(android.content.Context context, android.util.AttributeSet attrs)

        super(context, attrs);

        // TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveView);
        // mOrientation = a.getInt(R.styleable.WaveView_orientation, HORIZONTAL);
        // a.recycle();

        initDrawables();
    
Methods Summary
private voidannounceUnlockHandle()
Announces the unlock handle if accessibility is enabled.

        setContentDescription(mContext.getString(R.string.description_target_unlock_tablet));
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
        setContentDescription(null);
    
android.graphics.drawable.BitmapDrawablecreateDrawable(int resId)

        Resources res = getResources();
        Bitmap bitmap = BitmapFactory.decodeResource(res, resId);
        return new BitmapDrawable(res, bitmap);
    
private voiddispatchTriggerEvent(int whichHandle)
Dispatches a trigger event to listener. Ignored if a listener is not set.

param
whichHandle the handle that triggered the event.

        vibrate(VIBRATE_LONG);
        if (mOnTriggerListener != null) {
            mOnTriggerListener.onTrigger(this, whichHandle);
        }
    
private floatgetScaledGrabHandleRadius()

return
The radius in which the handle is grabbed scaled based on whether accessibility is enabled.

        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mUnlockHalo.getWidth();
        } else {
            return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED * mUnlockHalo.getWidth();
        }
    
protected intgetSuggestedMinimumHeight()

        // View should be large enough to contain the unlock ring + halo
        return mUnlockRing.getHeight() + mUnlockHalo.getHeight();
    
protected intgetSuggestedMinimumWidth()

        // View should be large enough to contain the unlock ring + halo
        return mUnlockRing.getWidth() + mUnlockHalo.getWidth();
    
private voidinitDrawables()

        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);
        }
    
public voidonAnimationUpdate(android.animation.ValueAnimator animation)


                            
            

                                                   
            
    

        
        invalidate();
    
protected voidonDraw(android.graphics.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);
        }
    
public booleanonHoverEvent(android.view.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);
    
protected voidonMeasure(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);
    
protected voidonSizeChanged(int w, int h, int oldw, int oldh)

        mLockCenterX = 0.5f * w;
        mLockCenterY = 0.5f * h;
        super.onSizeChanged(w, h, oldw, oldh);
    
public booleanonTouchEvent(android.view.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);
    
public voidreset()

        if (DBG) Log.v(TAG, "reset() : resets state to STATE_RESET_LOCK");
        mLockState = STATE_RESET_LOCK;
        invalidate();
    
private voidsetGrabbedState(int newState)
Sets the current grabbed state, and dispatches a grabbed state change event to our listener.

        if (newState != mGrabbedState) {
            mGrabbedState = newState;
            if (mOnTriggerListener != null) {
                mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
            }
        }
    
public voidsetOnTriggerListener(com.android.internal.widget.WaveView$OnTriggerListener listener)
Registers a callback to be invoked when the user triggers an event.

param
listener the OnDialTriggerListener to attach to this view

        mOnTriggerListener = listener;
    
private voidtryTransitionToStartAttemptState(android.view.MotionEvent event)
Tries to transition to start attempt state.

param
event A motion 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();
                }
            }
        }
    
private synchronized voidvibrate(long duration)
Triggers haptic feedback.

        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, VIBRATION_ATTRIBUTES);
        }
    
private voidwaveUpdateFrame(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);