1b505074e8273887fbcd1e933738a42e770085fb8Jim Miller/*
2b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * Copyright (C) 2011 The Android Open Source Project
3b505074e8273887fbcd1e933738a42e770085fb8Jim Miller *
4b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * Licensed under the Apache License, Version 2.0 (the "License");
5b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * you may not use this file except in compliance with the License.
6b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * You may obtain a copy of the License at
7b505074e8273887fbcd1e933738a42e770085fb8Jim Miller *
8b505074e8273887fbcd1e933738a42e770085fb8Jim Miller *      http://www.apache.org/licenses/LICENSE-2.0
9b505074e8273887fbcd1e933738a42e770085fb8Jim Miller *
10b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * Unless required by applicable law or agreed to in writing, software
11b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * distributed under the License is distributed on an "AS IS" BASIS,
12b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * See the License for the specific language governing permissions and
14b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * limitations under the License.
15b505074e8273887fbcd1e933738a42e770085fb8Jim Miller */
16b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
17b505074e8273887fbcd1e933738a42e770085fb8Jim Millerpackage com.android.internal.widget.multiwaveview;
18b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
19b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.animation.Animator;
20b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.animation.Animator.AnimatorListener;
211c8d207201150c29ac92c424e1320c715a64b5bcJim Millerimport android.animation.AnimatorListenerAdapter;
22b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.animation.TimeInterpolator;
23b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.animation.ValueAnimator;
24b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.animation.ValueAnimator.AnimatorUpdateListener;
253294b6b09b2f52cb44005720051c32c9c851fc9fJim Millerimport android.content.ComponentName;
26b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.content.Context;
273294b6b09b2f52cb44005720051c32c9c851fc9fJim Millerimport android.content.pm.PackageManager;
283294b6b09b2f52cb44005720051c32c9c851fc9fJim Millerimport android.content.pm.PackageManager.NameNotFoundException;
29b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.content.res.Resources;
30b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.content.res.TypedArray;
31b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.graphics.Canvas;
32b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.graphics.RectF;
33a073e570789e5b49e8339af44516444b13db4428Jim Millerimport android.graphics.drawable.Drawable;
343294b6b09b2f52cb44005720051c32c9c851fc9fJim Millerimport android.os.Bundle;
35723a725e790d269f32980116e775d3d7f0037865Jeff Sharkeyimport android.os.UserHandle;
36b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.os.Vibrator;
37723a725e790d269f32980116e775d3d7f0037865Jeff Sharkeyimport android.provider.Settings;
38f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganovimport android.text.TextUtils;
39b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.util.AttributeSet;
40b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.util.Log;
41b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.util.TypedValue;
42354619c1cc1b4668c81c5368b2256335cc9e8538Jim Millerimport android.view.Gravity;
43b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.view.MotionEvent;
44b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport android.view.View;
45f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganovimport android.view.accessibility.AccessibilityEvent;
46f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganovimport android.view.accessibility.AccessibilityManager;
47b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
48b505074e8273887fbcd1e933738a42e770085fb8Jim Millerimport com.android.internal.R;
49b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
50f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganovimport java.util.ArrayList;
51f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
52b505074e8273887fbcd1e933738a42e770085fb8Jim Miller/**
53b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * A special widget containing a center and outer ring. Moving the center ring to the outer ring
54b505074e8273887fbcd1e933738a42e770085fb8Jim Miller * causes an event that can be caught by implementing OnTriggerListener.
55b505074e8273887fbcd1e933738a42e770085fb8Jim Miller */
561c8d207201150c29ac92c424e1320c715a64b5bcJim Millerpublic class MultiWaveView extends View {
57b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private static final String TAG = "MultiWaveView";
58e20414f37a9aa0c5add34253033a2847905687d7Jim Miller    private static final boolean DEBUG = false;
59b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
60b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    // Wave state machine
61b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private static final int STATE_IDLE = 0;
62a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final int STATE_START = 1;
63a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final int STATE_FIRST_TOUCH = 2;
64a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final int STATE_TRACKING = 3;
65a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final int STATE_SNAP = 4;
66a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final int STATE_FINISH = 5;
67b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
68b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    // Animation properties.
69b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it
70b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
71b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    public interface OnTriggerListener {
72b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        int NO_HANDLE = 0;
73b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        int CENTER_HANDLE = 1;
74b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        public void onGrabbed(View v, int handle);
75b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        public void onReleased(View v, int handle);
76b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        public void onTrigger(View v, int target);
77b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        public void onGrabbedStateChange(View v, int handle);
78998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        public void onFinishFinalAnimation();
79b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
80b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
8110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller    // Tuneable parameters for animation
821c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private static final int CHEVRON_INCREMENTAL_DELAY = 160;
83ce6eb1f5f2b4dae3f4b487f6da579149f42a8b4bJim Miller    private static final int CHEVRON_ANIMATION_DURATION = 850;
841c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private static final int RETURN_TO_HOME_DELAY = 1200;
85a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final int RETURN_TO_HOME_DURATION = 200;
861c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private static final int HIDE_ANIMATION_DELAY = 200;
8710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller    private static final int HIDE_ANIMATION_DURATION = 200;
8810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller    private static final int SHOW_ANIMATION_DURATION = 200;
89998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller    private static final int SHOW_ANIMATION_DELAY = 50;
90a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final int INITIAL_SHOW_HANDLE_DURATION = 200;
91a073e570789e5b49e8339af44516444b13db4428Jim Miller
9266b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov    private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
93a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final float TARGET_SCALE_EXPANDED = 1.0f;
94a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final float TARGET_SCALE_COLLAPSED = 0.8f;
95a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final float RING_SCALE_EXPANDED = 1.0f;
96a073e570789e5b49e8339af44516444b13db4428Jim Miller    private static final float RING_SCALE_COLLAPSED = 0.5f;
9710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
981c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut;
99b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
100b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>();
101b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private ArrayList<TargetDrawable> mChevronDrawables = new ArrayList<TargetDrawable>();
10220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    private AnimationBundle mChevronAnimations = new AnimationBundle();
10320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    private AnimationBundle mTargetAnimations = new AnimationBundle();
10420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    private AnimationBundle mHandleAnimations = new AnimationBundle();
105f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private ArrayList<String> mTargetDescriptions;
106f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private ArrayList<String> mDirectionDescriptions;
107b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private OnTriggerListener mOnTriggerListener;
108b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private TargetDrawable mHandleDrawable;
109b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private TargetDrawable mOuterRing;
110b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private Vibrator mVibrator;
111b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
112b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private int mFeedbackCount = 3;
113b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private int mVibrationDuration = 0;
114b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private int mGrabbedState;
115b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private int mActiveTarget = -1;
116b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private float mTapRadius;
117b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private float mWaveCenterX;
118b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private float mWaveCenterY;
119354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller    private int mMaxTargetHeight;
120354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller    private int mMaxTargetWidth;
121354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller
122b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private float mOuterRadius = 0.0f;
123b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private float mSnapMargin = 0.0f;
124b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private boolean mDragging;
1251c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private int mNewTargetResources;
126b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
12720830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    private class AnimationBundle extends ArrayList<Tweener> {
12820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        private static final long serialVersionUID = 0xA84D78726F127468L;
12920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        private boolean mSuspended;
13020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
13120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        public void start() {
13220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            if (mSuspended) return; // ignore attempts to start animations
13320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            final int count = size();
13420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            for (int i = 0; i < count; i++) {
13520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                Tweener anim = get(i);
13620830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                anim.animator.start();
13720830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            }
13820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        }
13920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
140998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        public void cancel() {
141998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller            final int count = size();
142998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller            for (int i = 0; i < count; i++) {
143998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller                Tweener anim = get(i);
144998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller                anim.animator.cancel();
145998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller            }
146998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller            clear();
147998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        }
148998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller
14920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        public void stop() {
15020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            final int count = size();
15120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            for (int i = 0; i < count; i++) {
15220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                Tweener anim = get(i);
15320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                anim.animator.end();
15420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            }
15520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            clear();
15620830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        }
15720830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
15820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        public void setSuspended(boolean suspend) {
15920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            mSuspended = suspend;
16020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        }
16120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    };
16220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
1631c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
1641c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        public void onAnimationEnd(Animator animator) {
165b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
166998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller            dispatchOnFinishFinalAnimation();
167b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
1681c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    };
1691c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
1708dcd571b89580ada6b557fc5e72010fa9696cf4eBrandon Keely    private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() {
1718dcd571b89580ada6b557fc5e72010fa9696cf4eBrandon Keely        public void onAnimationEnd(Animator animator) {
1728dcd571b89580ada6b557fc5e72010fa9696cf4eBrandon Keely            ping();
1738dcd571b89580ada6b557fc5e72010fa9696cf4eBrandon Keely            switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
174998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller            dispatchOnFinishFinalAnimation();
1758dcd571b89580ada6b557fc5e72010fa9696cf4eBrandon Keely        }
1768dcd571b89580ada6b557fc5e72010fa9696cf4eBrandon Keely    };
1778dcd571b89580ada6b557fc5e72010fa9696cf4eBrandon Keely
1781c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() {
1791c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        public void onAnimationUpdate(ValueAnimator animation) {
1801c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            invalidateGlobalRegion(mHandleDrawable);
1811c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            invalidate();
1821c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        }
1831c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    };
1841c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
1851c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private boolean mAnimatingTargets;
1861c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() {
1871c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        public void onAnimationEnd(Animator animator) {
1881c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            if (mNewTargetResources != 0) {
1891c8d207201150c29ac92c424e1320c715a64b5bcJim Miller                internalSetTargetResources(mNewTargetResources);
1901c8d207201150c29ac92c424e1320c715a64b5bcJim Miller                mNewTargetResources = 0;
191a073e570789e5b49e8339af44516444b13db4428Jim Miller                hideTargets(false, false);
1921c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            }
1931c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            mAnimatingTargets = false;
1941c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        }
195b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    };
196be8d1cf1ac9fc514fb0cc2e8ef4a85beb0197fa0Jim Miller    private int mTargetResourceId;
197f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private int mTargetDescriptionsResourceId;
198f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private int mDirectionDescriptionsResourceId;
199e898ac59db04d8ab0762180ca8ec7cea1347aa09Jim Miller    private boolean mAlwaysTrackFinger;
200354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller    private int mHorizontalInset;
201354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller    private int mVerticalInset;
202354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller    private int mGravity = Gravity.TOP;
20310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller    private boolean mInitialLayout = true;
204a073e570789e5b49e8339af44516444b13db4428Jim Miller    private Tweener mBackgroundAnimator;
205b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
206b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    public MultiWaveView(Context context) {
207b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        this(context, null);
208b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
209b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
210b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    public MultiWaveView(Context context, AttributeSet attrs) {
211b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        super(context, attrs);
212b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Resources res = context.getResources();
213b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
214b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiWaveView);
215b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mOuterRadius = a.getDimension(R.styleable.MultiWaveView_outerRadius, mOuterRadius);
216b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mSnapMargin = a.getDimension(R.styleable.MultiWaveView_snapMargin, mSnapMargin);
217b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mVibrationDuration = a.getInt(R.styleable.MultiWaveView_vibrationDuration,
218b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                mVibrationDuration);
219b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mFeedbackCount = a.getInt(R.styleable.MultiWaveView_feedbackCount,
220b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                mFeedbackCount);
221b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mHandleDrawable = new TargetDrawable(res,
222b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller                a.peekValue(R.styleable.MultiWaveView_handleDrawable).resourceId);
223b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mTapRadius = mHandleDrawable.getWidth()/2;
224b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller        mOuterRing = new TargetDrawable(res,
225b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller                a.peekValue(R.styleable.MultiWaveView_waveDrawable).resourceId);
226e898ac59db04d8ab0762180ca8ec7cea1347aa09Jim Miller        mAlwaysTrackFinger = a.getBoolean(R.styleable.MultiWaveView_alwaysTrackFinger, false);
227b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
2284c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        // Read array of chevron drawables
2294c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        TypedValue outValue = new TypedValue();
2304c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        if (a.getValue(R.styleable.MultiWaveView_chevronDrawables, outValue)) {
2314c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            ArrayList<TargetDrawable> chevrons = loadDrawableArray(outValue.resourceId);
2324c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            for (int i = 0; i < chevrons.size(); i++) {
2334c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller                final TargetDrawable chevron = chevrons.get(i);
2344c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller                for (int k = 0; k < mFeedbackCount; k++) {
2354c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller                    mChevronDrawables.add(chevron == null ? null : new TargetDrawable(chevron));
2364c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller                }
2374c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            }
2384c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        }
2394c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller
240b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        // Read array of target drawables
241b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (a.getValue(R.styleable.MultiWaveView_targetDrawables, outValue)) {
2421c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            internalSetTargetResources(outValue.resourceId);
243b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
244b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (mTargetDrawables == null || mTargetDrawables.size() == 0) {
245b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            throw new IllegalStateException("Must specify at least one target drawable");
246b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
247b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
248f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        // Read array of target descriptions
249f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        if (a.getValue(R.styleable.MultiWaveView_targetDescriptions, outValue)) {
250f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            final int resourceId = outValue.resourceId;
251f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            if (resourceId == 0) {
252f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                throw new IllegalStateException("Must specify target descriptions");
253f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            }
254f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            setTargetDescriptionsResourceId(resourceId);
255f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
256f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
257f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        // Read array of direction descriptions
258f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        if (a.getValue(R.styleable.MultiWaveView_directionDescriptions, outValue)) {
259f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            final int resourceId = outValue.resourceId;
260f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            if (resourceId == 0) {
261f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                throw new IllegalStateException("Must specify direction descriptions");
262f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            }
263f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            setDirectionDescriptionsResourceId(resourceId);
264f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
265f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
266f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        a.recycle();
267955a016922ea49f154d190b054a202559b41a4d3Jim Miller
268955a016922ea49f154d190b054a202559b41a4d3Jim Miller        // Use gravity attribute from LinearLayout
269955a016922ea49f154d190b054a202559b41a4d3Jim Miller        a = context.obtainStyledAttributes(attrs, android.R.styleable.LinearLayout);
270955a016922ea49f154d190b054a202559b41a4d3Jim Miller        mGravity = a.getInt(android.R.styleable.LinearLayout_gravity, Gravity.TOP);
271955a016922ea49f154d190b054a202559b41a4d3Jim Miller        a.recycle();
272955a016922ea49f154d190b054a202559b41a4d3Jim Miller
273b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        setVibrateEnabled(mVibrationDuration > 0);
27420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        assignDefaultsIfNeeded();
275b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
276b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
277b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private void dump() {
278b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Log.v(TAG, "Outer Radius = " + mOuterRadius);
279b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Log.v(TAG, "SnapMargin = " + mSnapMargin);
280b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
281b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
282b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Log.v(TAG, "TapRadius = " + mTapRadius);
283b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Log.v(TAG, "WaveCenterX = " + mWaveCenterX);
284b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Log.v(TAG, "WaveCenterY = " + mWaveCenterY);
285b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
286b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
28720830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    public void suspendAnimations() {
28820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mChevronAnimations.setSuspended(true);
28920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mTargetAnimations.setSuspended(true);
29020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mHandleAnimations.setSuspended(true);
29120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    }
29220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
29320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    public void resumeAnimations() {
29420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mChevronAnimations.setSuspended(false);
29520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mTargetAnimations.setSuspended(false);
29620830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mHandleAnimations.setSuspended(false);
29720830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mChevronAnimations.start();
29820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mTargetAnimations.start();
29920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mHandleAnimations.start();
30020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    }
30120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
302b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    @Override
303b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    protected int getSuggestedMinimumWidth() {
304354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        // View should be large enough to contain the background + handle and
305354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        // target drawable on either edge.
306c6e523ea9bc15f18c9cbf04b05e8d2c90298453fJim Miller        return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth);
307b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
308b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
309b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    @Override
310b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    protected int getSuggestedMinimumHeight() {
311354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        // View should be large enough to contain the unlock ring + target and
312354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        // target drawable on either edge
313c6e523ea9bc15f18c9cbf04b05e8d2c90298453fJim Miller        return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight);
3146b05d58018c2806459c121e507c005639b74aee9Jim Miller    }
3156b05d58018c2806459c121e507c005639b74aee9Jim Miller
3166b05d58018c2806459c121e507c005639b74aee9Jim Miller    private int resolveMeasured(int measureSpec, int desired)
3176b05d58018c2806459c121e507c005639b74aee9Jim Miller    {
3186b05d58018c2806459c121e507c005639b74aee9Jim Miller        int result = 0;
3196b05d58018c2806459c121e507c005639b74aee9Jim Miller        int specSize = MeasureSpec.getSize(measureSpec);
3206b05d58018c2806459c121e507c005639b74aee9Jim Miller        switch (MeasureSpec.getMode(measureSpec)) {
3216b05d58018c2806459c121e507c005639b74aee9Jim Miller            case MeasureSpec.UNSPECIFIED:
3226b05d58018c2806459c121e507c005639b74aee9Jim Miller                result = desired;
3236b05d58018c2806459c121e507c005639b74aee9Jim Miller                break;
3246b05d58018c2806459c121e507c005639b74aee9Jim Miller            case MeasureSpec.AT_MOST:
3256b05d58018c2806459c121e507c005639b74aee9Jim Miller                result = Math.min(specSize, desired);
3266b05d58018c2806459c121e507c005639b74aee9Jim Miller                break;
3276b05d58018c2806459c121e507c005639b74aee9Jim Miller            case MeasureSpec.EXACTLY:
3286b05d58018c2806459c121e507c005639b74aee9Jim Miller            default:
3296b05d58018c2806459c121e507c005639b74aee9Jim Miller                result = specSize;
3306b05d58018c2806459c121e507c005639b74aee9Jim Miller        }
3316b05d58018c2806459c121e507c005639b74aee9Jim Miller        return result;
3326b05d58018c2806459c121e507c005639b74aee9Jim Miller    }
3336b05d58018c2806459c121e507c005639b74aee9Jim Miller
3346b05d58018c2806459c121e507c005639b74aee9Jim Miller    @Override
3356b05d58018c2806459c121e507c005639b74aee9Jim Miller    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3366b05d58018c2806459c121e507c005639b74aee9Jim Miller        final int minimumWidth = getSuggestedMinimumWidth();
3376b05d58018c2806459c121e507c005639b74aee9Jim Miller        final int minimumHeight = getSuggestedMinimumHeight();
338354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
339354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
34010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        computeInsets((computedWidth - minimumWidth), (computedHeight - minimumHeight));
341354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        setMeasuredDimension(computedWidth, computedHeight);
342b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
343b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
344b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private void switchToState(int state, float x, float y) {
345b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        switch (state) {
346b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case STATE_IDLE:
347b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                deactivateTargets();
348a073e570789e5b49e8339af44516444b13db4428Jim Miller                hideTargets(true, false);
349a073e570789e5b49e8339af44516444b13db4428Jim Miller                startBackgroundAnimation(0, 0.0f);
350b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
351b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
352b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
353a073e570789e5b49e8339af44516444b13db4428Jim Miller            case STATE_START:
354a073e570789e5b49e8339af44516444b13db4428Jim Miller                deactivateHandle(0, 0, 1.0f, null);
355a073e570789e5b49e8339af44516444b13db4428Jim Miller                startBackgroundAnimation(0, 0.0f);
356a073e570789e5b49e8339af44516444b13db4428Jim Miller                break;
357a073e570789e5b49e8339af44516444b13db4428Jim Miller
358b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case STATE_FIRST_TOUCH:
359b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                deactivateTargets();
3601c8d207201150c29ac92c424e1320c715a64b5bcJim Miller                showTargets(true);
361a073e570789e5b49e8339af44516444b13db4428Jim Miller                mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
362a073e570789e5b49e8339af44516444b13db4428Jim Miller                startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
363b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                setGrabbedState(OnTriggerListener.CENTER_HANDLE);
364f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                if (AccessibilityManager.getInstance(mContext).isEnabled()) {
365f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                    announceTargets();
366f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                }
367b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
368b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
369b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case STATE_TRACKING:
370b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
371b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
372b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case STATE_SNAP:
373b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
374b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
375b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case STATE_FINISH:
376b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                doFinish();
377b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
378b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
379b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
380b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
381a073e570789e5b49e8339af44516444b13db4428Jim Miller    private void activateHandle(int duration, int delay, float finalAlpha,
382a073e570789e5b49e8339af44516444b13db4428Jim Miller            AnimatorListener finishListener) {
383a073e570789e5b49e8339af44516444b13db4428Jim Miller        mHandleAnimations.cancel();
384a073e570789e5b49e8339af44516444b13db4428Jim Miller        mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
385a073e570789e5b49e8339af44516444b13db4428Jim Miller                "ease", Ease.Cubic.easeIn,
386a073e570789e5b49e8339af44516444b13db4428Jim Miller                "delay", delay,
387a073e570789e5b49e8339af44516444b13db4428Jim Miller                "alpha", finalAlpha,
388a073e570789e5b49e8339af44516444b13db4428Jim Miller                "onUpdate", mUpdateListener,
389a073e570789e5b49e8339af44516444b13db4428Jim Miller                "onComplete", finishListener));
390a073e570789e5b49e8339af44516444b13db4428Jim Miller        mHandleAnimations.start();
391a073e570789e5b49e8339af44516444b13db4428Jim Miller    }
392a073e570789e5b49e8339af44516444b13db4428Jim Miller
393a073e570789e5b49e8339af44516444b13db4428Jim Miller    private void deactivateHandle(int duration, int delay, float finalAlpha,
394a073e570789e5b49e8339af44516444b13db4428Jim Miller            AnimatorListener finishListener) {
395a073e570789e5b49e8339af44516444b13db4428Jim Miller        mHandleAnimations.cancel();
396a073e570789e5b49e8339af44516444b13db4428Jim Miller        mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
397a073e570789e5b49e8339af44516444b13db4428Jim Miller            "ease", Ease.Quart.easeOut,
398a073e570789e5b49e8339af44516444b13db4428Jim Miller            "delay", delay,
399a073e570789e5b49e8339af44516444b13db4428Jim Miller            "alpha", finalAlpha,
400a073e570789e5b49e8339af44516444b13db4428Jim Miller            "x", 0,
401a073e570789e5b49e8339af44516444b13db4428Jim Miller            "y", 0,
402a073e570789e5b49e8339af44516444b13db4428Jim Miller            "onUpdate", mUpdateListener,
403a073e570789e5b49e8339af44516444b13db4428Jim Miller            "onComplete", finishListener));
4042a7b17bb2604823e140fb867fe87836dd039611aJim Miller        mHandleAnimations.start();
405998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller    }
406998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller
407b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    /**
408b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * Animation used to attract user's attention to the target button.
409bf591ff682f14db7ba7b3554897e9cdcf245da59Jim Miller     * Assumes mChevronDrawables is an a list with an even number of chevrons filled with
410bf591ff682f14db7ba7b3554897e9cdcf245da59Jim Miller     * mFeedbackCount items in the order: left, right, top, bottom.
411b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     */
412b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private void startChevronAnimation() {
4134c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        final float chevronStartDistance = mHandleDrawable.getWidth() * 0.8f;
4144c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        final float chevronStopDistance = mOuterRadius * 0.9f / 2.0f;
415ce6eb1f5f2b4dae3f4b487f6da579149f42a8b4bJim Miller        final float startScale = 0.5f;
416ce6eb1f5f2b4dae3f4b487f6da579149f42a8b4bJim Miller        final float endScale = 2.0f;
4174c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        final int directionCount = mFeedbackCount > 0 ? mChevronDrawables.size()/mFeedbackCount : 0;
4184c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller
41920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mChevronAnimations.stop();
42020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
4214c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        // Add an animation for all chevron drawables.  There are mFeedbackCount drawables
4224c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        // in each direction and directionCount directions.
4234c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        for (int direction = 0; direction < directionCount; direction++) {
4244c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            double angle = 2.0 * Math.PI * direction / directionCount;
4254c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            final float sx = (float) Math.cos(angle);
4264c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            final float sy = 0.0f - (float) Math.sin(angle);
4274c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            final float[] xrange = new float[]
4284c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller                 {sx * chevronStartDistance, sx * chevronStopDistance};
4294c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            final float[] yrange = new float[]
4304c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller                 {sy * chevronStartDistance, sy * chevronStopDistance};
43170832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller            for (int count = 0; count < mFeedbackCount; count++) {
43270832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller                int delay = count * CHEVRON_INCREMENTAL_DELAY;
43370832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller                final TargetDrawable icon = mChevronDrawables.get(direction*mFeedbackCount + count);
43470832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller                if (icon == null) {
43570832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller                    continue;
43670832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller                }
437b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                mChevronAnimations.add(Tweener.to(icon, CHEVRON_ANIMATION_DURATION,
438b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                        "ease", mChevronAnimationInterpolator,
439b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                        "delay", delay,
4404c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller                        "x", xrange,
4414c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller                        "y", yrange,
44270832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller                        "alpha", new float[] {1.0f, 0.0f},
443ce6eb1f5f2b4dae3f4b487f6da579149f42a8b4bJim Miller                        "scaleX", new float[] {startScale, endScale},
444ce6eb1f5f2b4dae3f4b487f6da579149f42a8b4bJim Miller                        "scaleY", new float[] {startScale, endScale},
4451c8d207201150c29ac92c424e1320c715a64b5bcJim Miller                        "onUpdate", mUpdateListener));
446b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            }
447b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
44820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mChevronAnimations.start();
449b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
450b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
451b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private void deactivateTargets() {
45220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        final int count = mTargetDrawables.size();
45320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        for (int i = 0; i < count; i++) {
45420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            TargetDrawable target = mTargetDrawables.get(i);
455b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            target.setState(TargetDrawable.STATE_INACTIVE);
456b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
457b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mActiveTarget = -1;
458b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
459b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
460b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    void invalidateGlobalRegion(TargetDrawable drawable) {
461b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        int width = drawable.getWidth();
462b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        int height = drawable.getHeight();
463b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        RectF childBounds = new RectF(0, 0, width, height);
464b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        childBounds.offset(drawable.getX() - width/2, drawable.getY() - height/2);
465b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        View view = this;
466b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        while (view.getParent() != null && view.getParent() instanceof View) {
467b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            view = (View) view.getParent();
468b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            view.getMatrix().mapRect(childBounds);
469b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            view.invalidate((int) Math.floor(childBounds.left),
470b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                    (int) Math.floor(childBounds.top),
471b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                    (int) Math.ceil(childBounds.right),
472b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                    (int) Math.ceil(childBounds.bottom));
473b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
474b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
475b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
476b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    /**
477b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * Dispatches a trigger event to listener. Ignored if a listener is not set.
478960892c0afa7f2b91236928e29e3987ed35b2077Jim Miller     * @param whichTarget the target that was triggered.
479b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     */
480960892c0afa7f2b91236928e29e3987ed35b2077Jim Miller    private void dispatchTriggerEvent(int whichTarget) {
481b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        vibrate();
482b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (mOnTriggerListener != null) {
483960892c0afa7f2b91236928e29e3987ed35b2077Jim Miller            mOnTriggerListener.onTrigger(this, whichTarget);
484f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
485f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
486f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
487998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller    private void dispatchOnFinishFinalAnimation() {
488998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        if (mOnTriggerListener != null) {
489998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller            mOnTriggerListener.onFinishFinalAnimation();
490998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        }
491998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller    }
492998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller
493b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private void doFinish() {
494b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        final int activeTarget = mActiveTarget;
495a073e570789e5b49e8339af44516444b13db4428Jim Miller        final boolean targetHit =  activeTarget != -1;
496b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
497b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (targetHit) {
498a073e570789e5b49e8339af44516444b13db4428Jim Miller            if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);
499a073e570789e5b49e8339af44516444b13db4428Jim Miller
500a073e570789e5b49e8339af44516444b13db4428Jim Miller            highlightSelected(activeTarget);
5011c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
5021c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            // Inform listener of any active targets.  Typically only one will be active.
503a073e570789e5b49e8339af44516444b13db4428Jim Miller            deactivateHandle(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener);
504960892c0afa7f2b91236928e29e3987ed35b2077Jim Miller            dispatchTriggerEvent(activeTarget);
5059a720f5eb6c67b581df22f4ecb498cebb459babeJim Miller            if (!mAlwaysTrackFinger) {
5069a720f5eb6c67b581df22f4ecb498cebb459babeJim Miller                // Force ring and targets to finish animation to final expanded state
5079a720f5eb6c67b581df22f4ecb498cebb459babeJim Miller                mTargetAnimations.stop();
5089a720f5eb6c67b581df22f4ecb498cebb459babeJim Miller            }
509a073e570789e5b49e8339af44516444b13db4428Jim Miller        } else {
510a073e570789e5b49e8339af44516444b13db4428Jim Miller            // Animate handle back to the center based on current state.
511a073e570789e5b49e8339af44516444b13db4428Jim Miller            deactivateHandle(HIDE_ANIMATION_DURATION, HIDE_ANIMATION_DELAY, 1.0f,
512a073e570789e5b49e8339af44516444b13db4428Jim Miller                    mResetListenerWithPing);
513a073e570789e5b49e8339af44516444b13db4428Jim Miller            hideTargets(true, false);
514b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
5151c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
5161c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        setGrabbedState(OnTriggerListener.NO_HANDLE);
5171c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    }
5181c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
519a073e570789e5b49e8339af44516444b13db4428Jim Miller    private void highlightSelected(int activeTarget) {
520a073e570789e5b49e8339af44516444b13db4428Jim Miller        // Highlight the given target and fade others
521a073e570789e5b49e8339af44516444b13db4428Jim Miller        mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
522a073e570789e5b49e8339af44516444b13db4428Jim Miller        hideUnselected(activeTarget);
523a073e570789e5b49e8339af44516444b13db4428Jim Miller    }
524a073e570789e5b49e8339af44516444b13db4428Jim Miller
5251c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private void hideUnselected(int active) {
5261c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        for (int i = 0; i < mTargetDrawables.size(); i++) {
5271c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            if (i != active) {
5281c8d207201150c29ac92c424e1320c715a64b5bcJim Miller                mTargetDrawables.get(i).setAlpha(0.0f);
5291c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            }
5301c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        }
531b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
532b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
533a073e570789e5b49e8339af44516444b13db4428Jim Miller    private void hideTargets(boolean animate, boolean expanded) {
534998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        mTargetAnimations.cancel();
5351c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        // Note: these animations should complete at the same time so that we can swap out
5361c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        // the target assets asynchronously from the setTargetResources() call.
5371c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        mAnimatingTargets = animate;
53810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
53910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
540998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller
541a073e570789e5b49e8339af44516444b13db4428Jim Miller        final float targetScale = expanded ? TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
54210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        final int length = mTargetDrawables.size();
54310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        for (int i = 0; i < length; i++) {
54410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            TargetDrawable target = mTargetDrawables.get(i);
54510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            target.setState(TargetDrawable.STATE_INACTIVE);
54610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            mTargetAnimations.add(Tweener.to(target, duration,
54710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    "ease", Ease.Cubic.easeOut,
548b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                    "alpha", 0.0f,
549998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller                    "scaleX", targetScale,
550998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller                    "scaleY", targetScale,
55110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    "delay", delay,
55210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    "onUpdate", mUpdateListener));
553b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
55410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
555a073e570789e5b49e8339af44516444b13db4428Jim Miller        final float ringScaleTarget = expanded ? RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
55610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        mTargetAnimations.add(Tweener.to(mOuterRing, duration,
55710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "ease", Ease.Cubic.easeOut,
55810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "alpha", 0.0f,
55910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "scaleX", ringScaleTarget,
56010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "scaleY", ringScaleTarget,
56110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "delay", delay,
56210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "onUpdate", mUpdateListener,
56310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "onComplete", mTargetUpdateListener));
56420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
56520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mTargetAnimations.start();
566b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
567b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
5681c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    private void showTargets(boolean animate) {
56920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mTargetAnimations.stop();
5701c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        mAnimatingTargets = animate;
57110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
572998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
57310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        final int length = mTargetDrawables.size();
57410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        for (int i = 0; i < length; i++) {
57510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            TargetDrawable target = mTargetDrawables.get(i);
57610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            target.setState(TargetDrawable.STATE_INACTIVE);
577998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller            mTargetAnimations.add(Tweener.to(target, duration,
57810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    "ease", Ease.Cubic.easeOut,
579b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                    "alpha", 1.0f,
58010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    "scaleX", 1.0f,
58110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    "scaleY", 1.0f,
58210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    "delay", delay,
58310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    "onUpdate", mUpdateListener));
58410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        }
585998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        mTargetAnimations.add(Tweener.to(mOuterRing, duration,
58610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "ease", Ease.Cubic.easeOut,
58710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "alpha", 1.0f,
58810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "scaleX", 1.0f,
58910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "scaleY", 1.0f,
59010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "delay", delay,
59110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "onUpdate", mUpdateListener,
59210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                "onComplete", mTargetUpdateListener));
59320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
59420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        mTargetAnimations.start();
595b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
596b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
597b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private void vibrate() {
598723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey        final boolean hapticEnabled = Settings.System.getIntForUser(
599723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey                mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
600723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey                UserHandle.USER_CURRENT) != 0;
601723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey        if (mVibrator != null && hapticEnabled) {
602b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            mVibrator.vibrate(mVibrationDuration);
603b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
604b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
605b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
6064c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller    private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) {
607b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        Resources res = getContext().getResources();
608b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        TypedArray array = res.obtainTypedArray(resourceId);
6094c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        final int count = array.length();
6104c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count);
6114c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        for (int i = 0; i < count; i++) {
6124c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            TypedValue value = array.peekValue(i);
6134c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0);
6144c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            drawables.add(target);
6154c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        }
6164c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        array.recycle();
6174c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        return drawables;
6184c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller    }
6194c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller
6204c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller    private void internalSetTargetResources(int resourceId) {
6214c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        mTargetDrawables = loadDrawableArray(resourceId);
6224c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        mTargetResourceId = resourceId;
6234c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller        final int count = mTargetDrawables.size();
624354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        int maxWidth = mHandleDrawable.getWidth();
625354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        int maxHeight = mHandleDrawable.getHeight();
626b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        for (int i = 0; i < count; i++) {
6274c351d62e7a09bcc29e7d0329bcdd947a302cf40Jim Miller            TargetDrawable target = mTargetDrawables.get(i);
628354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            maxWidth = Math.max(maxWidth, target.getWidth());
629354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            maxHeight = Math.max(maxHeight, target.getHeight());
630354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        }
631354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) {
632354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            mMaxTargetWidth = maxWidth;
633354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            mMaxTargetHeight = maxHeight;
634354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            requestLayout(); // required to resize layout and call updateTargetPositions()
635354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        } else {
63610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            updateTargetPositions(mWaveCenterX, mWaveCenterY);
63710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            updateChevronPositions(mWaveCenterX, mWaveCenterY);
638b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
6391c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    }
6401c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
6411c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    /**
6421c8d207201150c29ac92c424e1320c715a64b5bcJim Miller     * Loads an array of drawables from the given resourceId.
6431c8d207201150c29ac92c424e1320c715a64b5bcJim Miller     *
6441c8d207201150c29ac92c424e1320c715a64b5bcJim Miller     * @param resourceId
6451c8d207201150c29ac92c424e1320c715a64b5bcJim Miller     */
6461c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    public void setTargetResources(int resourceId) {
6471c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        if (mAnimatingTargets) {
6481c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            // postpone this change until we return to the initial state
6491c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            mNewTargetResources = resourceId;
6501c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        } else {
6511c8d207201150c29ac92c424e1320c715a64b5bcJim Miller            internalSetTargetResources(resourceId);
6521c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        }
653b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
654b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
655be8d1cf1ac9fc514fb0cc2e8ef4a85beb0197fa0Jim Miller    public int getTargetResourceId() {
656be8d1cf1ac9fc514fb0cc2e8ef4a85beb0197fa0Jim Miller        return mTargetResourceId;
657be8d1cf1ac9fc514fb0cc2e8ef4a85beb0197fa0Jim Miller    }
658be8d1cf1ac9fc514fb0cc2e8ef4a85beb0197fa0Jim Miller
659b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    /**
660f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     * Sets the resource id specifying the target descriptions for accessibility.
661f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     *
662f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     * @param resourceId The resource id.
663f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     */
664f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    public void setTargetDescriptionsResourceId(int resourceId) {
665f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        mTargetDescriptionsResourceId = resourceId;
666f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        if (mTargetDescriptions != null) {
667f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            mTargetDescriptions.clear();
668f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
669f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
670f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
671f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    /**
672f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     * Gets the resource id specifying the target descriptions for accessibility.
673f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     *
674f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     * @return The resource id.
675f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     */
676f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    public int getTargetDescriptionsResourceId() {
677f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        return mTargetDescriptionsResourceId;
678f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
679f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
680f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    /**
681f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     * Sets the resource id specifying the target direction descriptions for accessibility.
682f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     *
683f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     * @param resourceId The resource id.
684f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     */
685f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    public void setDirectionDescriptionsResourceId(int resourceId) {
686f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        mDirectionDescriptionsResourceId = resourceId;
687f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        if (mDirectionDescriptions != null) {
688f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            mDirectionDescriptions.clear();
689f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
690f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
691f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
692f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    /**
693f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     * Gets the resource id specifying the target direction descriptions.
694f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     *
695f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     * @return The resource id.
696f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov     */
697f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    public int getDirectionDescriptionsResourceId() {
698f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        return mDirectionDescriptionsResourceId;
699f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
700f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
701f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    /**
702b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * Enable or disable vibrate on touch.
703b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     *
704b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * @param enabled
705b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     */
706b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    public void setVibrateEnabled(boolean enabled) {
707b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (enabled && mVibrator == null) {
708b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
709b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        } else {
710b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            mVibrator = null;
711b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
712b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
713b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
714b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    /**
715b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * Starts chevron animation. Example use case: show chevron animation whenever the phone rings
716b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * or the user touches the screen.
717b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     *
718b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     */
719b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    public void ping() {
720b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        startChevronAnimation();
721b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
722b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
723b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    /**
724b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * Resets the widget to default state and cancels all animation. If animate is 'true', will
725b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * animate objects into place. Otherwise, objects will snap back to place.
726b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     *
727b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * @param animate
728b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     */
729b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    public void reset(boolean animate) {
730a073e570789e5b49e8339af44516444b13db4428Jim Miller        mChevronAnimations.stop();
731a073e570789e5b49e8339af44516444b13db4428Jim Miller        mHandleAnimations.stop();
732998bb765290066822ff8a3b378dadd71deb0ce73Jim Miller        mTargetAnimations.stop();
733a073e570789e5b49e8339af44516444b13db4428Jim Miller        startBackgroundAnimation(0, 0.0f);
734b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        hideChevrons();
735a073e570789e5b49e8339af44516444b13db4428Jim Miller        hideTargets(animate, false);
736a073e570789e5b49e8339af44516444b13db4428Jim Miller        deactivateHandle(0, 0, 1.0f, null);
737bf591ff682f14db7ba7b3554897e9cdcf245da59Jim Miller        Tweener.reset();
738b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
739b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
740a073e570789e5b49e8339af44516444b13db4428Jim Miller    private void startBackgroundAnimation(int duration, float alpha) {
741a073e570789e5b49e8339af44516444b13db4428Jim Miller        Drawable background = getBackground();
742a073e570789e5b49e8339af44516444b13db4428Jim Miller        if (mAlwaysTrackFinger && background != null) {
743a073e570789e5b49e8339af44516444b13db4428Jim Miller            if (mBackgroundAnimator != null) {
744a073e570789e5b49e8339af44516444b13db4428Jim Miller                mBackgroundAnimator.animator.end();
745a073e570789e5b49e8339af44516444b13db4428Jim Miller            }
746a073e570789e5b49e8339af44516444b13db4428Jim Miller            mBackgroundAnimator = Tweener.to(background, duration,
747a073e570789e5b49e8339af44516444b13db4428Jim Miller                    "ease", Ease.Cubic.easeIn,
748a073e570789e5b49e8339af44516444b13db4428Jim Miller                    "alpha", new int[] {0, (int)(255.0f * alpha)},
749a073e570789e5b49e8339af44516444b13db4428Jim Miller                    "delay", SHOW_ANIMATION_DELAY);
750a073e570789e5b49e8339af44516444b13db4428Jim Miller            mBackgroundAnimator.animator.start();
751a073e570789e5b49e8339af44516444b13db4428Jim Miller        }
752a073e570789e5b49e8339af44516444b13db4428Jim Miller    }
753a073e570789e5b49e8339af44516444b13db4428Jim Miller
754b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    @Override
755b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    public boolean onTouchEvent(MotionEvent event) {
756b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        final int action = event.getAction();
757b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        boolean handled = false;
758b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        switch (action) {
759b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case MotionEvent.ACTION_DOWN:
760354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                if (DEBUG) Log.v(TAG, "*** DOWN ***");
761b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller                handleDown(event);
762b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                handled = true;
763b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
764b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
765b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case MotionEvent.ACTION_MOVE:
766354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                if (DEBUG) Log.v(TAG, "*** MOVE ***");
767b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller                handleMove(event);
768b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                handled = true;
769b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
770b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
771b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case MotionEvent.ACTION_UP:
772354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                if (DEBUG) Log.v(TAG, "*** UP ***");
773b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller                handleMove(event);
774b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller                handleUp(event);
775b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                handled = true;
776b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
777b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
778b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            case MotionEvent.ACTION_CANCEL:
779354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                if (DEBUG) Log.v(TAG, "*** CANCEL ***");
78010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                handleMove(event);
781bf032650e3938f264091764a606a06a0d34bd15aJim Miller                handleCancel(event);
782b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                handled = true;
783b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                break;
784b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
785b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        invalidate();
786b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        return handled ? true : super.onTouchEvent(event);
787b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
788b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
78970832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller    private void moveHandleTo(float x, float y, boolean animate) {
79070832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller        mHandleDrawable.setX(x);
79170832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller        mHandleDrawable.setY(y);
79270832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller    }
79370832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller
794b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller    private void handleDown(MotionEvent event) {
795a073e570789e5b49e8339af44516444b13db4428Jim Miller        float eventX = event.getX();
796a073e570789e5b49e8339af44516444b13db4428Jim Miller        float eventY = event.getY();
797a073e570789e5b49e8339af44516444b13db4428Jim Miller        switchToState(STATE_START, eventX, eventY);
798a073e570789e5b49e8339af44516444b13db4428Jim Miller        if (!trySwitchToFirstTouchState(eventX, eventY)) {
799b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            mDragging = false;
800b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            ping();
801b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
802b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
803b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
804b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller    private void handleUp(MotionEvent event) {
805b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
806b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller        switchToState(STATE_FINISH, event.getX(), event.getY());
807bf032650e3938f264091764a606a06a0d34bd15aJim Miller    }
808b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller
809bf032650e3938f264091764a606a06a0d34bd15aJim Miller    private void handleCancel(MotionEvent event) {
810bf032650e3938f264091764a606a06a0d34bd15aJim Miller        if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL");
81110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
81210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        // We should drop the active target here but it interferes with
81310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        // moving off the screen in the direction of the navigation bar. At some point we may
81410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        // want to revisit how we handle this. For now we'll allow a canceled event to
81510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        // activate the current target.
81610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
81710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        // mActiveTarget = -1; // Drop the active target if canceled.
81810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
819bf032650e3938f264091764a606a06a0d34bd15aJim Miller        switchToState(STATE_FINISH, event.getX(), event.getY());
820b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
821b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
822b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller    private void handleMove(MotionEvent event) {
823b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        int activeTarget = -1;
824b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller        final int historySize = event.getHistorySize();
82520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        ArrayList<TargetDrawable> targets = mTargetDrawables;
82620830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        int ntargets = targets.size();
82710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        float x = 0.0f;
82810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        float y = 0.0f;
829b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller        for (int k = 0; k < historySize + 1; k++) {
83010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            float eventX = k < historySize ? event.getHistoricalX(k) : event.getX();
83110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            float eventY = k < historySize ? event.getHistoricalY(k) : event.getY();
83210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            // tx and ty are relative to wave center
83310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            float tx = eventX - mWaveCenterX;
83410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            float ty = eventY - mWaveCenterY;
835b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller            float touchRadius = (float) Math.sqrt(dist2(tx, ty));
836b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller            final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
83710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            float limitX = tx * scale;
83810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            float limitY = ty * scale;
83953f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka            double angleRad = Math.atan2(-ty, tx);
840b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller
84120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            if (!mDragging) {
84220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                trySwitchToFirstTouchState(eventX, eventY);
843a073e570789e5b49e8339af44516444b13db4428Jim Miller            }
844a073e570789e5b49e8339af44516444b13db4428Jim Miller
845a073e570789e5b49e8339af44516444b13db4428Jim Miller            if (mDragging) {
84653f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                // For multiple targets, snap to the one that matches
84753f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                final float snapRadius = mOuterRadius - mSnapMargin;
84853f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                final float snapDistance2 = snapRadius * snapRadius;
84953f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                // Find first target in range
85053f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                for (int i = 0; i < ntargets; i++) {
85153f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                    TargetDrawable target = targets.get(i);
85253f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka
85353f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                    double targetMinRad = (i - 0.5) * 2 * Math.PI / ntargets;
85453f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                    double targetMaxRad = (i + 0.5) * 2 * Math.PI / ntargets;
85553f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                    if (target.isEnabled()) {
85653f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                        boolean angleMatches =
85753f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                            (angleRad > targetMinRad && angleRad <= targetMaxRad) ||
85853f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                            (angleRad + 2 * Math.PI > targetMinRad &&
85953f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                             angleRad + 2 * Math.PI <= targetMaxRad);
86053f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka                        if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
86120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                            activeTarget = i;
86220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                        }
863b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller                    }
864b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller                }
865b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            }
86610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            x = limitX;
86710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            y = limitY;
86810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        }
86910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
87020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        if (!mDragging) {
87120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            return;
87220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        }
87320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
87410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        if (activeTarget != -1) {
87510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            switchToState(STATE_SNAP, x,y);
87653f109bf4923e111e796014e6701a14e5bfa5d1aMichael Jurka            moveHandleTo(x, y, false);
87710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        } else {
87810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            switchToState(STATE_TRACKING, x, y);
87910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            moveHandleTo(x, y, false);
880b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
881b35e372bf4080f3b1940a371f1e39fb5dccde990Jim Miller
882b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        // Draw handle outside parent's bounds
883b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        invalidateGlobalRegion(mHandleDrawable);
884b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
88510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        if (mActiveTarget != activeTarget) {
88610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            // Defocus the old target
88710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            if (mActiveTarget != -1) {
88820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                TargetDrawable target = targets.get(mActiveTarget);
88910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
89010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    target.setState(TargetDrawable.STATE_INACTIVE);
89110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                }
89210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            }
89310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            // Focus the new target
89410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            if (activeTarget != -1) {
89520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                TargetDrawable target = targets.get(activeTarget);
89610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
89710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    target.setState(TargetDrawable.STATE_FOCUSED);
89810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                }
89910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                if (AccessibilityManager.getInstance(mContext).isEnabled()) {
90010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    String targetContentDescription = getTargetDescription(activeTarget);
90110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                    announceText(targetContentDescription);
90210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                }
903a073e570789e5b49e8339af44516444b13db4428Jim Miller                activateHandle(0, 0, 0.0f, null);
904a073e570789e5b49e8339af44516444b13db4428Jim Miller            } else {
905a073e570789e5b49e8339af44516444b13db4428Jim Miller                activateHandle(0, 0, 1.0f, null);
906f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            }
907b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
908b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mActiveTarget = activeTarget;
909b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
910b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
9112a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov    @Override
9122a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov    public boolean onHoverEvent(MotionEvent event) {
9132a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov        if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
9142a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            final int action = event.getAction();
9152a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            switch (action) {
9162a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                case MotionEvent.ACTION_HOVER_ENTER:
9172a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                    event.setAction(MotionEvent.ACTION_DOWN);
9182a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                    break;
9192a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                case MotionEvent.ACTION_HOVER_MOVE:
9202a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                    event.setAction(MotionEvent.ACTION_MOVE);
9212a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                    break;
9222a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                case MotionEvent.ACTION_HOVER_EXIT:
9232a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                    event.setAction(MotionEvent.ACTION_UP);
9242a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov                    break;
9252a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            }
9262a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            onTouchEvent(event);
9272a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            event.setAction(action);
9282a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov        }
9292a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov        return super.onHoverEvent(event);
9302a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov    }
9312a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov
932b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    /**
933b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * Sets the current grabbed state, and dispatches a grabbed state change
934b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     * event to our listener.
935b505074e8273887fbcd1e933738a42e770085fb8Jim Miller     */
936b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private void setGrabbedState(int newState) {
937b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (newState != mGrabbedState) {
938b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            if (newState != OnTriggerListener.NO_HANDLE) {
939b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                vibrate();
940b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            }
941b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            mGrabbedState = newState;
942b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            if (mOnTriggerListener != null) {
943354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                if (newState == OnTriggerListener.NO_HANDLE) {
944354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                    mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE);
945354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                } else {
946354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                    mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE);
947354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                }
948960892c0afa7f2b91236928e29e3987ed35b2077Jim Miller                mOnTriggerListener.onGrabbedStateChange(this, newState);
949b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            }
950b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
951b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
952b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
95320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    private boolean trySwitchToFirstTouchState(float x, float y) {
95410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        final float tx = x - mWaveCenterX;
95510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        final float ty = y - mWaveCenterY;
95610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledTapRadiusSquared()) {
9572a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            if (DEBUG) Log.v(TAG, "** Handle HIT");
9582a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            switchToState(STATE_FIRST_TOUCH, x, y);
95910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            moveHandleTo(tx, ty, false);
9602a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            mDragging = true;
9612a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov            return true;
9622a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov        }
9632a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov        return false;
9642a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov    }
9652a671ac905e97d108e53e11856b01356f9248cc8Svetoslav Ganov
96620830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller    private void assignDefaultsIfNeeded() {
967b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (mOuterRadius == 0.0f) {
96820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
969b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
970b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        if (mSnapMargin == 0.0f) {
971b505074e8273887fbcd1e933738a42e770085fb8Jim Miller            mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
972b505074e8273887fbcd1e933738a42e770085fb8Jim Miller                    SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
973b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
9741c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    }
9751c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
97610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller    private void computeInsets(int dx, int dy) {
977e56ffdc7b31b0937628609cc3bbaa15879023569Fabrice Di Meglio        final int layoutDirection = getLayoutDirection();
978354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
979354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller
980354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
981354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            case Gravity.LEFT:
982354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                mHorizontalInset = 0;
983354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                break;
984354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            case Gravity.RIGHT:
985354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                mHorizontalInset = dx;
986354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                break;
987354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            case Gravity.CENTER_HORIZONTAL:
988354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            default:
989354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                mHorizontalInset = dx / 2;
990354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                break;
991354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        }
992354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
993354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            case Gravity.TOP:
994354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                mVerticalInset = 0;
995354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                break;
996354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            case Gravity.BOTTOM:
997354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                mVerticalInset = dy;
998354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                break;
999354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            case Gravity.CENTER_VERTICAL:
1000354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller            default:
1001354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                mVerticalInset = dy / 2;
1002354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller                break;
1003354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller        }
1004354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller    }
1005354619c1cc1b4668c81c5368b2256335cc9e8538Jim Miller
10061c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    @Override
10071c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
10081c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        super.onLayout(changed, left, top, right, bottom);
10091c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        final int width = right - left;
10101c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        final int height = bottom - top;
101120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller
1012c6e523ea9bc15f18c9cbf04b05e8d2c90298453fJim Miller        // Target placement width/height. This puts the targets on the greater of the ring
1013c6e523ea9bc15f18c9cbf04b05e8d2c90298453fJim Miller        // width or the specified outer radius.
1014c6e523ea9bc15f18c9cbf04b05e8d2c90298453fJim Miller        final float placementWidth = Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
1015c6e523ea9bc15f18c9cbf04b05e8d2c90298453fJim Miller        final float placementHeight = Math.max(mOuterRing.getHeight(), 2 * mOuterRadius);
1016955a016922ea49f154d190b054a202559b41a4d3Jim Miller        float newWaveCenterX = mHorizontalInset
1017c6e523ea9bc15f18c9cbf04b05e8d2c90298453fJim Miller                + Math.max(width, mMaxTargetWidth + placementWidth) / 2;
1018955a016922ea49f154d190b054a202559b41a4d3Jim Miller        float newWaveCenterY = mVerticalInset
1019c6e523ea9bc15f18c9cbf04b05e8d2c90298453fJim Miller                + Math.max(height, + mMaxTargetHeight + placementHeight) / 2;
10201c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
102110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        if (mInitialLayout) {
102210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            hideChevrons();
1023a073e570789e5b49e8339af44516444b13db4428Jim Miller            hideTargets(false, false);
102410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            moveHandleTo(0, 0, false);
102510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            mInitialLayout = false;
10261c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        }
102710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
102810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        mOuterRing.setPositionX(newWaveCenterX);
102910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        mOuterRing.setPositionY(newWaveCenterY);
103010c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
103110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        mHandleDrawable.setPositionX(newWaveCenterX);
103210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        mHandleDrawable.setPositionY(newWaveCenterY);
103310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
103410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        updateTargetPositions(newWaveCenterX, newWaveCenterY);
103510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        updateChevronPositions(newWaveCenterX, newWaveCenterY);
103610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
103710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        mWaveCenterX = newWaveCenterX;
103810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        mWaveCenterY = newWaveCenterY;
103910c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
10401c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        if (DEBUG) dump();
10411c8d207201150c29ac92c424e1320c715a64b5bcJim Miller    }
10421c8d207201150c29ac92c424e1320c715a64b5bcJim Miller
104310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller    private void updateTargetPositions(float centerX, float centerY) {
10441c8d207201150c29ac92c424e1320c715a64b5bcJim Miller        // Reposition the target drawables if the view changed.
104520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        ArrayList<TargetDrawable> targets = mTargetDrawables;
104620830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        final int size = targets.size();
104720830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        final float alpha = (float) (-2.0f * Math.PI / size);
104820830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        for (int i = 0; i < size; i++) {
104920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            final TargetDrawable targetIcon = targets.get(i);
105020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            final float angle = alpha * i;
105110c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            targetIcon.setPositionX(centerX);
105210c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            targetIcon.setPositionY(centerY);
105310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            targetIcon.setX(mOuterRadius * (float) Math.cos(angle));
105410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            targetIcon.setY(mOuterRadius * (float) Math.sin(angle));
105510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller        }
105610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller    }
105710c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller
105810c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller    private void updateChevronPositions(float centerX, float centerY) {
105920830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        ArrayList<TargetDrawable> chevrons = mChevronDrawables;
106020830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        final int size = chevrons.size();
106120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        for (int i = 0; i < size; i++) {
106220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            TargetDrawable target = chevrons.get(i);
106310c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            if (target != null) {
106410c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                target.setPositionX(centerX);
106510c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller                target.setPositionY(centerY);
106610c66afbd05e91143ea9bd109d3ce578e53dab14Jim Miller            }
1067b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
1068b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
1069b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
1070b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private void hideChevrons() {
107120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        ArrayList<TargetDrawable> chevrons = mChevronDrawables;
107220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        final int size = chevrons.size();
107320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        for (int i = 0; i < size; i++) {
107420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            TargetDrawable chevron = chevrons.get(i);
107570832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller            if (chevron != null) {
107670832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller                chevron.setAlpha(0.0f);
107770832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller            }
1078b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
1079b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
1080b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
1081b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    @Override
1082b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    protected void onDraw(Canvas canvas) {
1083b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mOuterRing.draw(canvas);
108420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        final int ntargets = mTargetDrawables.size();
108520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        for (int i = 0; i < ntargets; i++) {
108620830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            TargetDrawable target = mTargetDrawables.get(i);
108770832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller            if (target != null) {
108870832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller                target.draw(canvas);
108970832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller            }
1090b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
109120830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        final int nchevrons = mChevronDrawables.size();
109220830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller        for (int i = 0; i < nchevrons; i++) {
109320830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            TargetDrawable chevron = mChevronDrawables.get(i);
109420830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller            if (chevron != null) {
109520830421fe223bf2a8a69a67a6d26b0b5beb5baaJim Miller                chevron.draw(canvas);
109670832a3d77d90f09fb7ba27612c9cbec6a92abe6Jim Miller            }
1097b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        }
1098b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mHandleDrawable.draw(canvas);
1099b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
1100b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
1101b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    public void setOnTriggerListener(OnTriggerListener listener) {
1102b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        mOnTriggerListener = listener;
1103b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
1104b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
1105b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private float square(float d) {
1106b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        return d * d;
1107b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
1108b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
1109b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    private float dist2(float dx, float dy) {
1110b505074e8273887fbcd1e933738a42e770085fb8Jim Miller        return dx*dx + dy*dy;
1111b505074e8273887fbcd1e933738a42e770085fb8Jim Miller    }
1112b505074e8273887fbcd1e933738a42e770085fb8Jim Miller
111366b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov    private float getScaledTapRadiusSquared() {
111466b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov        final float scaledTapRadius;
111566b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
111666b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov            scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mTapRadius;
111766b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov        } else {
111866b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov            scaledTapRadius = mTapRadius;
111966b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov        }
112066b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov        return square(scaledTapRadius);
112166b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov    }
112266b191dad09a7552d1579a4a1d57a84e27a9ddb1Svetoslav Ganov
1123f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private void announceTargets() {
1124f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        StringBuilder utterance = new StringBuilder();
1125f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        final int targetCount = mTargetDrawables.size();
1126f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        for (int i = 0; i < targetCount; i++) {
1127f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            String targetDescription = getTargetDescription(i);
1128f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            String directionDescription = getDirectionDescription(i);
1129f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            if (!TextUtils.isEmpty(targetDescription)
1130f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                    && !TextUtils.isEmpty(directionDescription)) {
113157df88bc94f9aa06450e158b5a59ec29e3fe8874Svetoslav Ganov                String text = String.format(directionDescription, targetDescription);
113257df88bc94f9aa06450e158b5a59ec29e3fe8874Svetoslav Ganov                utterance.append(text);
113357df88bc94f9aa06450e158b5a59ec29e3fe8874Svetoslav Ganov            }
113457df88bc94f9aa06450e158b5a59ec29e3fe8874Svetoslav Ganov            if (utterance.length() > 0) {
113557df88bc94f9aa06450e158b5a59ec29e3fe8874Svetoslav Ganov                announceText(utterance.toString());
1136f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            }
1137f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
1138f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
1139f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
1140f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private void announceText(String text) {
1141f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        setContentDescription(text);
1142f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
1143f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        setContentDescription(null);
1144f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
1145f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
1146f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private String getTargetDescription(int index) {
1147f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) {
1148f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId);
1149f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            if (mTargetDrawables.size() != mTargetDescriptions.size()) {
1150f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                Log.w(TAG, "The number of target drawables must be"
1151f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                        + " euqal to the number of target descriptions.");
1152f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                return null;
1153f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            }
1154f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
1155f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        return mTargetDescriptions.get(index);
1156f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
1157f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
1158f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private String getDirectionDescription(int index) {
1159f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) {
1160f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId);
1161f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            if (mTargetDrawables.size() != mDirectionDescriptions.size()) {
1162f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                Log.w(TAG, "The number of target drawables must be"
1163f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                        + " euqal to the number of direction descriptions.");
1164f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov                return null;
1165f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            }
1166f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
1167f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        return mDirectionDescriptions.get(index);
1168f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
1169f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov
1170f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    private ArrayList<String> loadDescriptions(int resourceId) {
1171f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        TypedArray array = getContext().getResources().obtainTypedArray(resourceId);
1172f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        final int count = array.length();
1173f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        ArrayList<String> targetContentDescriptions = new ArrayList<String>(count);
1174f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        for (int i = 0; i < count; i++) {
1175f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            String contentDescription = array.getString(i);
1176f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov            targetContentDescriptions.add(contentDescription);
1177f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        }
1178f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        array.recycle();
1179f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov        return targetContentDescriptions;
1180f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov    }
1181b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller
1182b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller    public int getResourceIdForTarget(int index) {
1183b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller        final TargetDrawable drawable = mTargetDrawables.get(index);
1184b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller        return drawable == null ? 0 : drawable.getResourceId();
1185b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller    }
1186b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller
1187b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller    public void setEnableTarget(int resourceId, boolean enabled) {
1188b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller        for (int i = 0; i < mTargetDrawables.size(); i++) {
1189b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller            final TargetDrawable target = mTargetDrawables.get(i);
1190b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller            if (target.getResourceId() == resourceId) {
1191b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller                target.setEnabled(enabled);
1192b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller                break; // should never be more than one match
1193b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller            }
1194b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller        }
1195b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller    }
1196b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller
1197b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller    /**
1198b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller     * Gets the position of a target in the array that matches the given resource.
1199b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller     * @param resourceId
1200b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller     * @return the index or -1 if not found
1201b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller     */
1202b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller    public int getTargetPosition(int resourceId) {
1203b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller        for (int i = 0; i < mTargetDrawables.size(); i++) {
1204b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller            final TargetDrawable target = mTargetDrawables.get(i);
1205b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller            if (target.getResourceId() == resourceId) {
1206b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller                return i; // should never be more than one match
1207b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller            }
1208b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller        }
1209b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller        return -1;
1210b030476d193a423f6c1baf3053f66fc768c925e0Jim Miller    }
12113294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller
12123294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller    private boolean replaceTargetDrawables(Resources res, int existingResourceId,
12133294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            int newResourceId) {
12143294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        if (existingResourceId == 0 || newResourceId == 0) {
12153294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            return false;
12163294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        }
12173294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller
12183294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        boolean result = false;
12193294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        final ArrayList<TargetDrawable> drawables = mTargetDrawables;
12203294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        final int size = drawables.size();
12213294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        for (int i = 0; i < size; i++) {
12223294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            final TargetDrawable target = drawables.get(i);
12233294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            if (target != null && target.getResourceId() == existingResourceId) {
12243294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                target.setDrawable(res, newResourceId);
12253294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                result = true;
12263294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            }
12273294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        }
12283294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller
12293294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        if (result) {
12303294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            requestLayout(); // in case any given drawable's size changes
12313294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        }
12323294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller
12333294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        return result;
12343294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller    }
12353294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller
12363294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller    /**
12373294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller     * Searches the given package for a resource to use to replace the Drawable on the
12383294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller     * target with the given resource id
12393294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller     * @param component of the .apk that contains the resource
12403294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller     * @param name of the metadata in the .apk
12413294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller     * @param existingResId the resource id of the target to search for
12423294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller     * @return true if found in the given package and replaced at least one target Drawables
12433294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller     */
12443294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller    public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name,
12453294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                int existingResId) {
12463294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        if (existingResId == 0) return false;
12473294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller
12483294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        try {
12493294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            PackageManager packageManager = mContext.getPackageManager();
12503294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            // Look for the search icon specified in the activity meta-data
12513294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            Bundle metaData = packageManager.getActivityInfo(
12523294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                    component, PackageManager.GET_META_DATA).metaData;
12533294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            if (metaData != null) {
12543294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                int iconResId = metaData.getInt(name);
12553294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                if (iconResId != 0) {
12563294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                    Resources res = packageManager.getResourcesForActivity(component);
12573294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                    return replaceTargetDrawables(res, existingResId, iconResId);
12583294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                }
12593294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            }
12603294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        } catch (NameNotFoundException e) {
12613294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            Log.w(TAG, "Failed to swap drawable; "
12623294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                    + component.flattenToShortString() + " not found", e);
12633294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        } catch (Resources.NotFoundException nfe) {
12643294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller            Log.w(TAG, "Failed to swap drawable from "
12653294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller                    + component.flattenToShortString(), nfe);
12663294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        }
12673294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller        return false;
12683294b6b09b2f52cb44005720051c32c9c851fc9fJim Miller    }
1269f058340b2f1c3d8114c48581680b4294122fe371Svetoslav Ganov}
1270