1e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen/*
2e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Copyright (C) 2009 The Android Open Source Project
3e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen *
4e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Licensed under the Apache License, Version 2.0 (the "License");
5e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * you may not use this file except in compliance with the License.
6e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * You may obtain a copy of the License at
7e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen *
8e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen *      http://www.apache.org/licenses/LICENSE-2.0
9e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen *
10e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Unless required by applicable law or agreed to in writing, software
11e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * distributed under the License is distributed on an "AS IS" BASIS,
12e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * See the License for the specific language governing permissions and
14e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * limitations under the License.
15e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */
16e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
17e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenpackage com.android.internal.widget;
18e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
19e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.content.Context;
20e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.content.res.Resources;
2174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.content.res.TypedArray;
22e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.graphics.Canvas;
2374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.graphics.Paint;
2474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.graphics.Bitmap;
2574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.graphics.BitmapFactory;
2674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaenimport android.graphics.Matrix;
277b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlockimport android.media.AudioAttributes;
28723a725e790d269f32980116e775d3d7f0037865Jeff Sharkeyimport android.os.UserHandle;
29e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.os.Vibrator;
30723a725e790d269f32980116e775d3d7f0037865Jeff Sharkeyimport android.provider.Settings;
31e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.util.AttributeSet;
32e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.util.Log;
33e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.view.MotionEvent;
34e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport android.view.View;
35896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.VelocityTracker;
36896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.ViewConfiguration;
37896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaenimport android.view.animation.DecelerateInterpolator;
38f9e1a0b369740e11ea1ed4f141ffb936fc1a6cdbJohn Spurlock
391ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaenimport static android.view.animation.AnimationUtils.currentAnimationTimeMillis;
40f9e1a0b369740e11ea1ed4f141ffb936fc1a6cdbJohn Spurlock
41e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenimport com.android.internal.R;
42e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
43e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
44e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen/**
45e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * Custom view that presents up to two items that are selectable by rotating a semi-circle from
46e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * left to right, or right to left.  Used by incoming call screen, and the lock screen when no
47e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen * security pattern is set.
48e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen */
49e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaenpublic class RotarySelector extends View {
5074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    public static final int HORIZONTAL = 0;
5174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    public static final int VERTICAL = 1;
5274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
53e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private static final String LOG_TAG = "RotarySelector";
54e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private static final boolean DBG = false;
555037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller    private static final boolean VISUAL_DEBUG = false;
56e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
577b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
587b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
597b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
607b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock            .build();
617b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock
62e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    // Listener for onDialTrigger() callbacks.
63e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private OnDialTriggerListener mOnDialTriggerListener;
64e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
65e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private float mDensity;
66e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
67e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    // UI elements
6874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private Bitmap mBackground;
69c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen    private Bitmap mDimple;
70d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller    private Bitmap mDimpleDim;
71e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
72c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen    private Bitmap mLeftHandleIcon;
73c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen    private Bitmap mRightHandleIcon;
74e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
7574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private Bitmap mArrowShortLeftAndRight;
7674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private Bitmap mArrowLongLeft;  // Long arrow starting on the left, pointing clockwise
7774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private Bitmap mArrowLongRight;  // Long arrow starting on the right, pointing CCW
78e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
79e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    // positions of the left and right handle
80e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private int mLeftHandleX;
81e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private int mRightHandleX;
82e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
83896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    // current offset of rotary widget along the x axis
84896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private int mRotaryOffsetX = 0;
85e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
86e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    // state of the animation used to bring the handle back to its start position when
87e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    // the user lets go before triggering an action
88e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private boolean mAnimating = false;
89896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private long mAnimationStartTime;
90052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen    private long mAnimationDuration;
91896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private int mAnimatingDeltaXStart;   // the animation will interpolate from this delta to zero
92896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private int mAnimatingDeltaXEnd;
93896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
94896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private DecelerateInterpolator mInterpolator;
95e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
9674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private Paint mPaint = new Paint();
9774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
9874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    // used to rotate the background and arrow assets depending on orientation
9974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    final Matrix mBgMatrix = new Matrix();
10074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    final Matrix mArrowMatrix = new Matrix();
10174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
102e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
103e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * If the user is currently dragging something.
104e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
105e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private int mGrabbedState = NOTHING_GRABBED;
10688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown    public static final int NOTHING_GRABBED = 0;
10788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown    public static final int LEFT_HANDLE_GRABBED = 1;
10888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown    public static final int RIGHT_HANDLE_GRABBED = 2;
109e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
110e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
111e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Whether the user has triggered something (e.g dragging the left handle all the way over to
112e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * the right).
113e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
114e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private boolean mTriggered = false;
115e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
116e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    // Vibration (haptic feedback)
117e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private Vibrator mVibrator;
1189485aec1df6094b7d2497212c8fe04a6d459b4cdJim Miller    private static final long VIBRATE_SHORT = 20;  // msec
1199485aec1df6094b7d2497212c8fe04a6d459b4cdJim Miller    private static final long VIBRATE_LONG = 20;  // msec
120e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
121e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
122e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * The drawable for the arrows need to be scrunched this many dips towards the rotary bg below
123e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * it.
124e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
125e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private static final int ARROW_SCRUNCH_DIP = 6;
126e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
127e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
128e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * How far inset the left and right circles should be
129e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
130e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private static final int EDGE_PADDING_DIP = 9;
131e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
132e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
1335fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen     * How far from the edge of the screen the user must drag to trigger the event.
1345fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen     */
135052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen    private static final int EDGE_TRIGGER_DIP = 100;
1365fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen
1375fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen    /**
138e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Dimensions of arc in background drawable.
139e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
140e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    static final int OUTER_ROTARY_RADIUS_DIP = 390;
141e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    static final int ROTARY_STROKE_WIDTH_DIP = 83;
142052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen    static final int SNAP_BACK_ANIMATION_DURATION_MILLIS = 300;
143052e187edaa3934a290c81a6efcc664b59a09b59Karl Rosaen    static final int SPIN_ANIMATION_DURATION_MILLIS = 800;
144e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
1455fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen    private int mEdgeTriggerThresh;
146278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen    private int mDimpleWidth;
147278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen    private int mBackgroundWidth;
148278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen    private int mBackgroundHeight;
149896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private final int mOuterRadius;
150896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private final int mInnerRadius;
151896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private int mDimpleSpacing;
152896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
153896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private VelocityTracker mVelocityTracker;
154896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private int mMinimumVelocity;
155896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private int mMaximumVelocity;
156896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
157896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    /**
158896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen     * The number of dimples we are flinging when we do the "spin" animation.  Used to know when to
159896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen     * wrap the icons back around so they "rotate back" onto the screen.
160896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen     * @see #updateAnimation()
161896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen     */
162896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private int mDimplesOfFling = 0;
163896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
16474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    /**
16574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen     * Either {@link #HORIZONTAL} or {@link #VERTICAL}.
16674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen     */
16774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private int mOrientation;
168896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
169e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
1701ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen    public RotarySelector(Context context) {
1711ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen        this(context, null);
1721ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen    }
1731ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen
174e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
175e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Constructor used when this widget is created from a layout file.
176e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
177e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    public RotarySelector(Context context, AttributeSet attrs) {
178e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        super(context, attrs);
17974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
18074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        TypedArray a =
18174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            context.obtainStyledAttributes(attrs, R.styleable.RotarySelector);
18274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mOrientation = a.getInt(R.styleable.RotarySelector_orientation, HORIZONTAL);
18374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        a.recycle();
184e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
185e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        Resources r = getResources();
186e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        mDensity = r.getDisplayMetrics().density;
187e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        if (DBG) log("- Density: " + mDensity);
188e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
189e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // Assets (all are BitmapDrawables).
19074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mBackground = getBitmapFor(R.drawable.jog_dial_bg);
191c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen        mDimple = getBitmapFor(R.drawable.jog_dial_dimple);
192d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller        mDimpleDim = getBitmapFor(R.drawable.jog_dial_dimple_dim);
193e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
19474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mArrowLongLeft = getBitmapFor(R.drawable.jog_dial_arrow_long_left_green);
19574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mArrowLongRight = getBitmapFor(R.drawable.jog_dial_arrow_long_right_red);
19674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mArrowShortLeftAndRight = getBitmapFor(R.drawable.jog_dial_arrow_short_left_and_right);
1971ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen
198896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mInterpolator = new DecelerateInterpolator(1f);
1995fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen
2005fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen        mEdgeTriggerThresh = (int) (mDensity * EDGE_TRIGGER_DIP);
201278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen
202c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen        mDimpleWidth = mDimple.getWidth();
203278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen
20474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mBackgroundWidth = mBackground.getWidth();
20574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mBackgroundHeight = mBackground.getHeight();
206896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mOuterRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP);
207896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mInnerRadius = (int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity);
208896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
209896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
210896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity() * 2;
211896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
212896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    }
213896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
21474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private Bitmap getBitmapFor(int resId) {
21574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        return BitmapFactory.decodeResource(getContext().getResources(), resId);
21674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    }
21774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
218896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    @Override
219896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
220896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        super.onSizeChanged(w, h, oldw, oldh);
221896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
22274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int edgePadding = (int) (EDGE_PADDING_DIP * mDensity);
22374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mLeftHandleX = edgePadding + mDimpleWidth / 2;
22474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int length = isHoriz() ? w : h;
22574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mRightHandleX = length - edgePadding - mDimpleWidth / 2;
22674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mDimpleSpacing = (length / 2) - mLeftHandleX;
22774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
22874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        // bg matrix only needs to be calculated once
22974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mBgMatrix.setTranslate(0, 0);
23074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        if (!isHoriz()) {
23174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            // set up matrix for translating drawing of background and arrow assets
23274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            final int left = w - mBackgroundHeight;
23374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            mBgMatrix.preRotate(-90, 0, 0);
23474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            mBgMatrix.postTranslate(left, h);
23574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
23674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        } else {
23774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            mBgMatrix.postTranslate(0, h - mBackgroundHeight);
23874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        }
23974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    }
240896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
24174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private boolean isHoriz() {
24274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        return mOrientation == HORIZONTAL;
243e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
244e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
245e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
246e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Sets the left handle icon to a given resource.
247e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *
248e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * The resource should refer to a Drawable object, or use 0 to remove
249e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * the icon.
250e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *
251e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * @param resId the resource ID.
252e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
253e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    public void setLeftHandleResource(int resId) {
254e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        if (resId != 0) {
255c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen            mLeftHandleIcon = getBitmapFor(resId);
256e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
257e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        invalidate();
258e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
259e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
260e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
261e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Sets the right handle icon to a given resource.
262e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *
263e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * The resource should refer to a Drawable object, or use 0 to remove
264e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * the icon.
265e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *
266e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * @param resId the resource ID.
267e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
268e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    public void setRightHandleResource(int resId) {
269e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        if (resId != 0) {
270c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen            mRightHandleIcon = getBitmapFor(resId);
271e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
272e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        invalidate();
273e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
274e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
275c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen
276e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    @Override
277e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
27874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int length = isHoriz() ?
27974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                MeasureSpec.getSize(widthMeasureSpec) :
28074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                MeasureSpec.getSize(heightMeasureSpec);
28174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int arrowScrunch = (int) (ARROW_SCRUNCH_DIP * mDensity);
28274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int arrowH = mArrowShortLeftAndRight.getHeight();
283e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
284e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // by making the height less than arrow + bg, arrow and bg will be scrunched together,
285e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // overlaying somewhat (though on transparent portions of the drawable).
286e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // this works because the arrows are drawn from the top, and the rotary bg is drawn
287e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // from the bottom.
28874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int height = mBackgroundHeight + arrowH - arrowScrunch;
289e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
29074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        if (isHoriz()) {
29174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            setMeasuredDimension(length, height);
29274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        } else {
29374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            setMeasuredDimension(height, length);
29474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        }
29574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    }
296e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
297e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    @Override
298e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    protected void onDraw(Canvas canvas) {
299e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        super.onDraw(canvas);
30074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
30174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int width = getWidth();
30274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
3035037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller        if (VISUAL_DEBUG) {
3045037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            // draw bounding box around widget
3055037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            mPaint.setColor(0xffff0000);
3065037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            mPaint.setStyle(Paint.Style.STROKE);
3075037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            canvas.drawRect(0, 0, width, getHeight(), mPaint);
3085037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller        }
309e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
310e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        final int height = getHeight();
311e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
312e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // update animating state before we draw anything
313278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen        if (mAnimating) {
314896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            updateAnimation();
315e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
316e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
317e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // Background:
31874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        canvas.drawBitmap(mBackground, mBgMatrix, mPaint);
319e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
320e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // Draw the correct arrow(s) depending on the current state:
32174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        mArrowMatrix.reset();
322e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        switch (mGrabbedState) {
323e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen            case NOTHING_GRABBED:
32474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                //mArrowShortLeftAndRight;
325e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                break;
326e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen            case LEFT_HANDLE_GRABBED:
32774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                mArrowMatrix.setTranslate(0, 0);
32874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                if (!isHoriz()) {
32974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mArrowMatrix.preRotate(-90, 0, 0);
33074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mArrowMatrix.postTranslate(0, height);
33174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                }
33274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                canvas.drawBitmap(mArrowLongLeft, mArrowMatrix, mPaint);
333e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                break;
334e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen            case RIGHT_HANDLE_GRABBED:
33574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                mArrowMatrix.setTranslate(0, 0);
33674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                if (!isHoriz()) {
33774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mArrowMatrix.preRotate(-90, 0, 0);
33874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    // since bg width is > height of screen in landscape mode...
33974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mArrowMatrix.postTranslate(0, height + (mBackgroundWidth - height));
34074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                }
34174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                canvas.drawBitmap(mArrowLongRight, mArrowMatrix, mPaint);
342e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                break;
343e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen            default:
344e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                throw new IllegalStateException("invalid mGrabbedState: " + mGrabbedState);
345e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
346e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
34774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int bgHeight = mBackgroundHeight;
34874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int bgTop = isHoriz() ?
34974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                height - bgHeight:
35074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                width - bgHeight;
3515037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller
3525037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller        if (VISUAL_DEBUG) {
3535037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            // draw circle bounding arc drawable: good sanity check we're doing the math correctly
3545037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            float or = OUTER_ROTARY_RADIUS_DIP * mDensity;
3555037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            final int vOffset = mBackgroundWidth - height;
3565037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            final int midX = isHoriz() ? width / 2 : mBackgroundWidth / 2 - vOffset;
3575037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            if (isHoriz()) {
3585037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                canvas.drawCircle(midX, or + bgTop, or, mPaint);
3595037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            } else {
3605037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                canvas.drawCircle(or + bgTop, midX, or, mPaint);
3615037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            }
3625037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller        }
363d9b6f14a6926dce7ad3d98e6e30b503c69c904c0Jim Miller
36474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        // left dimple / icon
365e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        {
366896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            final int xOffset = mLeftHandleX + mRotaryOffsetX;
367e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen            final int drawableY = getYOnArc(
36874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mBackgroundWidth,
369896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mInnerRadius,
370896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mOuterRadius,
371e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                    xOffset);
3725037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            final int x = isHoriz() ? xOffset : drawableY + bgTop;
3735037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            final int y = isHoriz() ? drawableY + bgTop : height - xOffset;
3745037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            if (mGrabbedState != RIGHT_HANDLE_GRABBED) {
3755037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimple, canvas, x, y);
3765037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mLeftHandleIcon, canvas, x, y);
37774646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            } else {
3785037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimpleDim, canvas, x, y);
3795fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen            }
380e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
381e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
38274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        // center dimple
38374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        {
38474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            final int xOffset = isHoriz() ?
38574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    width / 2 + mRotaryOffsetX:
38674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    height / 2 + mRotaryOffsetX;
387e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen            final int drawableY = getYOnArc(
38874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mBackgroundWidth,
389896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mInnerRadius,
390896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mOuterRadius,
391e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                    xOffset);
392e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
39374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            if (isHoriz()) {
3945037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimpleDim, canvas, xOffset, drawableY + bgTop);
39574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            } else {
39674646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                // vertical
3975037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - xOffset);
39874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            }
399e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
400e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
40174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        // right dimple / icon
402e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        {
403896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            final int xOffset = mRightHandleX + mRotaryOffsetX;
404e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen            final int drawableY = getYOnArc(
40574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mBackgroundWidth,
406896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mInnerRadius,
407896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mOuterRadius,
408e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                    xOffset);
409e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
4105037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            final int x = isHoriz() ? xOffset : drawableY + bgTop;
4115037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            final int y = isHoriz() ? drawableY + bgTop : height - xOffset;
4125037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller            if (mGrabbedState != LEFT_HANDLE_GRABBED) {
4135037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimple, canvas, x, y);
4145037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mRightHandleIcon, canvas, x, y);
41574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            } else {
4165037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimpleDim, canvas, x, y);
4175fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen            }
418e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
419e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
420896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        // draw extra left hand dimples
421896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        int dimpleLeft = mRotaryOffsetX + mLeftHandleX - mDimpleSpacing;
422896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        final int halfdimple = mDimpleWidth / 2;
423896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        while (dimpleLeft > -halfdimple) {
424896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            final int drawableY = getYOnArc(
42574646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mBackgroundWidth,
426896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mInnerRadius,
427896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mOuterRadius,
428896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    dimpleLeft);
429896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
43074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            if (isHoriz()) {
4315037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimpleDim, canvas, dimpleLeft, drawableY + bgTop);
43274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            } else {
4335037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleLeft);
43474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            }
435896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            dimpleLeft -= mDimpleSpacing;
436896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        }
437896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
438896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        // draw extra right hand dimples
439896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        int dimpleRight = mRotaryOffsetX + mRightHandleX + mDimpleSpacing;
440896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        final int rightThresh = mRight + halfdimple;
441896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        while (dimpleRight < rightThresh) {
442896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            final int drawableY = getYOnArc(
44374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    mBackgroundWidth,
444896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mInnerRadius,
445896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mOuterRadius,
446896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    dimpleRight);
447896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
44874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            if (isHoriz()) {
4495037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimpleDim, canvas, dimpleRight, drawableY + bgTop);
45074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            } else {
4515037e57fd43bccf79be80bc140b33d1fa69abe13Jim Miller                drawCentered(mDimpleDim, canvas, drawableY + bgTop, height - dimpleRight);
45274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            }
453896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            dimpleRight += mDimpleSpacing;
454896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        }
455e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
456e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
457e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
45874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen     * Assuming bitmap is a bounding box around a piece of an arc drawn by two concentric circles
459e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * (as the background drawable for the rotary widget is), and given an x coordinate along the
460e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * drawable, return the y coordinate of a point on the arc that is between the two concentric
461e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * circles.  The resulting y combined with the incoming x is a point along the circle in
462e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * between the two concentric circles.
463e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *
46474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen     * @param backgroundWidth The width of the asset (the bottom of the box surrounding the arc).
465e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * @param innerRadius The radius of the circle that intersects the drawable at the bottom two
466e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *        corders of the drawable (top two corners in terms of drawing coordinates).
467e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * @param outerRadius The radius of the circle who's top most point is the top center of the
468e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *        drawable (bottom center in terms of drawing coordinates).
46974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen     * @param x The distance along the x axis of the desired point.    @return The y coordinate, in drawing coordinates, that will place (x, y) along the circle
470e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *        in between the two concentric circles.
471e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
47274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen    private int getYOnArc(int backgroundWidth, int innerRadius, int outerRadius, int x) {
473e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
474e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // the hypotenuse
475e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        final int halfWidth = (outerRadius - innerRadius) / 2;
476e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        final int middleRadius = innerRadius + halfWidth;
477e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
478e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // the bottom leg of the triangle
47974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int triangleBottom = (backgroundWidth / 2) - x;
480e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
481e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // "Our offense is like the pythagorean theorem: There is no answer!" - Shaquille O'Neal
482e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        final int triangleY =
483e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                (int) Math.sqrt(middleRadius * middleRadius - triangleBottom * triangleBottom);
484e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
485e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // convert to drawing coordinates:
486e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // middleRadius - triangleY =
487e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        //   the vertical distance from the outer edge of the circle to the desired point
488e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        // from there we add the distance from the top of the drawable to the middle circle
489e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        return middleRadius - triangleY + halfWidth;
490e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
491e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
492e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
493e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Handle touch screen events.
494e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *
495e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * @param event The motion event.
496e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * @return True if the event was handled, false otherwise.
497e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
498e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    @Override
499e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    public boolean onTouchEvent(MotionEvent event) {
500278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen        if (mAnimating) {
501e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen            return true;
502e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
503896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        if (mVelocityTracker == null) {
504896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            mVelocityTracker = VelocityTracker.obtain();
505896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        }
506896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mVelocityTracker.addMovement(event);
507896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
50874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int height = getHeight();
509e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
51074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final int eventX = isHoriz() ?
51174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                (int) event.getX():
51274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                height - ((int) event.getY());
513278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen        final int hitWindow = mDimpleWidth;
514e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
5151ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen        final int action = event.getAction();
5161ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen        switch (action) {
5171ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen            case MotionEvent.ACTION_DOWN:
5181ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                if (DBG) log("touch-down");
5191ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                mTriggered = false;
5201ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                if (mGrabbedState != NOTHING_GRABBED) {
5211ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    reset();
5221ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    invalidate();
523e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                }
5241ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                if (eventX < mLeftHandleX + hitWindow) {
525896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mRotaryOffsetX = eventX - mLeftHandleX;
52688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown                    setGrabbedState(LEFT_HANDLE_GRABBED);
5271ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    invalidate();
5281ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    vibrate(VIBRATE_SHORT);
5291ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                } else if (eventX > mRightHandleX - hitWindow) {
530896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mRotaryOffsetX = eventX - mRightHandleX;
53188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown                    setGrabbedState(RIGHT_HANDLE_GRABBED);
5321ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    invalidate();
5331ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    vibrate(VIBRATE_SHORT);
534e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen                }
5351ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                break;
536e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
5371ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen            case MotionEvent.ACTION_MOVE:
5381ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                if (DBG) log("touch-move");
5391ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                if (mGrabbedState == LEFT_HANDLE_GRABBED) {
540896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mRotaryOffsetX = eventX - mLeftHandleX;
5411ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    invalidate();
54274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    final int rightThresh = isHoriz() ? getRight() : height;
54374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                    if (eventX >= rightThresh - mEdgeTriggerThresh && !mTriggered) {
5441ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                        mTriggered = true;
545278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen                        dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE);
546896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                        final VelocityTracker velocityTracker = mVelocityTracker;
547896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
54874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                        final int rawVelocity = isHoriz() ?
54974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                                (int) velocityTracker.getXVelocity():
55074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                                -(int) velocityTracker.getYVelocity();
55174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                        final int velocity = Math.max(mMinimumVelocity, rawVelocity);
552896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                        mDimplesOfFling = Math.max(
553896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                8,
554896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                Math.abs(velocity / mDimpleSpacing));
555896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                        startAnimationWithVelocity(
556896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                eventX - mLeftHandleX,
557896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                mDimplesOfFling * mDimpleSpacing,
558896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                velocity);
5591ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    }
5601ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                } else if (mGrabbedState == RIGHT_HANDLE_GRABBED) {
561896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mRotaryOffsetX = eventX - mRightHandleX;
5621ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    invalidate();
5635fef93b2a827cfafee04d7cfb827262c9b75fd91Karl Rosaen                    if (eventX <= mEdgeTriggerThresh && !mTriggered) {
5641ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                        mTriggered = true;
565278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen                        dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE);
566896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                        final VelocityTracker velocityTracker = mVelocityTracker;
567896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
56874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                        final int rawVelocity = isHoriz() ?
56974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                                (int) velocityTracker.getXVelocity():
57074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                                - (int) velocityTracker.getYVelocity();
57174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                        final int velocity = Math.min(-mMinimumVelocity, rawVelocity);
572896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                        mDimplesOfFling = Math.max(
573896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                8,
574896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                Math.abs(velocity / mDimpleSpacing));
575896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                        startAnimationWithVelocity(
576896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                eventX - mRightHandleX,
577896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                -(mDimplesOfFling * mDimpleSpacing),
578896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                                velocity);
5791ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                    }
5801ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                }
5811ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                break;
5821ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen            case MotionEvent.ACTION_UP:
5831ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                if (DBG) log("touch-up");
5841ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                // handle animating back to start if they didn't trigger
5851ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                if (mGrabbedState == LEFT_HANDLE_GRABBED
5861ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                        && Math.abs(eventX - mLeftHandleX) > 5) {
587278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen                    // set up "snap back" animation
588896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    startAnimation(eventX - mLeftHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS);
5891ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                } else if (mGrabbedState == RIGHT_HANDLE_GRABBED
5901ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                        && Math.abs(eventX - mRightHandleX) > 5) {
591278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen                    // set up "snap back" animation
592896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    startAnimation(eventX - mRightHandleX, 0, SNAP_BACK_ANIMATION_DURATION_MILLIS);
5931ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                }
594896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                mRotaryOffsetX = 0;
59588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown                setGrabbedState(NOTHING_GRABBED);
5961ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                invalidate();
597896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                if (mVelocityTracker != null) {
598896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mVelocityTracker.recycle(); // wishin' we had generational GC
599896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mVelocityTracker = null;
600896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                }
6011ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                break;
6021ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen            case MotionEvent.ACTION_CANCEL:
6031ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                if (DBG) log("touch-cancel");
6041ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                reset();
6051ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                invalidate();
606896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                if (mVelocityTracker != null) {
607896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mVelocityTracker.recycle();
608896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                    mVelocityTracker = null;
609896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                }
6101ca654e1193010365de10f55cbfebe1521c71db4Karl Rosaen                break;
611e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
612e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        return true;
613e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
614e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
615896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private void startAnimation(int startX, int endX, int duration) {
616896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimating = true;
617896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimationStartTime = currentAnimationTimeMillis();
618896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimationDuration = duration;
619896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimatingDeltaXStart = startX;
620896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimatingDeltaXEnd = endX;
62188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown        setGrabbedState(NOTHING_GRABBED);
622896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mDimplesOfFling = 0;
623896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        invalidate();
624896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    }
625896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
626896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private void startAnimationWithVelocity(int startX, int endX, int pixelsPerSecond) {
627896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimating = true;
628896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimationStartTime = currentAnimationTimeMillis();
629896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimationDuration = 1000 * (endX - startX) / pixelsPerSecond;
630896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimatingDeltaXStart = startX;
631896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mAnimatingDeltaXEnd = endX;
63288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown        setGrabbedState(NOTHING_GRABBED);
633896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        invalidate();
634896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    }
635896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
636896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    private void updateAnimation() {
637896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        final long millisSoFar = currentAnimationTimeMillis() - mAnimationStartTime;
638896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        final long millisLeft = mAnimationDuration - millisSoFar;
639896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        final int totalDeltaX = mAnimatingDeltaXStart - mAnimatingDeltaXEnd;
64074646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        final boolean goingRight = totalDeltaX < 0;
641896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        if (DBG) log("millisleft for animating: " + millisLeft);
642896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        if (millisLeft <= 0) {
643896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            reset();
644896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            return;
645896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        }
646896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        // from 0 to 1 as animation progresses
647896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        float interpolation =
648896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                mInterpolator.getInterpolation((float) millisSoFar / mAnimationDuration);
649896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        final int dx = (int) (totalDeltaX * (1 - interpolation));
650896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mRotaryOffsetX = mAnimatingDeltaXEnd + dx;
65174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen
65274646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        // once we have gone far enough to animate the current buttons off screen, we start
65374646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        // wrapping the offset back to the other side so that when the animation is finished,
65474646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen        // the buttons will come back into their original places.
655896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        if (mDimplesOfFling > 0) {
656ff9c54b7214e99b3182f53f3089da0503f3edddcKarl Rosaen            if (!goingRight && mRotaryOffsetX < -3 * mDimpleSpacing) {
657896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                // wrap around on fling left
65874646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                mRotaryOffsetX += mDimplesOfFling * mDimpleSpacing;
65974646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen            } else if (goingRight && mRotaryOffsetX > 3 * mDimpleSpacing) {
660896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen                // wrap around on fling right
66174646ad618a9ca289efa99b4a822e66ca61b8f95Karl Rosaen                mRotaryOffsetX -= mDimplesOfFling * mDimpleSpacing;
662896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen            }
663896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        }
664896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        invalidate();
665896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen    }
666896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen
667e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private void reset() {
668e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        mAnimating = false;
669896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mRotaryOffsetX = 0;
670896264f603ad90e58557c5d3f34bc6389fff3fc5Karl Rosaen        mDimplesOfFling = 0;
67188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown        setGrabbedState(NOTHING_GRABBED);
672e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        mTriggered = false;
673e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
674e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
675e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
676e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Triggers haptic feedback.
677e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
678e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private synchronized void vibrate(long duration) {
679723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey        final boolean hapticEnabled = Settings.System.getIntForUser(
680723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey                mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
681723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey                UserHandle.USER_CURRENT) != 0;
682723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey        if (hapticEnabled) {
683723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey            if (mVibrator == null) {
684723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey                mVibrator = (android.os.Vibrator) getContext()
685723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey                        .getSystemService(Context.VIBRATOR_SERVICE);
686723a725e790d269f32980116e775d3d7f0037865Jeff Sharkey            }
6877b41467704f941b11af6aace3e40993afc7f6c6fJohn Spurlock            mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES);
688e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
689e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
690e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
691e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
692c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen     * Draw the bitmap so that it's centered
693c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen     * on the point (x,y), then draws it using specified canvas.
694e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * TODO: is there already a utility method somewhere for this?
695e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
696c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen    private void drawCentered(Bitmap d, Canvas c, int x, int y) {
697c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen        int w = d.getWidth();
698c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen        int h = d.getHeight();
699c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen
700c8ad6dcb8abcf5f7296faecdcfa93a01fb4011b9Karl Rosaen        c.drawBitmap(d, x - (w / 2), y - (h / 2), mPaint);
701e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
702e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
703e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
704e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
705e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Registers a callback to be invoked when the dial
706e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * is "triggered" by rotating it one way or the other.
707e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     *
708e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * @param l the OnDialTriggerListener to attach to this view
709e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
710e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    public void setOnDialTriggerListener(OnDialTriggerListener l) {
711e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        mOnDialTriggerListener = l;
712e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
713e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
714e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
715e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Dispatches a trigger event to our listener.
716e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
717278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen    private void dispatchTriggerEvent(int whichHandle) {
718e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        vibrate(VIBRATE_LONG);
719e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        if (mOnDialTriggerListener != null) {
720278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen            mOnDialTriggerListener.onDialTrigger(this, whichHandle);
721e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        }
722e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
723e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
724e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    /**
72588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown     * Sets the current grabbed state, and dispatches a grabbed state change
72688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown     * event to our listener.
72788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown     */
72888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown    private void setGrabbedState(int newState) {
72988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown        if (newState != mGrabbedState) {
73088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown            mGrabbedState = newState;
73188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown            if (mOnDialTriggerListener != null) {
73288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown                mOnDialTriggerListener.onGrabbedStateChange(this, mGrabbedState);
73388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown            }
73488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown        }
73588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown    }
73688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown
73788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown    /**
738e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * Interface definition for a callback to be invoked when the dial
739e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     * is "triggered" by rotating it one way or the other.
740e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen     */
741e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    public interface OnDialTriggerListener {
742e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        /**
743e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         * The dial was triggered because the user grabbed the left handle,
744e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         * and rotated the dial clockwise.
745e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         */
746e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        public static final int LEFT_HANDLE = 1;
747e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
748e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        /**
749e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         * The dial was triggered because the user grabbed the right handle,
750e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         * and rotated the dial counterclockwise.
751e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         */
752e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        public static final int RIGHT_HANDLE = 2;
753e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
754e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        /**
755e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         * Called when the dial is triggered.
756e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         *
757e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         * @param v The view that was triggered
758e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         * @param whichHandle  Which "dial handle" the user grabbed,
759278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen         *        either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}.
760e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen         */
761278ec5d6f57f01fb5d9acce5794daf66f5535baaKarl Rosaen        void onDialTrigger(View v, int whichHandle);
76288e037577f7db140e4ef88b77eefaa910e06e5f5David Brown
76388e037577f7db140e4ef88b77eefaa910e06e5f5David Brown        /**
76488e037577f7db140e4ef88b77eefaa910e06e5f5David Brown         * Called when the "grabbed state" changes (i.e. when
76588e037577f7db140e4ef88b77eefaa910e06e5f5David Brown         * the user either grabs or releases one of the handles.)
76688e037577f7db140e4ef88b77eefaa910e06e5f5David Brown         *
76788e037577f7db140e4ef88b77eefaa910e06e5f5David Brown         * @param v the view that was triggered
76888e037577f7db140e4ef88b77eefaa910e06e5f5David Brown         * @param grabbedState the new state: either {@link #NOTHING_GRABBED},
76988e037577f7db140e4ef88b77eefaa910e06e5f5David Brown         * {@link #LEFT_HANDLE_GRABBED}, or {@link #RIGHT_HANDLE_GRABBED}.
77088e037577f7db140e4ef88b77eefaa910e06e5f5David Brown         */
77188e037577f7db140e4ef88b77eefaa910e06e5f5David Brown        void onGrabbedStateChange(View v, int grabbedState);
772e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
773e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
774e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
775e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    // Debugging / testing code
776e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen
777e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    private void log(String msg) {
778e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen        Log.d(LOG_TAG, msg);
779e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen    }
780e4d95d02a25fb6596a3bf622ba57d4145773da90Karl Rosaen}
781