Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2012 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.internal.widget.multiwaveview; |
| 18 | |
| 19 | import android.animation.Animator; |
| 20 | import android.animation.Animator.AnimatorListener; |
| 21 | import android.animation.AnimatorListenerAdapter; |
| 22 | import android.animation.TimeInterpolator; |
| 23 | import android.animation.ValueAnimator; |
| 24 | import android.animation.ValueAnimator.AnimatorUpdateListener; |
| 25 | import android.content.ComponentName; |
| 26 | import android.content.Context; |
| 27 | import android.content.pm.PackageManager; |
| 28 | import android.content.pm.PackageManager.NameNotFoundException; |
| 29 | import android.content.res.Resources; |
| 30 | import android.content.res.TypedArray; |
| 31 | import android.graphics.Canvas; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 32 | import android.graphics.drawable.Drawable; |
| 33 | import android.os.Bundle; |
Jeff Sharkey | 723a725 | 2012-10-12 14:26:31 -0700 | [diff] [blame] | 34 | import android.os.UserHandle; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 35 | import android.os.Vibrator; |
Jeff Sharkey | 723a725 | 2012-10-12 14:26:31 -0700 | [diff] [blame] | 36 | import android.provider.Settings; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 37 | import android.text.TextUtils; |
| 38 | import android.util.AttributeSet; |
| 39 | import android.util.Log; |
| 40 | import android.util.TypedValue; |
| 41 | import android.view.Gravity; |
| 42 | import android.view.MotionEvent; |
| 43 | import android.view.View; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 44 | import android.view.accessibility.AccessibilityManager; |
| 45 | |
| 46 | import com.android.internal.R; |
| 47 | |
| 48 | import java.util.ArrayList; |
| 49 | |
| 50 | /** |
| 51 | * A re-usable widget containing a center, outer ring and wave animation. |
| 52 | */ |
| 53 | public class GlowPadView extends View { |
| 54 | private static final String TAG = "GlowPadView"; |
| 55 | private static final boolean DEBUG = false; |
| 56 | |
| 57 | // Wave state machine |
| 58 | private static final int STATE_IDLE = 0; |
| 59 | private static final int STATE_START = 1; |
| 60 | private static final int STATE_FIRST_TOUCH = 2; |
| 61 | private static final int STATE_TRACKING = 3; |
| 62 | private static final int STATE_SNAP = 4; |
| 63 | private static final int STATE_FINISH = 5; |
| 64 | |
| 65 | // Animation properties. |
| 66 | private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it |
| 67 | |
| 68 | public interface OnTriggerListener { |
| 69 | int NO_HANDLE = 0; |
| 70 | int CENTER_HANDLE = 1; |
| 71 | public void onGrabbed(View v, int handle); |
| 72 | public void onReleased(View v, int handle); |
| 73 | public void onTrigger(View v, int target); |
| 74 | public void onGrabbedStateChange(View v, int handle); |
| 75 | public void onFinishFinalAnimation(); |
| 76 | } |
| 77 | |
| 78 | // Tuneable parameters for animation |
Chet Haase | 167a323 | 2013-10-02 16:11:54 -0700 | [diff] [blame] | 79 | private static final int WAVE_ANIMATION_DURATION = 1000; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 80 | private static final int RETURN_TO_HOME_DELAY = 1200; |
| 81 | private static final int RETURN_TO_HOME_DURATION = 200; |
| 82 | private static final int HIDE_ANIMATION_DELAY = 200; |
| 83 | private static final int HIDE_ANIMATION_DURATION = 200; |
| 84 | private static final int SHOW_ANIMATION_DURATION = 200; |
| 85 | private static final int SHOW_ANIMATION_DELAY = 50; |
| 86 | private static final int INITIAL_SHOW_HANDLE_DURATION = 200; |
| 87 | private static final int REVEAL_GLOW_DELAY = 0; |
| 88 | private static final int REVEAL_GLOW_DURATION = 0; |
| 89 | |
| 90 | private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f; |
| 91 | private static final float TARGET_SCALE_EXPANDED = 1.0f; |
| 92 | private static final float TARGET_SCALE_COLLAPSED = 0.8f; |
| 93 | private static final float RING_SCALE_EXPANDED = 1.0f; |
| 94 | private static final float RING_SCALE_COLLAPSED = 0.5f; |
| 95 | |
| 96 | private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>(); |
| 97 | private AnimationBundle mWaveAnimations = new AnimationBundle(); |
| 98 | private AnimationBundle mTargetAnimations = new AnimationBundle(); |
| 99 | private AnimationBundle mGlowAnimations = new AnimationBundle(); |
| 100 | private ArrayList<String> mTargetDescriptions; |
| 101 | private ArrayList<String> mDirectionDescriptions; |
| 102 | private OnTriggerListener mOnTriggerListener; |
| 103 | private TargetDrawable mHandleDrawable; |
| 104 | private TargetDrawable mOuterRing; |
| 105 | private Vibrator mVibrator; |
| 106 | |
| 107 | private int mFeedbackCount = 3; |
| 108 | private int mVibrationDuration = 0; |
| 109 | private int mGrabbedState; |
| 110 | private int mActiveTarget = -1; |
| 111 | private float mGlowRadius; |
| 112 | private float mWaveCenterX; |
| 113 | private float mWaveCenterY; |
| 114 | private int mMaxTargetHeight; |
| 115 | private int mMaxTargetWidth; |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 116 | private float mRingScaleFactor = 1f; |
Adam Cohen | f988bdf | 2012-11-07 14:28:23 -0800 | [diff] [blame] | 117 | private boolean mAllowScaling; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 118 | |
| 119 | private float mOuterRadius = 0.0f; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 120 | private float mSnapMargin = 0.0f; |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 121 | private float mFirstItemOffset = 0.0f; |
| 122 | private boolean mMagneticTargets = false; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 123 | private boolean mDragging; |
| 124 | private int mNewTargetResources; |
| 125 | |
| 126 | private class AnimationBundle extends ArrayList<Tweener> { |
| 127 | private static final long serialVersionUID = 0xA84D78726F127468L; |
| 128 | private boolean mSuspended; |
| 129 | |
| 130 | public void start() { |
| 131 | if (mSuspended) return; // ignore attempts to start animations |
| 132 | final int count = size(); |
| 133 | for (int i = 0; i < count; i++) { |
| 134 | Tweener anim = get(i); |
| 135 | anim.animator.start(); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | public void cancel() { |
| 140 | final int count = size(); |
| 141 | for (int i = 0; i < count; i++) { |
| 142 | Tweener anim = get(i); |
| 143 | anim.animator.cancel(); |
| 144 | } |
| 145 | clear(); |
| 146 | } |
| 147 | |
| 148 | public void stop() { |
| 149 | final int count = size(); |
| 150 | for (int i = 0; i < count; i++) { |
| 151 | Tweener anim = get(i); |
| 152 | anim.animator.end(); |
| 153 | } |
| 154 | clear(); |
| 155 | } |
| 156 | |
| 157 | public void setSuspended(boolean suspend) { |
| 158 | mSuspended = suspend; |
| 159 | } |
| 160 | }; |
| 161 | |
| 162 | private AnimatorListener mResetListener = new AnimatorListenerAdapter() { |
| 163 | public void onAnimationEnd(Animator animator) { |
| 164 | switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); |
| 165 | dispatchOnFinishFinalAnimation(); |
| 166 | } |
| 167 | }; |
| 168 | |
| 169 | private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() { |
| 170 | public void onAnimationEnd(Animator animator) { |
| 171 | ping(); |
| 172 | switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); |
| 173 | dispatchOnFinishFinalAnimation(); |
| 174 | } |
| 175 | }; |
| 176 | |
| 177 | private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() { |
| 178 | public void onAnimationUpdate(ValueAnimator animation) { |
| 179 | invalidate(); |
| 180 | } |
| 181 | }; |
| 182 | |
| 183 | private boolean mAnimatingTargets; |
| 184 | private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() { |
| 185 | public void onAnimationEnd(Animator animator) { |
| 186 | if (mNewTargetResources != 0) { |
| 187 | internalSetTargetResources(mNewTargetResources); |
| 188 | mNewTargetResources = 0; |
| 189 | hideTargets(false, false); |
| 190 | } |
| 191 | mAnimatingTargets = false; |
| 192 | } |
| 193 | }; |
| 194 | private int mTargetResourceId; |
| 195 | private int mTargetDescriptionsResourceId; |
| 196 | private int mDirectionDescriptionsResourceId; |
| 197 | private boolean mAlwaysTrackFinger; |
| 198 | private int mHorizontalInset; |
| 199 | private int mVerticalInset; |
| 200 | private int mGravity = Gravity.TOP; |
| 201 | private boolean mInitialLayout = true; |
| 202 | private Tweener mBackgroundAnimator; |
| 203 | private PointCloud mPointCloud; |
| 204 | private float mInnerRadius; |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 205 | private int mPointerId; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 206 | |
| 207 | public GlowPadView(Context context) { |
| 208 | this(context, null); |
| 209 | } |
| 210 | |
| 211 | public GlowPadView(Context context, AttributeSet attrs) { |
| 212 | super(context, attrs); |
| 213 | Resources res = context.getResources(); |
| 214 | |
| 215 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GlowPadView); |
| 216 | mInnerRadius = a.getDimension(R.styleable.GlowPadView_innerRadius, mInnerRadius); |
| 217 | mOuterRadius = a.getDimension(R.styleable.GlowPadView_outerRadius, mOuterRadius); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 218 | mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin); |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 219 | mFirstItemOffset = (float) Math.toRadians( |
| 220 | a.getFloat(R.styleable.GlowPadView_firstItemOffset, |
| 221 | (float) Math.toDegrees(mFirstItemOffset))); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 222 | mVibrationDuration = a.getInt(R.styleable.GlowPadView_vibrationDuration, |
| 223 | mVibrationDuration); |
| 224 | mFeedbackCount = a.getInt(R.styleable.GlowPadView_feedbackCount, |
| 225 | mFeedbackCount); |
Adam Cohen | f988bdf | 2012-11-07 14:28:23 -0800 | [diff] [blame] | 226 | mAllowScaling = a.getBoolean(R.styleable.GlowPadView_allowScaling, false); |
Jim Miller | a592d22 | 2012-06-29 17:41:25 -0700 | [diff] [blame] | 227 | TypedValue handle = a.peekValue(R.styleable.GlowPadView_handleDrawable); |
| 228 | mHandleDrawable = new TargetDrawable(res, handle != null ? handle.resourceId : 0); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 229 | mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); |
| 230 | mOuterRing = new TargetDrawable(res, |
| 231 | getResourceId(a, R.styleable.GlowPadView_outerRingDrawable)); |
| 232 | |
| 233 | mAlwaysTrackFinger = a.getBoolean(R.styleable.GlowPadView_alwaysTrackFinger, false); |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 234 | mMagneticTargets = a.getBoolean(R.styleable.GlowPadView_magneticTargets, mMagneticTargets); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 235 | |
| 236 | int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable); |
Alan Viverette | 8eea3ea | 2014-02-03 18:40:20 -0800 | [diff] [blame] | 237 | Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 238 | mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f); |
| 239 | |
| 240 | TypedValue outValue = new TypedValue(); |
| 241 | |
| 242 | // Read array of target drawables |
| 243 | if (a.getValue(R.styleable.GlowPadView_targetDrawables, outValue)) { |
| 244 | internalSetTargetResources(outValue.resourceId); |
| 245 | } |
| 246 | if (mTargetDrawables == null || mTargetDrawables.size() == 0) { |
| 247 | throw new IllegalStateException("Must specify at least one target drawable"); |
| 248 | } |
| 249 | |
| 250 | // Read array of target descriptions |
| 251 | if (a.getValue(R.styleable.GlowPadView_targetDescriptions, outValue)) { |
| 252 | final int resourceId = outValue.resourceId; |
| 253 | if (resourceId == 0) { |
| 254 | throw new IllegalStateException("Must specify target descriptions"); |
| 255 | } |
| 256 | setTargetDescriptionsResourceId(resourceId); |
| 257 | } |
| 258 | |
| 259 | // Read array of direction descriptions |
| 260 | if (a.getValue(R.styleable.GlowPadView_directionDescriptions, outValue)) { |
| 261 | final int resourceId = outValue.resourceId; |
| 262 | if (resourceId == 0) { |
| 263 | throw new IllegalStateException("Must specify direction descriptions"); |
| 264 | } |
| 265 | setDirectionDescriptionsResourceId(resourceId); |
| 266 | } |
| 267 | |
Adam Powell | 962159a | 2012-10-23 16:09:29 -0700 | [diff] [blame] | 268 | mGravity = a.getInt(R.styleable.GlowPadView_gravity, Gravity.TOP); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 269 | |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 270 | a.recycle(); |
| 271 | |
| 272 | setVibrateEnabled(mVibrationDuration > 0); |
| 273 | |
| 274 | assignDefaultsIfNeeded(); |
| 275 | |
| 276 | mPointCloud = new PointCloud(pointDrawable); |
| 277 | mPointCloud.makePointCloud(mInnerRadius, mOuterRadius); |
| 278 | mPointCloud.glowManager.setRadius(mGlowRadius); |
| 279 | } |
| 280 | |
| 281 | private int getResourceId(TypedArray a, int id) { |
| 282 | TypedValue tv = a.peekValue(id); |
| 283 | return tv == null ? 0 : tv.resourceId; |
| 284 | } |
| 285 | |
| 286 | private void dump() { |
| 287 | Log.v(TAG, "Outer Radius = " + mOuterRadius); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 288 | Log.v(TAG, "SnapMargin = " + mSnapMargin); |
| 289 | Log.v(TAG, "FeedbackCount = " + mFeedbackCount); |
| 290 | Log.v(TAG, "VibrationDuration = " + mVibrationDuration); |
| 291 | Log.v(TAG, "GlowRadius = " + mGlowRadius); |
| 292 | Log.v(TAG, "WaveCenterX = " + mWaveCenterX); |
| 293 | Log.v(TAG, "WaveCenterY = " + mWaveCenterY); |
| 294 | } |
| 295 | |
| 296 | public void suspendAnimations() { |
| 297 | mWaveAnimations.setSuspended(true); |
| 298 | mTargetAnimations.setSuspended(true); |
| 299 | mGlowAnimations.setSuspended(true); |
| 300 | } |
| 301 | |
| 302 | public void resumeAnimations() { |
| 303 | mWaveAnimations.setSuspended(false); |
| 304 | mTargetAnimations.setSuspended(false); |
| 305 | mGlowAnimations.setSuspended(false); |
| 306 | mWaveAnimations.start(); |
| 307 | mTargetAnimations.start(); |
| 308 | mGlowAnimations.start(); |
| 309 | } |
| 310 | |
| 311 | @Override |
| 312 | protected int getSuggestedMinimumWidth() { |
| 313 | // View should be large enough to contain the background + handle and |
| 314 | // target drawable on either edge. |
| 315 | return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth); |
| 316 | } |
| 317 | |
| 318 | @Override |
| 319 | protected int getSuggestedMinimumHeight() { |
| 320 | // View should be large enough to contain the unlock ring + target and |
| 321 | // target drawable on either edge |
| 322 | return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight); |
| 323 | } |
| 324 | |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 325 | /** |
| 326 | * This gets the suggested width accounting for the ring's scale factor. |
| 327 | */ |
| 328 | protected int getScaledSuggestedMinimumWidth() { |
| 329 | return (int) (mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) |
| 330 | + mMaxTargetWidth); |
| 331 | } |
| 332 | |
| 333 | /** |
| 334 | * This gets the suggested height accounting for the ring's scale factor. |
| 335 | */ |
| 336 | protected int getScaledSuggestedMinimumHeight() { |
| 337 | return (int) (mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) |
| 338 | + mMaxTargetHeight); |
| 339 | } |
| 340 | |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 341 | private int resolveMeasured(int measureSpec, int desired) |
| 342 | { |
| 343 | int result = 0; |
| 344 | int specSize = MeasureSpec.getSize(measureSpec); |
| 345 | switch (MeasureSpec.getMode(measureSpec)) { |
| 346 | case MeasureSpec.UNSPECIFIED: |
| 347 | result = desired; |
| 348 | break; |
| 349 | case MeasureSpec.AT_MOST: |
| 350 | result = Math.min(specSize, desired); |
| 351 | break; |
| 352 | case MeasureSpec.EXACTLY: |
| 353 | default: |
| 354 | result = specSize; |
| 355 | } |
| 356 | return result; |
| 357 | } |
| 358 | |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 359 | private void switchToState(int state, float x, float y) { |
| 360 | switch (state) { |
| 361 | case STATE_IDLE: |
| 362 | deactivateTargets(); |
| 363 | hideGlow(0, 0, 0.0f, null); |
| 364 | startBackgroundAnimation(0, 0.0f); |
| 365 | mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); |
| 366 | mHandleDrawable.setAlpha(1.0f); |
| 367 | break; |
| 368 | |
| 369 | case STATE_START: |
| 370 | startBackgroundAnimation(0, 0.0f); |
| 371 | break; |
| 372 | |
| 373 | case STATE_FIRST_TOUCH: |
| 374 | mHandleDrawable.setAlpha(0.0f); |
| 375 | deactivateTargets(); |
| 376 | showTargets(true); |
| 377 | startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f); |
| 378 | setGrabbedState(OnTriggerListener.CENTER_HANDLE); |
| 379 | if (AccessibilityManager.getInstance(mContext).isEnabled()) { |
| 380 | announceTargets(); |
| 381 | } |
| 382 | break; |
| 383 | |
| 384 | case STATE_TRACKING: |
| 385 | mHandleDrawable.setAlpha(0.0f); |
| 386 | showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 1.0f, null); |
| 387 | break; |
| 388 | |
| 389 | case STATE_SNAP: |
| 390 | // TODO: Add transition states (see list_selector_background_transition.xml) |
| 391 | mHandleDrawable.setAlpha(0.0f); |
| 392 | showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 0.0f, null); |
| 393 | break; |
| 394 | |
| 395 | case STATE_FINISH: |
| 396 | doFinish(); |
| 397 | break; |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | private void showGlow(int duration, int delay, float finalAlpha, |
| 402 | AnimatorListener finishListener) { |
| 403 | mGlowAnimations.cancel(); |
| 404 | mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration, |
| 405 | "ease", Ease.Cubic.easeIn, |
| 406 | "delay", delay, |
| 407 | "alpha", finalAlpha, |
| 408 | "onUpdate", mUpdateListener, |
| 409 | "onComplete", finishListener)); |
| 410 | mGlowAnimations.start(); |
| 411 | } |
| 412 | |
| 413 | private void hideGlow(int duration, int delay, float finalAlpha, |
| 414 | AnimatorListener finishListener) { |
| 415 | mGlowAnimations.cancel(); |
| 416 | mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration, |
| 417 | "ease", Ease.Quart.easeOut, |
| 418 | "delay", delay, |
| 419 | "alpha", finalAlpha, |
| 420 | "x", 0.0f, |
| 421 | "y", 0.0f, |
| 422 | "onUpdate", mUpdateListener, |
| 423 | "onComplete", finishListener)); |
| 424 | mGlowAnimations.start(); |
| 425 | } |
| 426 | |
| 427 | private void deactivateTargets() { |
| 428 | final int count = mTargetDrawables.size(); |
| 429 | for (int i = 0; i < count; i++) { |
| 430 | TargetDrawable target = mTargetDrawables.get(i); |
| 431 | target.setState(TargetDrawable.STATE_INACTIVE); |
| 432 | } |
| 433 | mActiveTarget = -1; |
| 434 | } |
| 435 | |
| 436 | /** |
| 437 | * Dispatches a trigger event to listener. Ignored if a listener is not set. |
| 438 | * @param whichTarget the target that was triggered. |
| 439 | */ |
| 440 | private void dispatchTriggerEvent(int whichTarget) { |
| 441 | vibrate(); |
| 442 | if (mOnTriggerListener != null) { |
| 443 | mOnTriggerListener.onTrigger(this, whichTarget); |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | private void dispatchOnFinishFinalAnimation() { |
| 448 | if (mOnTriggerListener != null) { |
| 449 | mOnTriggerListener.onFinishFinalAnimation(); |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | private void doFinish() { |
| 454 | final int activeTarget = mActiveTarget; |
| 455 | final boolean targetHit = activeTarget != -1; |
| 456 | |
| 457 | if (targetHit) { |
| 458 | if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit); |
| 459 | |
| 460 | highlightSelected(activeTarget); |
| 461 | |
| 462 | // Inform listener of any active targets. Typically only one will be active. |
| 463 | hideGlow(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener); |
| 464 | dispatchTriggerEvent(activeTarget); |
| 465 | if (!mAlwaysTrackFinger) { |
| 466 | // Force ring and targets to finish animation to final expanded state |
| 467 | mTargetAnimations.stop(); |
| 468 | } |
| 469 | } else { |
| 470 | // Animate handle back to the center based on current state. |
| 471 | hideGlow(HIDE_ANIMATION_DURATION, 0, 0.0f, mResetListenerWithPing); |
| 472 | hideTargets(true, false); |
| 473 | } |
| 474 | |
| 475 | setGrabbedState(OnTriggerListener.NO_HANDLE); |
| 476 | } |
| 477 | |
| 478 | private void highlightSelected(int activeTarget) { |
| 479 | // Highlight the given target and fade others |
| 480 | mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE); |
| 481 | hideUnselected(activeTarget); |
| 482 | } |
| 483 | |
| 484 | private void hideUnselected(int active) { |
| 485 | for (int i = 0; i < mTargetDrawables.size(); i++) { |
| 486 | if (i != active) { |
| 487 | mTargetDrawables.get(i).setAlpha(0.0f); |
| 488 | } |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | private void hideTargets(boolean animate, boolean expanded) { |
| 493 | mTargetAnimations.cancel(); |
| 494 | // Note: these animations should complete at the same time so that we can swap out |
| 495 | // the target assets asynchronously from the setTargetResources() call. |
| 496 | mAnimatingTargets = animate; |
| 497 | final int duration = animate ? HIDE_ANIMATION_DURATION : 0; |
| 498 | final int delay = animate ? HIDE_ANIMATION_DELAY : 0; |
| 499 | |
Jim Miller | a7da8af | 2012-06-19 16:17:19 -0700 | [diff] [blame] | 500 | final float targetScale = expanded ? |
Jim Miller | 5892e2e | 2012-06-18 17:04:58 -0700 | [diff] [blame] | 501 | TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 502 | final int length = mTargetDrawables.size(); |
| 503 | final TimeInterpolator interpolator = Ease.Cubic.easeOut; |
| 504 | for (int i = 0; i < length; i++) { |
| 505 | TargetDrawable target = mTargetDrawables.get(i); |
| 506 | target.setState(TargetDrawable.STATE_INACTIVE); |
| 507 | mTargetAnimations.add(Tweener.to(target, duration, |
| 508 | "ease", interpolator, |
| 509 | "alpha", 0.0f, |
| 510 | "scaleX", targetScale, |
| 511 | "scaleY", targetScale, |
| 512 | "delay", delay, |
| 513 | "onUpdate", mUpdateListener)); |
| 514 | } |
| 515 | |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 516 | float ringScaleTarget = expanded ? |
Jim Miller | 5892e2e | 2012-06-18 17:04:58 -0700 | [diff] [blame] | 517 | RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED; |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 518 | ringScaleTarget *= mRingScaleFactor; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 519 | mTargetAnimations.add(Tweener.to(mOuterRing, duration, |
| 520 | "ease", interpolator, |
| 521 | "alpha", 0.0f, |
| 522 | "scaleX", ringScaleTarget, |
| 523 | "scaleY", ringScaleTarget, |
| 524 | "delay", delay, |
| 525 | "onUpdate", mUpdateListener, |
| 526 | "onComplete", mTargetUpdateListener)); |
| 527 | |
| 528 | mTargetAnimations.start(); |
| 529 | } |
| 530 | |
| 531 | private void showTargets(boolean animate) { |
| 532 | mTargetAnimations.stop(); |
| 533 | mAnimatingTargets = animate; |
| 534 | final int delay = animate ? SHOW_ANIMATION_DELAY : 0; |
| 535 | final int duration = animate ? SHOW_ANIMATION_DURATION : 0; |
| 536 | final int length = mTargetDrawables.size(); |
| 537 | for (int i = 0; i < length; i++) { |
| 538 | TargetDrawable target = mTargetDrawables.get(i); |
| 539 | target.setState(TargetDrawable.STATE_INACTIVE); |
| 540 | mTargetAnimations.add(Tweener.to(target, duration, |
| 541 | "ease", Ease.Cubic.easeOut, |
| 542 | "alpha", 1.0f, |
| 543 | "scaleX", 1.0f, |
| 544 | "scaleY", 1.0f, |
| 545 | "delay", delay, |
| 546 | "onUpdate", mUpdateListener)); |
| 547 | } |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 548 | |
| 549 | float ringScale = mRingScaleFactor * RING_SCALE_EXPANDED; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 550 | mTargetAnimations.add(Tweener.to(mOuterRing, duration, |
| 551 | "ease", Ease.Cubic.easeOut, |
| 552 | "alpha", 1.0f, |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 553 | "scaleX", ringScale, |
| 554 | "scaleY", ringScale, |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 555 | "delay", delay, |
| 556 | "onUpdate", mUpdateListener, |
| 557 | "onComplete", mTargetUpdateListener)); |
| 558 | |
| 559 | mTargetAnimations.start(); |
| 560 | } |
| 561 | |
| 562 | private void vibrate() { |
Jeff Sharkey | 723a725 | 2012-10-12 14:26:31 -0700 | [diff] [blame] | 563 | final boolean hapticEnabled = Settings.System.getIntForUser( |
| 564 | mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, |
| 565 | UserHandle.USER_CURRENT) != 0; |
| 566 | if (mVibrator != null && hapticEnabled) { |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 567 | mVibrator.vibrate(mVibrationDuration); |
| 568 | } |
| 569 | } |
| 570 | |
| 571 | private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) { |
| 572 | Resources res = getContext().getResources(); |
| 573 | TypedArray array = res.obtainTypedArray(resourceId); |
| 574 | final int count = array.length(); |
| 575 | ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count); |
| 576 | for (int i = 0; i < count; i++) { |
| 577 | TypedValue value = array.peekValue(i); |
| 578 | TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0); |
| 579 | drawables.add(target); |
| 580 | } |
| 581 | array.recycle(); |
| 582 | return drawables; |
| 583 | } |
| 584 | |
| 585 | private void internalSetTargetResources(int resourceId) { |
| 586 | final ArrayList<TargetDrawable> targets = loadDrawableArray(resourceId); |
| 587 | mTargetDrawables = targets; |
| 588 | mTargetResourceId = resourceId; |
| 589 | |
| 590 | int maxWidth = mHandleDrawable.getWidth(); |
| 591 | int maxHeight = mHandleDrawable.getHeight(); |
| 592 | final int count = targets.size(); |
| 593 | for (int i = 0; i < count; i++) { |
| 594 | TargetDrawable target = targets.get(i); |
| 595 | maxWidth = Math.max(maxWidth, target.getWidth()); |
| 596 | maxHeight = Math.max(maxHeight, target.getHeight()); |
| 597 | } |
| 598 | if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) { |
| 599 | mMaxTargetWidth = maxWidth; |
| 600 | mMaxTargetHeight = maxHeight; |
| 601 | requestLayout(); // required to resize layout and call updateTargetPositions() |
| 602 | } else { |
| 603 | updateTargetPositions(mWaveCenterX, mWaveCenterY); |
| 604 | updatePointCloudPosition(mWaveCenterX, mWaveCenterY); |
| 605 | } |
| 606 | } |
| 607 | |
| 608 | /** |
| 609 | * Loads an array of drawables from the given resourceId. |
| 610 | * |
| 611 | * @param resourceId |
| 612 | */ |
| 613 | public void setTargetResources(int resourceId) { |
| 614 | if (mAnimatingTargets) { |
| 615 | // postpone this change until we return to the initial state |
| 616 | mNewTargetResources = resourceId; |
| 617 | } else { |
| 618 | internalSetTargetResources(resourceId); |
| 619 | } |
| 620 | } |
| 621 | |
| 622 | public int getTargetResourceId() { |
| 623 | return mTargetResourceId; |
| 624 | } |
| 625 | |
| 626 | /** |
| 627 | * Sets the resource id specifying the target descriptions for accessibility. |
| 628 | * |
| 629 | * @param resourceId The resource id. |
| 630 | */ |
| 631 | public void setTargetDescriptionsResourceId(int resourceId) { |
| 632 | mTargetDescriptionsResourceId = resourceId; |
| 633 | if (mTargetDescriptions != null) { |
| 634 | mTargetDescriptions.clear(); |
| 635 | } |
| 636 | } |
| 637 | |
| 638 | /** |
| 639 | * Gets the resource id specifying the target descriptions for accessibility. |
| 640 | * |
| 641 | * @return The resource id. |
| 642 | */ |
| 643 | public int getTargetDescriptionsResourceId() { |
| 644 | return mTargetDescriptionsResourceId; |
| 645 | } |
| 646 | |
| 647 | /** |
| 648 | * Sets the resource id specifying the target direction descriptions for accessibility. |
| 649 | * |
| 650 | * @param resourceId The resource id. |
| 651 | */ |
| 652 | public void setDirectionDescriptionsResourceId(int resourceId) { |
| 653 | mDirectionDescriptionsResourceId = resourceId; |
| 654 | if (mDirectionDescriptions != null) { |
| 655 | mDirectionDescriptions.clear(); |
| 656 | } |
| 657 | } |
| 658 | |
| 659 | /** |
| 660 | * Gets the resource id specifying the target direction descriptions. |
| 661 | * |
| 662 | * @return The resource id. |
| 663 | */ |
| 664 | public int getDirectionDescriptionsResourceId() { |
| 665 | return mDirectionDescriptionsResourceId; |
| 666 | } |
| 667 | |
| 668 | /** |
| 669 | * Enable or disable vibrate on touch. |
| 670 | * |
| 671 | * @param enabled |
| 672 | */ |
| 673 | public void setVibrateEnabled(boolean enabled) { |
| 674 | if (enabled && mVibrator == null) { |
| 675 | mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); |
| 676 | } else { |
| 677 | mVibrator = null; |
| 678 | } |
| 679 | } |
| 680 | |
| 681 | /** |
| 682 | * Starts wave animation. |
| 683 | * |
| 684 | */ |
| 685 | public void ping() { |
| 686 | if (mFeedbackCount > 0) { |
Jim Miller | 5892e2e | 2012-06-18 17:04:58 -0700 | [diff] [blame] | 687 | boolean doWaveAnimation = true; |
| 688 | final AnimationBundle waveAnimations = mWaveAnimations; |
| 689 | |
| 690 | // Don't do a wave if there's already one in progress |
| 691 | if (waveAnimations.size() > 0 && waveAnimations.get(0).animator.isRunning()) { |
| 692 | long t = waveAnimations.get(0).animator.getCurrentPlayTime(); |
| 693 | if (t < WAVE_ANIMATION_DURATION/2) { |
| 694 | doWaveAnimation = false; |
| 695 | } |
| 696 | } |
| 697 | |
| 698 | if (doWaveAnimation) { |
| 699 | startWaveAnimation(); |
| 700 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 701 | } |
| 702 | } |
| 703 | |
| 704 | private void stopAndHideWaveAnimation() { |
| 705 | mWaveAnimations.cancel(); |
| 706 | mPointCloud.waveManager.setAlpha(0.0f); |
| 707 | } |
| 708 | |
| 709 | private void startWaveAnimation() { |
| 710 | mWaveAnimations.cancel(); |
| 711 | mPointCloud.waveManager.setAlpha(1.0f); |
| 712 | mPointCloud.waveManager.setRadius(mHandleDrawable.getWidth()/2.0f); |
| 713 | mWaveAnimations.add(Tweener.to(mPointCloud.waveManager, WAVE_ANIMATION_DURATION, |
Jim Miller | 5892e2e | 2012-06-18 17:04:58 -0700 | [diff] [blame] | 714 | "ease", Ease.Quad.easeOut, |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 715 | "delay", 0, |
| 716 | "radius", 2.0f * mOuterRadius, |
| 717 | "onUpdate", mUpdateListener, |
| 718 | "onComplete", |
| 719 | new AnimatorListenerAdapter() { |
| 720 | public void onAnimationEnd(Animator animator) { |
| 721 | mPointCloud.waveManager.setRadius(0.0f); |
| 722 | mPointCloud.waveManager.setAlpha(0.0f); |
| 723 | } |
| 724 | })); |
| 725 | mWaveAnimations.start(); |
| 726 | } |
| 727 | |
| 728 | /** |
| 729 | * Resets the widget to default state and cancels all animation. If animate is 'true', will |
| 730 | * animate objects into place. Otherwise, objects will snap back to place. |
| 731 | * |
| 732 | * @param animate |
| 733 | */ |
| 734 | public void reset(boolean animate) { |
| 735 | mGlowAnimations.stop(); |
| 736 | mTargetAnimations.stop(); |
| 737 | startBackgroundAnimation(0, 0.0f); |
| 738 | stopAndHideWaveAnimation(); |
| 739 | hideTargets(animate, false); |
Jim Miller | a592d22 | 2012-06-29 17:41:25 -0700 | [diff] [blame] | 740 | hideGlow(0, 0, 0.0f, null); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 741 | Tweener.reset(); |
| 742 | } |
| 743 | |
| 744 | private void startBackgroundAnimation(int duration, float alpha) { |
| 745 | final Drawable background = getBackground(); |
| 746 | if (mAlwaysTrackFinger && background != null) { |
| 747 | if (mBackgroundAnimator != null) { |
| 748 | mBackgroundAnimator.animator.cancel(); |
| 749 | } |
| 750 | mBackgroundAnimator = Tweener.to(background, duration, |
| 751 | "ease", Ease.Cubic.easeIn, |
| 752 | "alpha", (int)(255.0f * alpha), |
| 753 | "delay", SHOW_ANIMATION_DELAY); |
| 754 | mBackgroundAnimator.animator.start(); |
| 755 | } |
| 756 | } |
| 757 | |
| 758 | @Override |
| 759 | public boolean onTouchEvent(MotionEvent event) { |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 760 | final int action = event.getActionMasked(); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 761 | boolean handled = false; |
| 762 | switch (action) { |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 763 | case MotionEvent.ACTION_POINTER_DOWN: |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 764 | case MotionEvent.ACTION_DOWN: |
| 765 | if (DEBUG) Log.v(TAG, "*** DOWN ***"); |
| 766 | handleDown(event); |
| 767 | handleMove(event); |
| 768 | handled = true; |
| 769 | break; |
| 770 | |
| 771 | case MotionEvent.ACTION_MOVE: |
| 772 | if (DEBUG) Log.v(TAG, "*** MOVE ***"); |
| 773 | handleMove(event); |
| 774 | handled = true; |
| 775 | break; |
| 776 | |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 777 | case MotionEvent.ACTION_POINTER_UP: |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 778 | case MotionEvent.ACTION_UP: |
| 779 | if (DEBUG) Log.v(TAG, "*** UP ***"); |
| 780 | handleMove(event); |
| 781 | handleUp(event); |
| 782 | handled = true; |
| 783 | break; |
| 784 | |
| 785 | case MotionEvent.ACTION_CANCEL: |
| 786 | if (DEBUG) Log.v(TAG, "*** CANCEL ***"); |
| 787 | handleMove(event); |
| 788 | handleCancel(event); |
| 789 | handled = true; |
| 790 | break; |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 791 | |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 792 | } |
| 793 | invalidate(); |
| 794 | return handled ? true : super.onTouchEvent(event); |
| 795 | } |
| 796 | |
| 797 | private void updateGlowPosition(float x, float y) { |
Adam Cohen | f988bdf | 2012-11-07 14:28:23 -0800 | [diff] [blame] | 798 | float dx = x - mOuterRing.getX(); |
| 799 | float dy = y - mOuterRing.getY(); |
| 800 | dx *= 1f / mRingScaleFactor; |
| 801 | dy *= 1f / mRingScaleFactor; |
| 802 | mPointCloud.glowManager.setX(mOuterRing.getX() + dx); |
| 803 | mPointCloud.glowManager.setY(mOuterRing.getY() + dy); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 804 | } |
| 805 | |
| 806 | private void handleDown(MotionEvent event) { |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 807 | int actionIndex = event.getActionIndex(); |
| 808 | float eventX = event.getX(actionIndex); |
| 809 | float eventY = event.getY(actionIndex); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 810 | switchToState(STATE_START, eventX, eventY); |
| 811 | if (!trySwitchToFirstTouchState(eventX, eventY)) { |
| 812 | mDragging = false; |
| 813 | } else { |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 814 | mPointerId = event.getPointerId(actionIndex); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 815 | updateGlowPosition(eventX, eventY); |
| 816 | } |
| 817 | } |
| 818 | |
| 819 | private void handleUp(MotionEvent event) { |
| 820 | if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE"); |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 821 | int actionIndex = event.getActionIndex(); |
| 822 | if (event.getPointerId(actionIndex) == mPointerId) { |
| 823 | switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex)); |
| 824 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 825 | } |
| 826 | |
| 827 | private void handleCancel(MotionEvent event) { |
| 828 | if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL"); |
| 829 | |
Jim Miller | 245b453 | 2012-10-07 20:16:54 -0700 | [diff] [blame] | 830 | // Drop the active target if canceled. |
| 831 | mActiveTarget = -1; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 832 | |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 833 | int actionIndex = event.findPointerIndex(mPointerId); |
| 834 | actionIndex = actionIndex == -1 ? 0 : actionIndex; |
| 835 | switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex)); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 836 | } |
| 837 | |
| 838 | private void handleMove(MotionEvent event) { |
| 839 | int activeTarget = -1; |
| 840 | final int historySize = event.getHistorySize(); |
| 841 | ArrayList<TargetDrawable> targets = mTargetDrawables; |
| 842 | int ntargets = targets.size(); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 843 | float x = 0.0f; |
| 844 | float y = 0.0f; |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 845 | float activeAngle = 0.0f; |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 846 | int actionIndex = event.findPointerIndex(mPointerId); |
| 847 | |
| 848 | if (actionIndex == -1) { |
| 849 | return; // no data for this pointer |
| 850 | } |
| 851 | |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 852 | for (int k = 0; k < historySize + 1; k++) { |
Jim Miller | b499884 | 2012-09-23 17:18:17 -0700 | [diff] [blame] | 853 | float eventX = k < historySize ? event.getHistoricalX(actionIndex, k) |
| 854 | : event.getX(actionIndex); |
| 855 | float eventY = k < historySize ? event.getHistoricalY(actionIndex, k) |
| 856 | : event.getY(actionIndex); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 857 | // tx and ty are relative to wave center |
| 858 | float tx = eventX - mWaveCenterX; |
| 859 | float ty = eventY - mWaveCenterY; |
| 860 | float touchRadius = (float) Math.sqrt(dist2(tx, ty)); |
| 861 | final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f; |
| 862 | float limitX = tx * scale; |
| 863 | float limitY = ty * scale; |
Michael Jurka | 53f109bf | 2012-06-13 17:38:14 -0700 | [diff] [blame] | 864 | double angleRad = Math.atan2(-ty, tx); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 865 | |
| 866 | if (!mDragging) { |
| 867 | trySwitchToFirstTouchState(eventX, eventY); |
| 868 | } |
| 869 | |
| 870 | if (mDragging) { |
Michael Jurka | 53f109bf | 2012-06-13 17:38:14 -0700 | [diff] [blame] | 871 | // For multiple targets, snap to the one that matches |
Adam Cohen | f988bdf | 2012-11-07 14:28:23 -0800 | [diff] [blame] | 872 | final float snapRadius = mRingScaleFactor * mOuterRadius - mSnapMargin; |
Michael Jurka | 53f109bf | 2012-06-13 17:38:14 -0700 | [diff] [blame] | 873 | final float snapDistance2 = snapRadius * snapRadius; |
| 874 | // Find first target in range |
| 875 | for (int i = 0; i < ntargets; i++) { |
| 876 | TargetDrawable target = targets.get(i); |
| 877 | |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 878 | double targetMinRad = mFirstItemOffset + (i - 0.5) * 2 * Math.PI / ntargets; |
| 879 | double targetMaxRad = mFirstItemOffset + (i + 0.5) * 2 * Math.PI / ntargets; |
Michael Jurka | 53f109bf | 2012-06-13 17:38:14 -0700 | [diff] [blame] | 880 | if (target.isEnabled()) { |
| 881 | boolean angleMatches = |
| 882 | (angleRad > targetMinRad && angleRad <= targetMaxRad) || |
| 883 | (angleRad + 2 * Math.PI > targetMinRad && |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 884 | angleRad + 2 * Math.PI <= targetMaxRad) || |
| 885 | (angleRad - 2 * Math.PI > targetMinRad && |
| 886 | angleRad - 2 * Math.PI <= targetMaxRad); |
Michael Jurka | 53f109bf | 2012-06-13 17:38:14 -0700 | [diff] [blame] | 887 | if (angleMatches && (dist2(tx, ty) > snapDistance2)) { |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 888 | activeTarget = i; |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 889 | activeAngle = (float) -angleRad; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 890 | } |
| 891 | } |
| 892 | } |
| 893 | } |
| 894 | x = limitX; |
| 895 | y = limitY; |
| 896 | } |
| 897 | |
| 898 | if (!mDragging) { |
| 899 | return; |
| 900 | } |
| 901 | |
| 902 | if (activeTarget != -1) { |
| 903 | switchToState(STATE_SNAP, x,y); |
Michael Jurka | 53f109bf | 2012-06-13 17:38:14 -0700 | [diff] [blame] | 904 | updateGlowPosition(x, y); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 905 | } else { |
| 906 | switchToState(STATE_TRACKING, x, y); |
| 907 | updateGlowPosition(x, y); |
| 908 | } |
| 909 | |
| 910 | if (mActiveTarget != activeTarget) { |
| 911 | // Defocus the old target |
| 912 | if (mActiveTarget != -1) { |
| 913 | TargetDrawable target = targets.get(mActiveTarget); |
| 914 | if (target.hasState(TargetDrawable.STATE_FOCUSED)) { |
| 915 | target.setState(TargetDrawable.STATE_INACTIVE); |
| 916 | } |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 917 | if (mMagneticTargets) { |
| 918 | updateTargetPosition(mActiveTarget, mWaveCenterX, mWaveCenterY); |
| 919 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 920 | } |
| 921 | // Focus the new target |
| 922 | if (activeTarget != -1) { |
| 923 | TargetDrawable target = targets.get(activeTarget); |
| 924 | if (target.hasState(TargetDrawable.STATE_FOCUSED)) { |
| 925 | target.setState(TargetDrawable.STATE_FOCUSED); |
| 926 | } |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 927 | if (mMagneticTargets) { |
| 928 | updateTargetPosition(activeTarget, mWaveCenterX, mWaveCenterY, activeAngle); |
| 929 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 930 | if (AccessibilityManager.getInstance(mContext).isEnabled()) { |
| 931 | String targetContentDescription = getTargetDescription(activeTarget); |
alanv | 78bfb98 | 2012-06-20 12:10:48 -0700 | [diff] [blame] | 932 | announceForAccessibility(targetContentDescription); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 933 | } |
| 934 | } |
| 935 | } |
| 936 | mActiveTarget = activeTarget; |
| 937 | } |
| 938 | |
| 939 | @Override |
| 940 | public boolean onHoverEvent(MotionEvent event) { |
| 941 | if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { |
| 942 | final int action = event.getAction(); |
| 943 | switch (action) { |
| 944 | case MotionEvent.ACTION_HOVER_ENTER: |
| 945 | event.setAction(MotionEvent.ACTION_DOWN); |
| 946 | break; |
| 947 | case MotionEvent.ACTION_HOVER_MOVE: |
| 948 | event.setAction(MotionEvent.ACTION_MOVE); |
| 949 | break; |
| 950 | case MotionEvent.ACTION_HOVER_EXIT: |
| 951 | event.setAction(MotionEvent.ACTION_UP); |
| 952 | break; |
| 953 | } |
| 954 | onTouchEvent(event); |
| 955 | event.setAction(action); |
| 956 | } |
Svetoslav Ganov | 7ce0c13 | 2012-11-07 15:54:56 -0800 | [diff] [blame] | 957 | super.onHoverEvent(event); |
| 958 | return true; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 959 | } |
| 960 | |
| 961 | /** |
| 962 | * Sets the current grabbed state, and dispatches a grabbed state change |
| 963 | * event to our listener. |
| 964 | */ |
| 965 | private void setGrabbedState(int newState) { |
| 966 | if (newState != mGrabbedState) { |
| 967 | if (newState != OnTriggerListener.NO_HANDLE) { |
| 968 | vibrate(); |
| 969 | } |
| 970 | mGrabbedState = newState; |
| 971 | if (mOnTriggerListener != null) { |
| 972 | if (newState == OnTriggerListener.NO_HANDLE) { |
| 973 | mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE); |
| 974 | } else { |
| 975 | mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE); |
| 976 | } |
| 977 | mOnTriggerListener.onGrabbedStateChange(this, newState); |
| 978 | } |
| 979 | } |
| 980 | } |
| 981 | |
| 982 | private boolean trySwitchToFirstTouchState(float x, float y) { |
| 983 | final float tx = x - mWaveCenterX; |
| 984 | final float ty = y - mWaveCenterY; |
| 985 | if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledGlowRadiusSquared()) { |
| 986 | if (DEBUG) Log.v(TAG, "** Handle HIT"); |
| 987 | switchToState(STATE_FIRST_TOUCH, x, y); |
| 988 | updateGlowPosition(tx, ty); |
| 989 | mDragging = true; |
| 990 | return true; |
| 991 | } |
| 992 | return false; |
| 993 | } |
| 994 | |
| 995 | private void assignDefaultsIfNeeded() { |
| 996 | if (mOuterRadius == 0.0f) { |
| 997 | mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f; |
| 998 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 999 | if (mSnapMargin == 0.0f) { |
| 1000 | mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, |
| 1001 | SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics()); |
| 1002 | } |
| 1003 | if (mInnerRadius == 0.0f) { |
| 1004 | mInnerRadius = mHandleDrawable.getWidth() / 10.0f; |
| 1005 | } |
| 1006 | } |
| 1007 | |
| 1008 | private void computeInsets(int dx, int dy) { |
Fabrice Di Meglio | e56ffdc | 2012-09-23 14:51:16 -0700 | [diff] [blame] | 1009 | final int layoutDirection = getLayoutDirection(); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1010 | final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); |
| 1011 | |
| 1012 | switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { |
| 1013 | case Gravity.LEFT: |
| 1014 | mHorizontalInset = 0; |
| 1015 | break; |
| 1016 | case Gravity.RIGHT: |
| 1017 | mHorizontalInset = dx; |
| 1018 | break; |
| 1019 | case Gravity.CENTER_HORIZONTAL: |
| 1020 | default: |
| 1021 | mHorizontalInset = dx / 2; |
| 1022 | break; |
| 1023 | } |
| 1024 | switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) { |
| 1025 | case Gravity.TOP: |
| 1026 | mVerticalInset = 0; |
| 1027 | break; |
| 1028 | case Gravity.BOTTOM: |
| 1029 | mVerticalInset = dy; |
| 1030 | break; |
| 1031 | case Gravity.CENTER_VERTICAL: |
| 1032 | default: |
| 1033 | mVerticalInset = dy / 2; |
| 1034 | break; |
| 1035 | } |
| 1036 | } |
| 1037 | |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 1038 | /** |
| 1039 | * Given the desired width and height of the ring and the allocated width and height, compute |
| 1040 | * how much we need to scale the ring. |
| 1041 | */ |
| 1042 | private float computeScaleFactor(int desiredWidth, int desiredHeight, |
| 1043 | int actualWidth, int actualHeight) { |
Adam Cohen | f988bdf | 2012-11-07 14:28:23 -0800 | [diff] [blame] | 1044 | |
| 1045 | // Return unity if scaling is not allowed. |
| 1046 | if (!mAllowScaling) return 1f; |
| 1047 | |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 1048 | final int layoutDirection = getLayoutDirection(); |
| 1049 | final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); |
| 1050 | |
| 1051 | float scaleX = 1f; |
| 1052 | float scaleY = 1f; |
| 1053 | |
| 1054 | // We use the gravity as a cue for whether we want to scale on a particular axis. |
| 1055 | // We only scale to fit horizontally if we're not pinned to the left or right. Likewise, |
| 1056 | // we only scale to fit vertically if we're not pinned to the top or bottom. In these |
| 1057 | // cases, we want the ring to hang off the side or top/bottom, respectively. |
| 1058 | switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { |
| 1059 | case Gravity.LEFT: |
| 1060 | case Gravity.RIGHT: |
| 1061 | break; |
| 1062 | case Gravity.CENTER_HORIZONTAL: |
| 1063 | default: |
| 1064 | if (desiredWidth > actualWidth) { |
| 1065 | scaleX = (1f * actualWidth - mMaxTargetWidth) / |
| 1066 | (desiredWidth - mMaxTargetWidth); |
| 1067 | } |
| 1068 | break; |
| 1069 | } |
| 1070 | switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) { |
| 1071 | case Gravity.TOP: |
| 1072 | case Gravity.BOTTOM: |
| 1073 | break; |
| 1074 | case Gravity.CENTER_VERTICAL: |
| 1075 | default: |
| 1076 | if (desiredHeight > actualHeight) { |
| 1077 | scaleY = (1f * actualHeight - mMaxTargetHeight) / |
| 1078 | (desiredHeight - mMaxTargetHeight); |
| 1079 | } |
| 1080 | break; |
| 1081 | } |
| 1082 | return Math.min(scaleX, scaleY); |
| 1083 | } |
| 1084 | |
| 1085 | @Override |
| 1086 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 1087 | final int minimumWidth = getSuggestedMinimumWidth(); |
| 1088 | final int minimumHeight = getSuggestedMinimumHeight(); |
| 1089 | int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth); |
| 1090 | int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight); |
| 1091 | |
| 1092 | mRingScaleFactor = computeScaleFactor(minimumWidth, minimumHeight, |
| 1093 | computedWidth, computedHeight); |
| 1094 | |
| 1095 | int scaledWidth = getScaledSuggestedMinimumWidth(); |
| 1096 | int scaledHeight = getScaledSuggestedMinimumHeight(); |
| 1097 | |
| 1098 | computeInsets(computedWidth - scaledWidth, computedHeight - scaledHeight); |
| 1099 | setMeasuredDimension(computedWidth, computedHeight); |
| 1100 | } |
| 1101 | |
| 1102 | private float getRingWidth() { |
| 1103 | return mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius); |
| 1104 | } |
| 1105 | |
| 1106 | private float getRingHeight() { |
| 1107 | return mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius); |
| 1108 | } |
| 1109 | |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1110 | @Override |
| 1111 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
| 1112 | super.onLayout(changed, left, top, right, bottom); |
| 1113 | final int width = right - left; |
| 1114 | final int height = bottom - top; |
| 1115 | |
| 1116 | // Target placement width/height. This puts the targets on the greater of the ring |
| 1117 | // width or the specified outer radius. |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 1118 | final float placementWidth = getRingWidth(); |
| 1119 | final float placementHeight = getRingHeight(); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1120 | float newWaveCenterX = mHorizontalInset |
| 1121 | + Math.max(width, mMaxTargetWidth + placementWidth) / 2; |
| 1122 | float newWaveCenterY = mVerticalInset |
| 1123 | + Math.max(height, + mMaxTargetHeight + placementHeight) / 2; |
| 1124 | |
| 1125 | if (mInitialLayout) { |
| 1126 | stopAndHideWaveAnimation(); |
| 1127 | hideTargets(false, false); |
| 1128 | mInitialLayout = false; |
| 1129 | } |
| 1130 | |
| 1131 | mOuterRing.setPositionX(newWaveCenterX); |
| 1132 | mOuterRing.setPositionY(newWaveCenterY); |
| 1133 | |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 1134 | mPointCloud.setScale(mRingScaleFactor); |
| 1135 | |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1136 | mHandleDrawable.setPositionX(newWaveCenterX); |
| 1137 | mHandleDrawable.setPositionY(newWaveCenterY); |
| 1138 | |
| 1139 | updateTargetPositions(newWaveCenterX, newWaveCenterY); |
| 1140 | updatePointCloudPosition(newWaveCenterX, newWaveCenterY); |
| 1141 | updateGlowPosition(newWaveCenterX, newWaveCenterY); |
| 1142 | |
| 1143 | mWaveCenterX = newWaveCenterX; |
| 1144 | mWaveCenterY = newWaveCenterY; |
| 1145 | |
| 1146 | if (DEBUG) dump(); |
| 1147 | } |
| 1148 | |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 1149 | private void updateTargetPosition(int i, float centerX, float centerY) { |
| 1150 | final float angle = getAngle(getSliceAngle(), i); |
| 1151 | updateTargetPosition(i, centerX, centerY, angle); |
| 1152 | } |
| 1153 | |
| 1154 | private void updateTargetPosition(int i, float centerX, float centerY, float angle) { |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 1155 | final float placementRadiusX = getRingWidth() / 2; |
| 1156 | final float placementRadiusY = getRingHeight() / 2; |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 1157 | if (i >= 0) { |
| 1158 | ArrayList<TargetDrawable> targets = mTargetDrawables; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1159 | final TargetDrawable targetIcon = targets.get(i); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1160 | targetIcon.setPositionX(centerX); |
| 1161 | targetIcon.setPositionY(centerY); |
Adam Cohen | e41dd0f | 2012-11-06 23:06:22 -0800 | [diff] [blame] | 1162 | targetIcon.setX(placementRadiusX * (float) Math.cos(angle)); |
| 1163 | targetIcon.setY(placementRadiusY * (float) Math.sin(angle)); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1164 | } |
| 1165 | } |
| 1166 | |
Chris Wren | f0ee5b8 | 2012-10-26 17:56:11 -0400 | [diff] [blame] | 1167 | private void updateTargetPositions(float centerX, float centerY) { |
| 1168 | updateTargetPositions(centerX, centerY, false); |
| 1169 | } |
| 1170 | |
| 1171 | private void updateTargetPositions(float centerX, float centerY, boolean skipActive) { |
| 1172 | final int size = mTargetDrawables.size(); |
| 1173 | final float alpha = getSliceAngle(); |
| 1174 | // Reposition the target drawables if the view changed. |
| 1175 | for (int i = 0; i < size; i++) { |
| 1176 | if (!skipActive || i != mActiveTarget) { |
| 1177 | updateTargetPosition(i, centerX, centerY, getAngle(alpha, i)); |
| 1178 | } |
| 1179 | } |
| 1180 | } |
| 1181 | |
| 1182 | private float getAngle(float alpha, int i) { |
| 1183 | return mFirstItemOffset + alpha * i; |
| 1184 | } |
| 1185 | |
| 1186 | private float getSliceAngle() { |
| 1187 | return (float) (-2.0f * Math.PI / mTargetDrawables.size()); |
| 1188 | } |
| 1189 | |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1190 | private void updatePointCloudPosition(float centerX, float centerY) { |
| 1191 | mPointCloud.setCenter(centerX, centerY); |
| 1192 | } |
| 1193 | |
| 1194 | @Override |
| 1195 | protected void onDraw(Canvas canvas) { |
| 1196 | mPointCloud.draw(canvas); |
| 1197 | mOuterRing.draw(canvas); |
| 1198 | final int ntargets = mTargetDrawables.size(); |
| 1199 | for (int i = 0; i < ntargets; i++) { |
| 1200 | TargetDrawable target = mTargetDrawables.get(i); |
| 1201 | if (target != null) { |
| 1202 | target.draw(canvas); |
| 1203 | } |
| 1204 | } |
| 1205 | mHandleDrawable.draw(canvas); |
| 1206 | } |
| 1207 | |
| 1208 | public void setOnTriggerListener(OnTriggerListener listener) { |
| 1209 | mOnTriggerListener = listener; |
| 1210 | } |
| 1211 | |
| 1212 | private float square(float d) { |
| 1213 | return d * d; |
| 1214 | } |
| 1215 | |
| 1216 | private float dist2(float dx, float dy) { |
| 1217 | return dx*dx + dy*dy; |
| 1218 | } |
| 1219 | |
| 1220 | private float getScaledGlowRadiusSquared() { |
| 1221 | final float scaledTapRadius; |
| 1222 | if (AccessibilityManager.getInstance(mContext).isEnabled()) { |
| 1223 | scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mGlowRadius; |
| 1224 | } else { |
| 1225 | scaledTapRadius = mGlowRadius; |
| 1226 | } |
| 1227 | return square(scaledTapRadius); |
| 1228 | } |
| 1229 | |
| 1230 | private void announceTargets() { |
| 1231 | StringBuilder utterance = new StringBuilder(); |
| 1232 | final int targetCount = mTargetDrawables.size(); |
| 1233 | for (int i = 0; i < targetCount; i++) { |
| 1234 | String targetDescription = getTargetDescription(i); |
| 1235 | String directionDescription = getDirectionDescription(i); |
| 1236 | if (!TextUtils.isEmpty(targetDescription) |
| 1237 | && !TextUtils.isEmpty(directionDescription)) { |
| 1238 | String text = String.format(directionDescription, targetDescription); |
| 1239 | utterance.append(text); |
| 1240 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1241 | } |
alanv | 78bfb98 | 2012-06-20 12:10:48 -0700 | [diff] [blame] | 1242 | if (utterance.length() > 0) { |
| 1243 | announceForAccessibility(utterance.toString()); |
| 1244 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1245 | } |
| 1246 | |
| 1247 | private String getTargetDescription(int index) { |
| 1248 | if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) { |
| 1249 | mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId); |
| 1250 | if (mTargetDrawables.size() != mTargetDescriptions.size()) { |
| 1251 | Log.w(TAG, "The number of target drawables must be" |
| 1252 | + " equal to the number of target descriptions."); |
| 1253 | return null; |
| 1254 | } |
| 1255 | } |
| 1256 | return mTargetDescriptions.get(index); |
| 1257 | } |
| 1258 | |
| 1259 | private String getDirectionDescription(int index) { |
| 1260 | if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) { |
| 1261 | mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId); |
| 1262 | if (mTargetDrawables.size() != mDirectionDescriptions.size()) { |
| 1263 | Log.w(TAG, "The number of target drawables must be" |
| 1264 | + " equal to the number of direction descriptions."); |
| 1265 | return null; |
| 1266 | } |
| 1267 | } |
| 1268 | return mDirectionDescriptions.get(index); |
| 1269 | } |
| 1270 | |
| 1271 | private ArrayList<String> loadDescriptions(int resourceId) { |
| 1272 | TypedArray array = getContext().getResources().obtainTypedArray(resourceId); |
| 1273 | final int count = array.length(); |
| 1274 | ArrayList<String> targetContentDescriptions = new ArrayList<String>(count); |
| 1275 | for (int i = 0; i < count; i++) { |
| 1276 | String contentDescription = array.getString(i); |
| 1277 | targetContentDescriptions.add(contentDescription); |
| 1278 | } |
| 1279 | array.recycle(); |
| 1280 | return targetContentDescriptions; |
| 1281 | } |
| 1282 | |
| 1283 | public int getResourceIdForTarget(int index) { |
| 1284 | final TargetDrawable drawable = mTargetDrawables.get(index); |
| 1285 | return drawable == null ? 0 : drawable.getResourceId(); |
| 1286 | } |
| 1287 | |
| 1288 | public void setEnableTarget(int resourceId, boolean enabled) { |
| 1289 | for (int i = 0; i < mTargetDrawables.size(); i++) { |
| 1290 | final TargetDrawable target = mTargetDrawables.get(i); |
| 1291 | if (target.getResourceId() == resourceId) { |
| 1292 | target.setEnabled(enabled); |
| 1293 | break; // should never be more than one match |
| 1294 | } |
| 1295 | } |
| 1296 | } |
| 1297 | |
| 1298 | /** |
| 1299 | * Gets the position of a target in the array that matches the given resource. |
| 1300 | * @param resourceId |
| 1301 | * @return the index or -1 if not found |
| 1302 | */ |
| 1303 | public int getTargetPosition(int resourceId) { |
| 1304 | for (int i = 0; i < mTargetDrawables.size(); i++) { |
| 1305 | final TargetDrawable target = mTargetDrawables.get(i); |
| 1306 | if (target.getResourceId() == resourceId) { |
| 1307 | return i; // should never be more than one match |
| 1308 | } |
| 1309 | } |
| 1310 | return -1; |
| 1311 | } |
| 1312 | |
| 1313 | private boolean replaceTargetDrawables(Resources res, int existingResourceId, |
| 1314 | int newResourceId) { |
| 1315 | if (existingResourceId == 0 || newResourceId == 0) { |
| 1316 | return false; |
| 1317 | } |
| 1318 | |
| 1319 | boolean result = false; |
| 1320 | final ArrayList<TargetDrawable> drawables = mTargetDrawables; |
| 1321 | final int size = drawables.size(); |
| 1322 | for (int i = 0; i < size; i++) { |
| 1323 | final TargetDrawable target = drawables.get(i); |
| 1324 | if (target != null && target.getResourceId() == existingResourceId) { |
| 1325 | target.setDrawable(res, newResourceId); |
| 1326 | result = true; |
| 1327 | } |
| 1328 | } |
| 1329 | |
| 1330 | if (result) { |
| 1331 | requestLayout(); // in case any given drawable's size changes |
| 1332 | } |
| 1333 | |
| 1334 | return result; |
| 1335 | } |
| 1336 | |
| 1337 | /** |
| 1338 | * Searches the given package for a resource to use to replace the Drawable on the |
| 1339 | * target with the given resource id |
| 1340 | * @param component of the .apk that contains the resource |
| 1341 | * @param name of the metadata in the .apk |
| 1342 | * @param existingResId the resource id of the target to search for |
| 1343 | * @return true if found in the given package and replaced at least one target Drawables |
| 1344 | */ |
| 1345 | public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name, |
| 1346 | int existingResId) { |
| 1347 | if (existingResId == 0) return false; |
| 1348 | |
Jim Miller | 45308b1 | 2012-06-18 19:23:39 -0700 | [diff] [blame] | 1349 | boolean replaced = false; |
| 1350 | if (component != null) { |
| 1351 | try { |
| 1352 | PackageManager packageManager = mContext.getPackageManager(); |
| 1353 | // Look for the search icon specified in the activity meta-data |
| 1354 | Bundle metaData = packageManager.getActivityInfo( |
| 1355 | component, PackageManager.GET_META_DATA).metaData; |
| 1356 | if (metaData != null) { |
| 1357 | int iconResId = metaData.getInt(name); |
| 1358 | if (iconResId != 0) { |
| 1359 | Resources res = packageManager.getResourcesForActivity(component); |
| 1360 | replaced = replaceTargetDrawables(res, existingResId, iconResId); |
| 1361 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1362 | } |
Jim Miller | 45308b1 | 2012-06-18 19:23:39 -0700 | [diff] [blame] | 1363 | } catch (NameNotFoundException e) { |
| 1364 | Log.w(TAG, "Failed to swap drawable; " |
| 1365 | + component.flattenToShortString() + " not found", e); |
| 1366 | } catch (Resources.NotFoundException nfe) { |
| 1367 | Log.w(TAG, "Failed to swap drawable from " |
| 1368 | + component.flattenToShortString(), nfe); |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1369 | } |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1370 | } |
Jim Miller | 45308b1 | 2012-06-18 19:23:39 -0700 | [diff] [blame] | 1371 | if (!replaced) { |
| 1372 | // Restore the original drawable |
| 1373 | replaceTargetDrawables(mContext.getResources(), existingResId, existingResId); |
| 1374 | } |
| 1375 | return replaced; |
Jim Miller | 955a016 | 2012-06-11 21:06:13 -0700 | [diff] [blame] | 1376 | } |
| 1377 | } |