blob: 2e97fdafebade8e5fabe1472032e8126fa3943b3 [file] [log] [blame]
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001/*
2 * Copyright (C) 2008 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
17package android.widget;
18
The Android Open Source Project3001a032009-02-19 10:57:31 -080019import android.app.AlertDialog;
20import android.app.Dialog;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080021import android.content.BroadcastReceiver;
The Android Open Source Projectda996f32009-02-13 12:57:50 -080022import android.content.ContentResolver;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080023import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
The Android Open Source Projectda996f32009-02-13 12:57:50 -080026import android.content.SharedPreferences;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080027import android.graphics.PixelFormat;
28import android.graphics.Rect;
29import android.os.Handler;
30import android.os.Message;
31import android.os.SystemClock;
32import android.provider.Settings;
33import android.util.Log;
34import android.view.Gravity;
35import android.view.KeyEvent;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewConfiguration;
The Android Open Source Project3001a032009-02-19 10:57:31 -080039import android.view.Window;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080040import android.view.WindowManager;
41import android.view.WindowManager.LayoutParams;
42import android.view.animation.Animation;
43import android.view.animation.AnimationUtils;
44import android.view.animation.DecelerateInterpolator;
45
46// TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration)
47
48/**
49 * TODO: Docs
The Android Open Source Project076357b2009-03-03 14:04:24 -080050 *
The Android Open Source Project3001a032009-02-19 10:57:31 -080051 * If you are using this with a custom View, please call
52 * {@link #setVisible(boolean) setVisible(false)} from the
53 * {@link View#onDetachedFromWindow}.
The Android Open Source Project076357b2009-03-03 14:04:24 -080054 *
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080055 * @hide
56 */
57public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
58 View.OnTouchListener, View.OnKeyListener {
The Android Open Source Project076357b2009-03-03 14:04:24 -080059
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -080060 private static final int ZOOM_RING_RADIUS_INSET = 24;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080061
62 private static final int ZOOM_RING_RECENTERING_DURATION = 500;
63
64 private static final String TAG = "ZoomRing";
65
The Android Open Source Project076357b2009-03-03 14:04:24 -080066 public static final boolean USE_OLD_ZOOM = false;
The Android Open Source Project3dec7d52009-03-02 22:54:33 -080067 public static boolean useOldZoom(Context context) {
The Android Open Source Project076357b2009-03-03 14:04:24 -080068 return Settings.System.getInt(context.getContentResolver(), "zoom", 1) == 0;
The Android Open Source Project3dec7d52009-03-02 22:54:33 -080069 }
The Android Open Source Project076357b2009-03-03 14:04:24 -080070
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080071 private static final int ZOOM_CONTROLS_TIMEOUT =
72 (int) ViewConfiguration.getZoomControlsTimeout();
The Android Open Source Project076357b2009-03-03 14:04:24 -080073
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080074 // TODO: move these to ViewConfiguration or re-use existing ones
75 // TODO: scale px values based on latest from ViewConfiguration
76 private static final int SECOND_TAP_TIMEOUT = 500;
77 private static final int ZOOM_RING_DISMISS_DELAY = SECOND_TAP_TIMEOUT / 2;
The Android Open Source Project3001a032009-02-19 10:57:31 -080078 // TODO: view config? at least scaled
79 private static final int MAX_PAN_GAP = 20;
80 private static final int MAX_INITIATE_PAN_GAP = 10;
81 // TODO view config
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -080082 private static final int INITIATE_PAN_DELAY = 300;
The Android Open Source Project076357b2009-03-03 14:04:24 -080083
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080084 private static final String SETTING_NAME_SHOWN_TOAST = "shown_zoom_ring_toast";
The Android Open Source Project076357b2009-03-03 14:04:24 -080085
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080086 private Context mContext;
87 private WindowManager mWindowManager;
The Android Open Source Project076357b2009-03-03 14:04:24 -080088
The Android Open Source Projectd24b8182009-02-10 15:44:00 -080089 /**
90 * The view that is being zoomed by this zoom ring.
91 */
92 private View mOwnerView;
93
94 /**
95 * The bounds of the owner view in global coordinates. This is recalculated
96 * each time the zoom ring is shown.
97 */
98 private Rect mOwnerViewBounds = new Rect();
99
100 /**
101 * The container that is added as a window.
102 */
103 private FrameLayout mContainer;
104 private LayoutParams mContainerLayoutParams;
105
The Android Open Source Project3001a032009-02-19 10:57:31 -0800106 /**
107 * The view (or null) that should receive touch events. This will get set if
108 * the touch down hits the container. It will be reset on the touch up.
109 */
110 private View mTouchTargetView;
111 /**
112 * The {@link #mTouchTargetView}'s location in window, set on touch down.
113 */
The Android Open Source Project076357b2009-03-03 14:04:24 -0800114 private int[] mTouchTargetLocationInWindow = new int[2];
The Android Open Source Project3001a032009-02-19 10:57:31 -0800115 /**
116 * If the zoom ring is dismissed but the user is still in a touch
117 * interaction, we set this to true. This will ignore all touch events until
118 * up/cancel, and then set the owner's touch listener to null.
119 */
120 private boolean mReleaseTouchListenerOnUp;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800121
122
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800123 /*
124 * Tap-drag is an interaction where the user first taps and then (quickly)
125 * does the clockwise or counter-clockwise drag. In reality, this is: (down,
126 * up, down, move in circles, up). This differs from the usual events of:
127 * (down, up, down, up, down, move in circles, up). While the only
128 * difference is the omission of an (up, down), for power-users this is a
129 * pretty big improvement as it now only requires them to focus on the
130 * screen once (for the first tap down) instead of twice (for the first tap
131 * down and then to grab the thumb).
132 */
133 private int mTapDragStartX;
134 private int mTapDragStartY;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800135
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800136 private static final int TOUCH_MODE_IDLE = 0;
137 private static final int TOUCH_MODE_WAITING_FOR_SECOND_TAP = 1;
138 private static final int TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT = 2;
139 private static final int TOUCH_MODE_FORWARDING_FOR_TAP_DRAG = 3;
140 private int mTouchMode;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800141
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800142 private boolean mIsZoomRingVisible;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800143
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800144 private ZoomRing mZoomRing;
145 private int mZoomRingWidth;
146 private int mZoomRingHeight;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800147
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800148 /** Invokes panning of owner view if the zoom ring is touching an edge. */
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800149 private Panner mPanner;
The Android Open Source Project3001a032009-02-19 10:57:31 -0800150 private long mTouchingEdgeStartTime;
151 private boolean mPanningEnabledForThisInteraction;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800152
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800153 private ImageView mPanningArrows;
154 private Animation mPanningArrowsEnterAnimation;
155 private Animation mPanningArrowsExitAnimation;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800156
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800157 private Rect mTempRect = new Rect();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800158
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800159 private OnZoomListener mCallback;
160
161 private ViewConfiguration mViewConfig;
162
163 /**
164 * When the zoom ring is centered on screen, this will be the x value used
165 * for the container's layout params.
166 */
167 private int mCenteredContainerX = Integer.MIN_VALUE;
168
169 /**
170 * When the zoom ring is centered on screen, this will be the y value used
171 * for the container's layout params.
172 */
173 private int mCenteredContainerY = Integer.MIN_VALUE;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800174
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800175 /**
176 * Scroller used to re-center the zoom ring if the user had dragged it to a
177 * corner and then double-taps any point on the owner view (the owner view
178 * will center the double-tapped point, but we should re-center the zoom
179 * ring).
180 * <p>
181 * The (x,y) of the scroller is the (x,y) of the container's layout params.
182 */
183 private Scroller mScroller;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800184
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800185 /**
186 * When showing the zoom ring, we add the view as a new window. However,
187 * there is logic that needs to know the size of the zoom ring which is
188 * determined after it's laid out. Therefore, we must post this logic onto
189 * the UI thread so it will be exceuted AFTER the layout. This is the logic.
190 */
191 private Runnable mPostedVisibleInitializer;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800192
The Android Open Source Project3001a032009-02-19 10:57:31 -0800193 /**
194 * Only touch from the main thread.
195 */
196 private static Dialog sTutorialDialog;
197 private static long sTutorialShowTime;
198 private static final int TUTORIAL_MIN_DISPLAY_TIME = 2000;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800199
200 private IntentFilter mConfigurationChangedFilter =
201 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800202
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800203 private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
204 @Override
205 public void onReceive(Context context, Intent intent) {
206 if (!mIsZoomRingVisible) return;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800207
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800208 mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
209 mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
210 }
211 };
The Android Open Source Project076357b2009-03-03 14:04:24 -0800212
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800213 /** Keeps the scroller going (or starts it). */
214 private static final int MSG_SCROLLER_TICK = 1;
215 /** When configuration changes, this is called after the UI thread is idle. */
216 private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
217 /** Used to delay the zoom ring dismissal. */
218 private static final int MSG_DISMISS_ZOOM_RING = 3;
219
220 private Handler mHandler = new Handler() {
221 @Override
222 public void handleMessage(Message msg) {
223 switch (msg.what) {
224 case MSG_SCROLLER_TICK:
225 onScrollerTick();
226 break;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800227
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800228 case MSG_POST_CONFIGURATION_CHANGED:
229 onPostConfigurationChanged();
230 break;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800231
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800232 case MSG_DISMISS_ZOOM_RING:
233 setVisible(false);
234 break;
235 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800236
237 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800238 };
The Android Open Source Project076357b2009-03-03 14:04:24 -0800239
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800240 public ZoomRingController(Context context, View ownerView) {
241 mContext = context;
242 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800243
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800244 mPanner = new Panner();
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800245 mOwnerView = ownerView;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800246
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800247 mZoomRing = new ZoomRing(context);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800248 mZoomRing.setId(com.android.internal.R.id.zoomControls);
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800249 mZoomRing.setLayoutParams(new FrameLayout.LayoutParams(
250 FrameLayout.LayoutParams.WRAP_CONTENT,
The Android Open Source Project3001a032009-02-19 10:57:31 -0800251 FrameLayout.LayoutParams.WRAP_CONTENT,
252 Gravity.CENTER));
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800253 mZoomRing.setCallback(this);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800254
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800255 createPanningArrows();
256
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800257 mContainerLayoutParams = new LayoutParams();
258 mContainerLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
The Android Open Source Project3001a032009-02-19 10:57:31 -0800259 mContainerLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800260 LayoutParams.FLAG_NOT_FOCUSABLE |
The Android Open Source Project3001a032009-02-19 10:57:31 -0800261 LayoutParams.FLAG_LAYOUT_NO_LIMITS;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800262 mContainerLayoutParams.height = LayoutParams.WRAP_CONTENT;
263 mContainerLayoutParams.width = LayoutParams.WRAP_CONTENT;
264 mContainerLayoutParams.type = LayoutParams.TYPE_APPLICATION_PANEL;
The Android Open Source Project3001a032009-02-19 10:57:31 -0800265 mContainerLayoutParams.format = PixelFormat.TRANSPARENT;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800266 // TODO: make a new animation for this
267 mContainerLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Dialog;
The Android Open Source Project3001a032009-02-19 10:57:31 -0800268
269 mContainer = new FrameLayout(context);
270 mContainer.setLayoutParams(mContainerLayoutParams);
271 mContainer.setMeasureAllChildren(true);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800272
The Android Open Source Project3001a032009-02-19 10:57:31 -0800273 mContainer.addView(mZoomRing);
274 mContainer.addView(mPanningArrows);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800275
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800276 mScroller = new Scroller(context, new DecelerateInterpolator());
The Android Open Source Project076357b2009-03-03 14:04:24 -0800277
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800278 mViewConfig = ViewConfiguration.get(context);
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800279 }
280
281 private void createPanningArrows() {
282 // TODO: style
283 mPanningArrows = new ImageView(mContext);
284 mPanningArrows.setImageResource(com.android.internal.R.drawable.zoom_ring_arrows);
285 mPanningArrows.setLayoutParams(new FrameLayout.LayoutParams(
286 FrameLayout.LayoutParams.WRAP_CONTENT,
287 FrameLayout.LayoutParams.WRAP_CONTENT,
288 Gravity.CENTER));
The Android Open Source Project3001a032009-02-19 10:57:31 -0800289 mPanningArrows.setVisibility(View.INVISIBLE);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800290
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800291 mPanningArrowsEnterAnimation = AnimationUtils.loadAnimation(mContext,
292 com.android.internal.R.anim.fade_in);
293 mPanningArrowsExitAnimation = AnimationUtils.loadAnimation(mContext,
294 com.android.internal.R.anim.fade_out);
295 }
296
297 /**
298 * Sets the angle (in radians) a user must travel in order for the client to
299 * get a callback. Once there is a callback, the accumulator resets. For
300 * example, if you set this to PI/6, it will give a callback every time the
301 * user moves PI/6 amount on the ring.
The Android Open Source Project076357b2009-03-03 14:04:24 -0800302 *
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800303 * @param callbackThreshold The angle for the callback threshold, in radians
304 */
305 public void setZoomCallbackThreshold(float callbackThreshold) {
306 mZoomRing.setCallbackThreshold((int) (callbackThreshold * ZoomRing.RADIAN_INT_MULTIPLIER));
307 }
The Android Open Source Project3001a032009-02-19 10:57:31 -0800308
309 /**
310 * Sets a drawable for the zoom ring track.
The Android Open Source Project076357b2009-03-03 14:04:24 -0800311 *
The Android Open Source Project3001a032009-02-19 10:57:31 -0800312 * @param drawable The drawable to use for the track.
313 * @hide Need a better way of doing this, but this one-off for browser so it
314 * can have its final look for the usability study
315 */
316 public void setZoomRingTrack(int drawable) {
317 mZoomRing.setBackgroundResource(drawable);
318 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800319
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800320 public void setCallback(OnZoomListener callback) {
321 mCallback = callback;
322 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800323
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800324 public void setThumbAngle(float angle) {
325 mZoomRing.setThumbAngle((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER));
326 }
327
The Android Open Source Project3001a032009-02-19 10:57:31 -0800328 public void setThumbAngleAnimated(float angle) {
329 mZoomRing.setThumbAngleAnimated((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER), 0);
330 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800331
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800332 public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
333 mZoomRing.setResetThumbAutomatically(resetThumbAutomatically);
334 }
335
The Android Open Source Project3001a032009-02-19 10:57:31 -0800336 public void setThumbClockwiseBound(float angle) {
The Android Open Source Project076357b2009-03-03 14:04:24 -0800337 mZoomRing.setThumbClockwiseBound(angle >= 0 ?
The Android Open Source Project3001a032009-02-19 10:57:31 -0800338 (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) :
339 Integer.MIN_VALUE);
340 }
341
342 public void setThumbCounterclockwiseBound(float angle) {
343 mZoomRing.setThumbCounterclockwiseBound(angle >= 0 ?
344 (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) :
345 Integer.MIN_VALUE);
346 }
347
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800348 public boolean isVisible() {
349 return mIsZoomRingVisible;
350 }
351
352 public void setVisible(boolean visible) {
353
The Android Open Source Project076357b2009-03-03 14:04:24 -0800354 if (useOldZoom(mContext)) return;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800355
356 if (visible) {
357 dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
358 } else {
359 mPanner.stop();
360 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800361
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800362 if (mIsZoomRingVisible == visible) {
363 return;
364 }
The Android Open Source Project3001a032009-02-19 10:57:31 -0800365 mIsZoomRingVisible = visible;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800366
367 if (visible) {
368 if (mContainerLayoutParams.token == null) {
369 mContainerLayoutParams.token = mOwnerView.getWindowToken();
370 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800371
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800372 mWindowManager.addView(mContainer, mContainerLayoutParams);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800373
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800374 if (mPostedVisibleInitializer == null) {
375 mPostedVisibleInitializer = new Runnable() {
376 public void run() {
377 refreshPositioningVariables();
378 resetZoomRing();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800379
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800380 // TODO: remove this 'update' and just center zoom ring before the
381 // 'add', but need to make sure we have the width and height (which
382 // probably can only be retrieved after it's measured, which happens
383 // after it's added).
384 mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800385
The Android Open Source Project3001a032009-02-19 10:57:31 -0800386 if (mCallback != null) {
387 mCallback.onVisibilityChanged(true);
388 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800389 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800390 };
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800391 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800392
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800393 mPanningArrows.setAnimation(null);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800394
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800395 mHandler.post(mPostedVisibleInitializer);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800396
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800397 // Handle configuration changes when visible
398 mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800399
The Android Open Source Project3001a032009-02-19 10:57:31 -0800400 // Steal key/touches events from the owner
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800401 mOwnerView.setOnKeyListener(this);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800402 mOwnerView.setOnTouchListener(this);
403 mReleaseTouchListenerOnUp = false;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800404
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800405 } else {
The Android Open Source Project3001a032009-02-19 10:57:31 -0800406 // Don't want to steal any more keys/touches
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800407 mOwnerView.setOnKeyListener(null);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800408 if (mTouchTargetView != null) {
409 // We are still stealing the touch events for this touch
410 // sequence, so release the touch listener later
411 mReleaseTouchListenerOnUp = true;
412 } else {
413 mOwnerView.setOnTouchListener(null);
414 }
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800415
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800416 // No longer care about configuration changes
417 mContext.unregisterReceiver(mConfigurationChangedReceiver);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800418
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800419 mWindowManager.removeView(mContainer);
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800420 mHandler.removeCallbacks(mPostedVisibleInitializer);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800421
The Android Open Source Project3001a032009-02-19 10:57:31 -0800422 if (mCallback != null) {
423 mCallback.onVisibilityChanged(false);
424 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800425 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800426
The Android Open Source Project3001a032009-02-19 10:57:31 -0800427 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800428
The Android Open Source Project3001a032009-02-19 10:57:31 -0800429 /**
430 * TODO: docs
The Android Open Source Project076357b2009-03-03 14:04:24 -0800431 *
The Android Open Source Project3001a032009-02-19 10:57:31 -0800432 * Notes:
433 * - Touch dispatching is different. Only direct children who are clickable are eligble for touch events.
434 * - Please ensure you set your View to INVISIBLE not GONE when hiding it.
The Android Open Source Project076357b2009-03-03 14:04:24 -0800435 *
The Android Open Source Project3001a032009-02-19 10:57:31 -0800436 * @return
437 */
438 public FrameLayout getContainer() {
439 return mContainer;
440 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800441
The Android Open Source Project3001a032009-02-19 10:57:31 -0800442 public int getZoomRingId() {
443 return mZoomRing.getId();
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800444 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800445
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800446 private void dismissZoomRingDelayed(int delay) {
447 mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
448 mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay);
449 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800450
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800451 private void resetZoomRing() {
452 mScroller.abortAnimation();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800453
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800454 mContainerLayoutParams.x = mCenteredContainerX;
455 mContainerLayoutParams.y = mCenteredContainerY;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800456
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800457 // Reset the thumb
458 mZoomRing.resetThumbAngle();
459 }
460
461 /**
462 * Should be called by the client for each event belonging to the second tap
463 * (the down, move, up, and cancel events).
The Android Open Source Project076357b2009-03-03 14:04:24 -0800464 *
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800465 * @param event The event belonging to the second tap.
466 * @return Whether the event was consumed.
467 */
468 public boolean handleDoubleTapEvent(MotionEvent event) {
The Android Open Source Project3dec7d52009-03-02 22:54:33 -0800469 int action = event.getAction();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800470
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800471 // TODO: make sure this works well with the
472 // ownerView.setOnTouchListener(this) instead of window receiving
473 // touches
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800474 if (action == MotionEvent.ACTION_DOWN) {
475 mTouchMode = TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT;
476 int x = (int) event.getX();
477 int y = (int) event.getY();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800478
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800479 refreshPositioningVariables();
480 setVisible(true);
481 centerPoint(x, y);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800482 ensureZoomRingIsCentered();
483
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800484 // Tap drag mode stuff
485 mTapDragStartX = x;
486 mTapDragStartY = y;
487
488 } else if (action == MotionEvent.ACTION_CANCEL) {
489 mTouchMode = TOUCH_MODE_IDLE;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800490
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800491 } else { // action is move or up
492 switch (mTouchMode) {
493 case TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT: {
494 switch (action) {
495 case MotionEvent.ACTION_MOVE:
496 int x = (int) event.getX();
497 int y = (int) event.getY();
498 if (Math.abs(x - mTapDragStartX) > mViewConfig.getScaledTouchSlop() ||
499 Math.abs(y - mTapDragStartY) >
500 mViewConfig.getScaledTouchSlop()) {
501 mZoomRing.setTapDragMode(true, x, y);
502 mTouchMode = TOUCH_MODE_FORWARDING_FOR_TAP_DRAG;
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800503 setTouchTargetView(mZoomRing);
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800504 }
505 return true;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800506
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800507 case MotionEvent.ACTION_UP:
508 mTouchMode = TOUCH_MODE_IDLE;
509 break;
510 }
511 break;
512 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800513
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800514 case TOUCH_MODE_FORWARDING_FOR_TAP_DRAG: {
515 switch (action) {
516 case MotionEvent.ACTION_MOVE:
517 giveTouchToZoomRing(event);
518 return true;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800519
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800520 case MotionEvent.ACTION_UP:
521 mTouchMode = TOUCH_MODE_IDLE;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800522
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800523 /*
524 * This is a power-user feature that only shows the
525 * zoom while the user is performing the tap-drag.
526 * That means once it is released, the zoom ring
527 * should disappear.
The Android Open Source Project076357b2009-03-03 14:04:24 -0800528 */
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800529 mZoomRing.setTapDragMode(false, (int) event.getX(), (int) event.getY());
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800530 dismissZoomRingDelayed(0);
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800531 break;
532 }
533 break;
534 }
535 }
536 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800537
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800538 return true;
539 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800540
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800541 private void ensureZoomRingIsCentered() {
542 LayoutParams lp = mContainerLayoutParams;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800543
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800544 if (lp.x != mCenteredContainerX || lp.y != mCenteredContainerY) {
545 int width = mContainer.getWidth();
546 int height = mContainer.getHeight();
547 mScroller.startScroll(lp.x, lp.y, mCenteredContainerX - lp.x,
548 mCenteredContainerY - lp.y, ZOOM_RING_RECENTERING_DURATION);
549 mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
550 }
551 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800552
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800553 private void refreshPositioningVariables() {
554 mZoomRingWidth = mZoomRing.getWidth();
555 mZoomRingHeight = mZoomRing.getHeight();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800556
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800557 // Calculate the owner view's bounds
558 mOwnerView.getGlobalVisibleRect(mOwnerViewBounds);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800559
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800560 // Get the center
561 Gravity.apply(Gravity.CENTER, mContainer.getWidth(), mContainer.getHeight(),
562 mOwnerViewBounds, mTempRect);
563 mCenteredContainerX = mTempRect.left;
564 mCenteredContainerY = mTempRect.top;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800565 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800566
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800567 /**
568 * Centers the point (in owner view's coordinates).
569 */
570 private void centerPoint(int x, int y) {
571 if (mCallback != null) {
572 mCallback.onCenter(x, y);
573 }
574 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800575
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800576 private void giveTouchToZoomRing(MotionEvent event) {
577 int rawX = (int) event.getRawX();
578 int rawY = (int) event.getRawY();
579 int x = rawX - mContainerLayoutParams.x - mZoomRing.getLeft();
580 int y = rawY - mContainerLayoutParams.y - mZoomRing.getTop();
581 mZoomRing.handleTouch(event.getAction(), event.getEventTime(), x, y, rawX, rawY);
582 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800583
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800584 public void onZoomRingSetMovableHintVisible(boolean visible) {
The Android Open Source Project076357b2009-03-03 14:04:24 -0800585 setPanningArrowsVisible(visible);
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800586 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800587
The Android Open Source Project3001a032009-02-19 10:57:31 -0800588 public void onUserInteractionStarted() {
589 mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
590 }
591
592 public void onUserInteractionStopped() {
593 dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
594 }
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800595
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800596 public void onZoomRingMovingStarted() {
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800597 mScroller.abortAnimation();
The Android Open Source Project3001a032009-02-19 10:57:31 -0800598 mTouchingEdgeStartTime = 0;
599 if (mCallback != null) {
600 mCallback.onBeginPan();
601 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800602 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800603
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800604 private void setPanningArrowsVisible(boolean visible) {
605 mPanningArrows.startAnimation(visible ? mPanningArrowsEnterAnimation
606 : mPanningArrowsExitAnimation);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800607 mPanningArrows.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800608 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800609
610 public boolean onZoomRingMoved(int deltaX, int deltaY) {
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800611 WindowManager.LayoutParams lp = mContainerLayoutParams;
612 Rect ownerBounds = mOwnerViewBounds;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800613
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800614 int zoomRingLeft = mZoomRing.getLeft();
615 int zoomRingTop = mZoomRing.getTop();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800616
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800617 int newX = lp.x + deltaX;
618 int newZoomRingX = newX + zoomRingLeft;
619 newZoomRingX = (newZoomRingX <= ownerBounds.left) ? ownerBounds.left :
620 (newZoomRingX + mZoomRingWidth > ownerBounds.right) ?
621 ownerBounds.right - mZoomRingWidth : newZoomRingX;
622 lp.x = newZoomRingX - zoomRingLeft;
623
624 int newY = lp.y + deltaY;
625 int newZoomRingY = newY + zoomRingTop;
626 newZoomRingY = (newZoomRingY <= ownerBounds.top) ? ownerBounds.top :
627 (newZoomRingY + mZoomRingHeight > ownerBounds.bottom) ?
628 ownerBounds.bottom - mZoomRingHeight : newZoomRingY;
629 lp.y = newZoomRingY - zoomRingTop;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800630
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800631 mWindowManager.updateViewLayout(mContainer, lp);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800632
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800633 // Check for pan
The Android Open Source Project3001a032009-02-19 10:57:31 -0800634 boolean horizontalPanning = true;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800635 int leftGap = newZoomRingX - ownerBounds.left;
636 if (leftGap < MAX_PAN_GAP) {
The Android Open Source Project3001a032009-02-19 10:57:31 -0800637 if (shouldPan(leftGap)) {
638 mPanner.setHorizontalStrength(-getStrengthFromGap(leftGap));
639 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800640 } else {
641 int rightGap = ownerBounds.right - (lp.x + mZoomRingWidth + zoomRingLeft);
642 if (rightGap < MAX_PAN_GAP) {
The Android Open Source Project3001a032009-02-19 10:57:31 -0800643 if (shouldPan(rightGap)) {
644 mPanner.setHorizontalStrength(getStrengthFromGap(rightGap));
645 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800646 } else {
647 mPanner.setHorizontalStrength(0);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800648 horizontalPanning = false;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800649 }
650 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800651
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800652 int topGap = newZoomRingY - ownerBounds.top;
653 if (topGap < MAX_PAN_GAP) {
The Android Open Source Project3001a032009-02-19 10:57:31 -0800654 if (shouldPan(topGap)) {
655 mPanner.setVerticalStrength(-getStrengthFromGap(topGap));
656 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800657 } else {
658 int bottomGap = ownerBounds.bottom - (lp.y + mZoomRingHeight + zoomRingTop);
659 if (bottomGap < MAX_PAN_GAP) {
The Android Open Source Project3001a032009-02-19 10:57:31 -0800660 if (shouldPan(bottomGap)) {
661 mPanner.setVerticalStrength(getStrengthFromGap(bottomGap));
662 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800663 } else {
664 mPanner.setVerticalStrength(0);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800665 if (!horizontalPanning) {
666 // Neither are panning, reset any timer to start pan mode
667 mTouchingEdgeStartTime = 0;
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800668 mPanningEnabledForThisInteraction = false;
669 mPanner.stop();
The Android Open Source Project3001a032009-02-19 10:57:31 -0800670 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800671 }
672 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800673
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800674 return true;
675 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800676
The Android Open Source Project3001a032009-02-19 10:57:31 -0800677 private boolean shouldPan(int gap) {
678 if (mPanningEnabledForThisInteraction) return true;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800679
The Android Open Source Project3001a032009-02-19 10:57:31 -0800680 if (gap < MAX_INITIATE_PAN_GAP) {
681 long time = SystemClock.elapsedRealtime();
682 if (mTouchingEdgeStartTime != 0 &&
683 mTouchingEdgeStartTime + INITIATE_PAN_DELAY < time) {
684 mPanningEnabledForThisInteraction = true;
685 return true;
686 } else if (mTouchingEdgeStartTime == 0) {
687 mTouchingEdgeStartTime = time;
688 } else {
689 }
690 } else {
691 // Moved away from the initiate pan gap, so reset the timer
692 mTouchingEdgeStartTime = 0;
693 }
694 return false;
695 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800696
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800697 public void onZoomRingMovingStopped() {
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800698 mPanner.stop();
The Android Open Source Project3001a032009-02-19 10:57:31 -0800699 setPanningArrowsVisible(false);
700 if (mCallback != null) {
701 mCallback.onEndPan();
702 }
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800703 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800704
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800705 private int getStrengthFromGap(int gap) {
706 return gap > MAX_PAN_GAP ? 0 :
707 (MAX_PAN_GAP - gap) * 100 / MAX_PAN_GAP;
708 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800709
The Android Open Source Project3001a032009-02-19 10:57:31 -0800710 public void onZoomRingThumbDraggingStarted() {
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800711 if (mCallback != null) {
The Android Open Source Project3001a032009-02-19 10:57:31 -0800712 mCallback.onBeginDrag();
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800713 }
714 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800715
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800716 public boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle) {
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800717 if (mCallback != null) {
718 int deltaZoomLevel = -numLevels;
719 int globalZoomCenterX = mContainerLayoutParams.x + mZoomRing.getLeft() +
720 mZoomRingWidth / 2;
721 int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() +
722 mZoomRingHeight / 2;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800723
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800724 return mCallback.onDragZoom(deltaZoomLevel,
725 globalZoomCenterX - mOwnerViewBounds.left,
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800726 globalZoomCenterY - mOwnerViewBounds.top,
727 (float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER,
728 (float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER);
729 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800730
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800731 return false;
732 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800733
The Android Open Source Project3001a032009-02-19 10:57:31 -0800734 public void onZoomRingThumbDraggingStopped() {
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800735 if (mCallback != null) {
The Android Open Source Project3001a032009-02-19 10:57:31 -0800736 mCallback.onEndDrag();
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800737 }
738 }
739
The Android Open Source Project3001a032009-02-19 10:57:31 -0800740 public void onZoomRingDismissed(boolean dismissImmediately) {
741 if (dismissImmediately) {
742 mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800743 setVisible(false);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800744 } else {
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800745 dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800746 }
747 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800748
The Android Open Source Project3001a032009-02-19 10:57:31 -0800749 public void onRingDown(int tickAngle, int touchAngle) {
750 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800751
The Android Open Source Project3001a032009-02-19 10:57:31 -0800752 public boolean onTouch(View v, MotionEvent event) {
753 if (sTutorialDialog != null && sTutorialDialog.isShowing() &&
754 SystemClock.elapsedRealtime() - sTutorialShowTime >= TUTORIAL_MIN_DISPLAY_TIME) {
755 finishZoomTutorial();
756 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800757
The Android Open Source Project3001a032009-02-19 10:57:31 -0800758 int action = event.getAction();
759
760 if (mReleaseTouchListenerOnUp) {
The Android Open Source Project076357b2009-03-03 14:04:24 -0800761 // The ring was dismissed but we need to throw away all events until the up
The Android Open Source Project3001a032009-02-19 10:57:31 -0800762 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
763 mOwnerView.setOnTouchListener(null);
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800764 setTouchTargetView(null);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800765 mReleaseTouchListenerOnUp = false;
766 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800767
The Android Open Source Project3001a032009-02-19 10:57:31 -0800768 // Eat this event
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800769 return true;
770 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800771
The Android Open Source Project3001a032009-02-19 10:57:31 -0800772 View targetView = mTouchTargetView;
773
774 switch (action) {
775 case MotionEvent.ACTION_DOWN:
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800776 targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY());
777 setTouchTargetView(targetView);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800778 break;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800779
The Android Open Source Project3001a032009-02-19 10:57:31 -0800780 case MotionEvent.ACTION_UP:
781 case MotionEvent.ACTION_CANCEL:
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800782 setTouchTargetView(null);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800783 break;
784 }
785
786 if (targetView != null) {
787 // The upperleft corner of the target view in raw coordinates
788 int targetViewRawX = mContainerLayoutParams.x + mTouchTargetLocationInWindow[0];
789 int targetViewRawY = mContainerLayoutParams.y + mTouchTargetLocationInWindow[1];
The Android Open Source Project076357b2009-03-03 14:04:24 -0800790
The Android Open Source Project3001a032009-02-19 10:57:31 -0800791 MotionEvent containerEvent = MotionEvent.obtain(event);
792 // Convert the motion event into the target view's coordinates (from
793 // owner view's coordinates)
794 containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX,
795 mOwnerViewBounds.top - targetViewRawY);
796 boolean retValue = targetView.dispatchTouchEvent(containerEvent);
797 containerEvent.recycle();
798 return retValue;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800799
The Android Open Source Project3001a032009-02-19 10:57:31 -0800800 } else {
801 if (action == MotionEvent.ACTION_DOWN) {
802 dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
803 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800804
The Android Open Source Project3001a032009-02-19 10:57:31 -0800805 return false;
806 }
807 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800808
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800809 private void setTouchTargetView(View view) {
810 mTouchTargetView = view;
811 if (view != null) {
812 view.getLocationInWindow(mTouchTargetLocationInWindow);
813 }
814 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800815
The Android Open Source Project3001a032009-02-19 10:57:31 -0800816 /**
817 * Returns the View that should receive a touch at the given coordinates.
The Android Open Source Project076357b2009-03-03 14:04:24 -0800818 *
The Android Open Source Project3001a032009-02-19 10:57:31 -0800819 * @param rawX The raw X.
820 * @param rawY The raw Y.
821 * @return The view that should receive the touches, or null if there is not one.
822 */
823 private View getViewForTouch(int rawX, int rawY) {
The Android Open Source Project076357b2009-03-03 14:04:24 -0800824 // Check to see if it is touching the ring
The Android Open Source Project3001a032009-02-19 10:57:31 -0800825 int containerCenterX = mContainerLayoutParams.x + mContainer.getWidth() / 2;
826 int containerCenterY = mContainerLayoutParams.y + mContainer.getHeight() / 2;
827 int distanceFromCenterX = rawX - containerCenterX;
828 int distanceFromCenterY = rawY - containerCenterY;
829 int zoomRingRadius = mZoomRingWidth / 2 - ZOOM_RING_RADIUS_INSET;
830 if (distanceFromCenterX * distanceFromCenterX +
831 distanceFromCenterY * distanceFromCenterY <=
832 zoomRingRadius * zoomRingRadius) {
833 return mZoomRing;
834 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800835
The Android Open Source Project3001a032009-02-19 10:57:31 -0800836 // Check to see if it is touching any other clickable View.
837 // Reverse order so the child drawn on top gets first dibs.
838 int containerCoordsX = rawX - mContainerLayoutParams.x;
839 int containerCoordsY = rawY - mContainerLayoutParams.y;
840 Rect frame = mTempRect;
841 for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
842 View child = mContainer.getChildAt(i);
843 if (child == mZoomRing || child.getVisibility() != View.VISIBLE ||
844 !child.isClickable()) {
845 continue;
846 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800847
The Android Open Source Project3001a032009-02-19 10:57:31 -0800848 child.getHitRect(frame);
849 if (frame.contains(containerCoordsX, containerCoordsY)) {
850 return child;
851 }
852 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800853
The Android Open Source Project3001a032009-02-19 10:57:31 -0800854 return null;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800855 }
856
857 /** Steals key events from the owner view. */
858 public boolean onKey(View v, int keyCode, KeyEvent event) {
859 switch (keyCode) {
860 case KeyEvent.KEYCODE_DPAD_LEFT:
861 case KeyEvent.KEYCODE_DPAD_RIGHT:
862 // Eat these
863 return true;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800864
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800865 case KeyEvent.KEYCODE_DPAD_UP:
866 case KeyEvent.KEYCODE_DPAD_DOWN:
867 // Keep the zoom alive a little longer
868 dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
The Android Open Source Project3001a032009-02-19 10:57:31 -0800869 // They started zooming, hide the thumb arrows
870 mZoomRing.setThumbArrowsVisible(false);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800871
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800872 if (mCallback != null && event.getAction() == KeyEvent.ACTION_DOWN) {
873 mCallback.onSimpleZoom(keyCode == KeyEvent.KEYCODE_DPAD_UP);
874 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800875
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800876 return true;
877 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800878
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800879 return false;
880 }
881
882 private void onScrollerTick() {
883 if (!mScroller.computeScrollOffset() || !mIsZoomRingVisible) return;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800884
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800885 mContainerLayoutParams.x = mScroller.getCurrX();
886 mContainerLayoutParams.y = mScroller.getCurrY();
887 mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
888
889 mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
890 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800891
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800892 private void onPostConfigurationChanged() {
893 dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
894 refreshPositioningVariables();
895 ensureZoomRingIsCentered();
896 }
897
The Android Open Source Project3001a032009-02-19 10:57:31 -0800898 /*
899 * This is static so Activities can call this instead of the Views
900 * (Activities usually do not have a reference to the ZoomRingController
901 * instance.)
902 */
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800903 /**
904 * Shows a "tutorial" (some text) to the user teaching her the new zoom
The Android Open Source Project3001a032009-02-19 10:57:31 -0800905 * invocation method. Must call from the main thread.
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800906 * <p>
907 * It checks the global system setting to ensure this has not been seen
908 * before. Furthermore, if the application does not have privilege to write
909 * to the system settings, it will store this bit locally in a shared
910 * preference.
The Android Open Source Project076357b2009-03-03 14:04:24 -0800911 *
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800912 * @hide This should only be used by our main apps--browser, maps, and
913 * gallery
914 */
915 public static void showZoomTutorialOnce(Context context) {
916 ContentResolver cr = context.getContentResolver();
917 if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TOAST, 0) == 1) {
918 return;
919 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800920
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800921 SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
922 if (sp.getInt(SETTING_NAME_SHOWN_TOAST, 0) == 1) {
923 return;
924 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800925
The Android Open Source Project3001a032009-02-19 10:57:31 -0800926 if (sTutorialDialog != null && sTutorialDialog.isShowing()) {
927 sTutorialDialog.dismiss();
928 }
929
930 sTutorialDialog = new AlertDialog.Builder(context)
The Android Open Source Project076357b2009-03-03 14:04:24 -0800931 .setMessage(
932 com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short)
The Android Open Source Project3001a032009-02-19 10:57:31 -0800933 .setIcon(0)
934 .create();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800935
The Android Open Source Project3001a032009-02-19 10:57:31 -0800936 Window window = sTutorialDialog.getWindow();
937 window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800938 window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND |
The Android Open Source Project3001a032009-02-19 10:57:31 -0800939 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
940 window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
941 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
The Android Open Source Project076357b2009-03-03 14:04:24 -0800942
The Android Open Source Project3001a032009-02-19 10:57:31 -0800943 sTutorialDialog.show();
944 sTutorialShowTime = SystemClock.elapsedRealtime();
945 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800946
947 public void finishZoomTutorial() {
The Android Open Source Project3001a032009-02-19 10:57:31 -0800948 if (sTutorialDialog == null) return;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800949
The Android Open Source Project3001a032009-02-19 10:57:31 -0800950 sTutorialDialog.dismiss();
951 sTutorialDialog = null;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800952
The Android Open Source Project3001a032009-02-19 10:57:31 -0800953 // Record that they have seen the tutorial
The Android Open Source Project076357b2009-03-03 14:04:24 -0800954 try {
955 Settings.System.putInt(mContext.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1);
956 } catch (SecurityException e) {
957 /*
958 * The app does not have permission to clear this global flag, make
959 * sure the user does not see the message when he comes back to this
960 * same app at least.
961 */
962 SharedPreferences sp = mContext.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
963 sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit();
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800964 }
The Android Open Source Projectda996f32009-02-13 12:57:50 -0800965 }
The Android Open Source Project076357b2009-03-03 14:04:24 -0800966
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -0800967 public void setPannerStartVelocity(float startVelocity) {
968 mPanner.mStartVelocity = startVelocity;
969 }
970
971 public void setPannerAcceleration(float acceleration) {
972 mPanner.mAcceleration = acceleration;
973 }
974
975 public void setPannerMaxVelocity(float maxVelocity) {
976 mPanner.mMaxVelocity = maxVelocity;
977 }
978
979 public void setPannerStartAcceleratingDuration(int duration) {
980 mPanner.mStartAcceleratingDuration = duration;
981 }
982
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800983 private class Panner implements Runnable {
984 private static final int RUN_DELAY = 15;
985 private static final float STOP_SLOWDOWN = 0.8f;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800986
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800987 private final Handler mUiHandler = new Handler();
The Android Open Source Project076357b2009-03-03 14:04:24 -0800988
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800989 private int mVerticalStrength;
990 private int mHorizontalStrength;
991
992 private boolean mStopping;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800993
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800994 /** The time this current pan started. */
995 private long mStartTime;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800996
The Android Open Source Projectd24b8182009-02-10 15:44:00 -0800997 /** The time of the last callback to pan the map/browser/etc. */
998 private long mPreviousCallbackTime;
The Android Open Source Project076357b2009-03-03 14:04:24 -0800999
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -08001000 // TODO Adjust to be DPI safe
1001 private float mStartVelocity = 135;
1002 private float mAcceleration = 160;
1003 private float mMaxVelocity = 1000;
1004 private int mStartAcceleratingDuration = 700;
1005 private float mVelocity;
The Android Open Source Project076357b2009-03-03 14:04:24 -08001006
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001007 /** -100 (full left) to 0 (none) to 100 (full right) */
1008 public void setHorizontalStrength(int horizontalStrength) {
1009 if (mHorizontalStrength == 0 && mVerticalStrength == 0 && horizontalStrength != 0) {
1010 start();
1011 } else if (mVerticalStrength == 0 && horizontalStrength == 0) {
1012 stop();
1013 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001014
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001015 mHorizontalStrength = horizontalStrength;
1016 mStopping = false;
1017 }
1018
1019 /** -100 (full up) to 0 (none) to 100 (full down) */
1020 public void setVerticalStrength(int verticalStrength) {
1021 if (mHorizontalStrength == 0 && mVerticalStrength == 0 && verticalStrength != 0) {
1022 start();
1023 } else if (mHorizontalStrength == 0 && verticalStrength == 0) {
1024 stop();
1025 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001026
1027 mVerticalStrength = verticalStrength;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001028 mStopping = false;
1029 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001030
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001031 private void start() {
1032 mUiHandler.post(this);
1033 mPreviousCallbackTime = 0;
1034 mStartTime = 0;
1035 }
1036
1037 public void stop() {
1038 mStopping = true;
1039 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001040
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001041 public void run() {
1042 if (mStopping) {
1043 mHorizontalStrength *= STOP_SLOWDOWN;
1044 mVerticalStrength *= STOP_SLOWDOWN;
1045 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001046
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001047 if (mHorizontalStrength == 0 && mVerticalStrength == 0) {
1048 return;
1049 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001050
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001051 boolean firstRun = mPreviousCallbackTime == 0;
1052 long curTime = SystemClock.elapsedRealtime();
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -08001053 int panAmount = getPanAmount(mPreviousCallbackTime, curTime);
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001054 mPreviousCallbackTime = curTime;
The Android Open Source Project076357b2009-03-03 14:04:24 -08001055
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001056 if (firstRun) {
1057 mStartTime = curTime;
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -08001058 mVelocity = mStartVelocity;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001059 } else {
1060 int panX = panAmount * mHorizontalStrength / 100;
1061 int panY = panAmount * mVerticalStrength / 100;
The Android Open Source Project076357b2009-03-03 14:04:24 -08001062
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001063 if (mCallback != null) {
1064 mCallback.onPan(panX, panY);
1065 }
1066 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001067
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001068 mUiHandler.postDelayed(this, RUN_DELAY);
1069 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001070
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -08001071 private int getPanAmount(long previousTime, long currentTime) {
1072 if (mVelocity > mMaxVelocity) {
1073 mVelocity = mMaxVelocity;
1074 } else if (mVelocity < mMaxVelocity) {
1075 // See if it's time to add in some acceleration
1076 if (currentTime - mStartTime > mStartAcceleratingDuration) {
1077 mVelocity += (currentTime - previousTime) * mAcceleration / 1000;
1078 }
1079 }
The Android Open Source Project076357b2009-03-03 14:04:24 -08001080
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -08001081 return (int) ((currentTime - previousTime) * mVelocity) / 1000;
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001082 }
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -08001083
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001084 }
The Android Open Source Project15ab3ea2009-02-20 07:38:31 -08001085
The Android Open Source Project076357b2009-03-03 14:04:24 -08001086
1087
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001088 public interface OnZoomListener {
The Android Open Source Project3001a032009-02-19 10:57:31 -08001089 void onBeginDrag();
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001090 boolean onDragZoom(int deltaZoomLevel, int centerX, int centerY, float startAngle,
1091 float curAngle);
The Android Open Source Project3001a032009-02-19 10:57:31 -08001092 void onEndDrag();
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001093 void onSimpleZoom(boolean deltaZoomLevel);
The Android Open Source Project3001a032009-02-19 10:57:31 -08001094 void onBeginPan();
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001095 boolean onPan(int deltaX, int deltaY);
The Android Open Source Project3001a032009-02-19 10:57:31 -08001096 void onEndPan();
The Android Open Source Projectd24b8182009-02-10 15:44:00 -08001097 void onCenter(int x, int y);
1098 void onVisibilityChanged(boolean visible);
1099 }
1100}