blob: 450c966bdbebb5d7656d0a1c042009dc953e5eb2 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -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
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.graphics.PixelFormat;
24import android.graphics.Rect;
25import android.os.Handler;
26import android.os.Message;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070027import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.Gravity;
29import android.view.KeyEvent;
30import android.view.LayoutInflater;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewConfiguration;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070034import android.view.ViewGroup;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070035import android.view.ViewParent;
36import android.view.ViewRoot;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.view.WindowManager;
38import android.view.View.OnClickListener;
39import android.view.WindowManager.LayoutParams;
40
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070041/*
42 * Implementation notes:
43 * - The zoom controls are displayed in their own window.
44 * (Easier for the client and better performance)
The Android Open Source Project10592532009-03-18 17:39:46 -070045 * - This window is never touchable, and by default is not focusable.
46 * Its rect is quite big (fills horizontally) but has empty space between the
47 * edges and center. Touches there should be given to the owner. Instead of
48 * having the window touchable and dispatching these empty touch events to the
49 * owner, we set the window to not touchable and steal events from owner
50 * via onTouchListener.
51 * - To make the buttons clickable, it attaches an OnTouchListener to the owner
52 * view and does the hit detection locally (attaches when visible, detaches when invisible).
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070053 * - When it is focusable, it forwards uninteresting events to the owner view's
54 * view hierarchy.
55 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056/**
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070057 * The {@link ZoomButtonsController} handles showing and hiding the zoom
The Android Open Source Project10592532009-03-18 17:39:46 -070058 * controls and positioning it relative to an owner view. It also gives the
59 * client access to the zoom controls container, allowing for additional
60 * accessory buttons to be shown in the zoom controls window.
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070061 * <p>
The Android Open Source Project10592532009-03-18 17:39:46 -070062 * Typically, clients should call {@link #setVisible(boolean) setVisible(true)}
63 * on a touch down or move (no need to call {@link #setVisible(boolean)
64 * setVisible(false)} since it will time out on its own). Also, whenever the
65 * owner cannot be zoomed further, the client should update
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070066 * {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}.
67 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 * If you are using this with a custom View, please call
Steve Howard16bd9372010-04-12 14:46:09 -070069 * {@link #setVisible(boolean) setVisible(false)} from
70 * {@link View#onDetachedFromWindow} and from {@link View#onVisibilityChanged}
71 * when <code>visibility != View.VISIBLE</code>.
The Android Open Source Project10592532009-03-18 17:39:46 -070072 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 */
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070074public class ZoomButtonsController implements View.OnTouchListener {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075
76 private static final String TAG = "ZoomButtonsController";
77
78 private static final int ZOOM_CONTROLS_TIMEOUT =
79 (int) ViewConfiguration.getZoomControlsTimeout();
80
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070082 private int mTouchPaddingScaledSq;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070083
Owen Lin470681e2009-05-27 17:44:57 -070084 private final Context mContext;
85 private final WindowManager mWindowManager;
The Android Open Source Project10592532009-03-18 17:39:46 -070086 private boolean mAutoDismissControls = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087
88 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070089 * The view that is being zoomed by this zoom controller.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 */
Owen Lin470681e2009-05-27 17:44:57 -070091 private final View mOwnerView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092
93 /**
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -070094 * The location of the owner view on the screen. This is recalculated
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070095 * each time the zoom controller is shown.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 */
Owen Lin470681e2009-05-27 17:44:57 -070097 private final int[] mOwnerViewRawLocation = new int[2];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098
99 /**
100 * The container that is added as a window.
101 */
Owen Lin470681e2009-05-27 17:44:57 -0700102 private final FrameLayout mContainer;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 private LayoutParams mContainerLayoutParams;
Owen Lin470681e2009-05-27 17:44:57 -0700104 private final int[] mContainerRawLocation = new int[2];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105
106 private ZoomControls mControls;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700107
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 /**
109 * The view (or null) that should receive touch events. This will get set if
110 * the touch down hits the container. It will be reset on the touch up.
111 */
112 private View mTouchTargetView;
113 /**
114 * The {@link #mTouchTargetView}'s location in window, set on touch down.
115 */
Owen Lin470681e2009-05-27 17:44:57 -0700116 private final int[] mTouchTargetWindowLocation = new int[2];
The Android Open Source Project10592532009-03-18 17:39:46 -0700117
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700119 * If the zoom controller is dismissed but the user is still in a touch
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 * interaction, we set this to true. This will ignore all touch events until
121 * up/cancel, and then set the owner's touch listener to null.
The Android Open Source Project10592532009-03-18 17:39:46 -0700122 * <p>
123 * Otherwise, the owner view would get mismatched events (i.e., touch move
124 * even though it never got the touch down.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 */
126 private boolean mReleaseTouchListenerOnUp;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700127
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700128 /** Whether the container has been added to the window manager. */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 private boolean mIsVisible;
130
Owen Lin470681e2009-05-27 17:44:57 -0700131 private final Rect mTempRect = new Rect();
132 private final int[] mTempIntArray = new int[2];
The Android Open Source Project10592532009-03-18 17:39:46 -0700133
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 private OnZoomListener mCallback;
135
136 /**
137 * When showing the zoom, we add the view as a new window. However, there is
138 * logic that needs to know the size of the zoom which is determined after
139 * it's laid out. Therefore, we must post this logic onto the UI thread so
140 * it will be exceuted AFTER the layout. This is the logic.
141 */
142 private Runnable mPostedVisibleInitializer;
143
Owen Lin470681e2009-05-27 17:44:57 -0700144 private final IntentFilter mConfigurationChangedFilter =
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
146
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700147 /**
148 * Needed to reposition the zoom controls after configuration changes.
149 */
Owen Lin470681e2009-05-27 17:44:57 -0700150 private final BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 @Override
152 public void onReceive(Context context, Intent intent) {
153 if (!mIsVisible) return;
154
155 mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
156 mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
157 }
158 };
159
160 /** When configuration changes, this is called after the UI thread is idle. */
161 private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700162 /** Used to delay the zoom controller dismissal. */
163 private static final int MSG_DISMISS_ZOOM_CONTROLS = 3;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 /**
165 * If setVisible(true) is called and the owner view's window token is null,
166 * we delay the setVisible(true) call until it is not null.
167 */
168 private static final int MSG_POST_SET_VISIBLE = 4;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700169
Owen Lin470681e2009-05-27 17:44:57 -0700170 private final Handler mHandler = new Handler() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 @Override
172 public void handleMessage(Message msg) {
173 switch (msg.what) {
174 case MSG_POST_CONFIGURATION_CHANGED:
175 onPostConfigurationChanged();
176 break;
177
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700178 case MSG_DISMISS_ZOOM_CONTROLS:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 setVisible(false);
180 break;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700181
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 case MSG_POST_SET_VISIBLE:
183 if (mOwnerView.getWindowToken() == null) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700184 // Doh, it is still null, just ignore the set visible call
185 Log.e(TAG,
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700186 "Cannot make the zoom controller visible if the owner view is " +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 "not attached to a window.");
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700188 } else {
189 setVisible(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 break;
192 }
193
194 }
195 };
196
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700197 /**
198 * Constructor for the {@link ZoomButtonsController}.
The Android Open Source Project10592532009-03-18 17:39:46 -0700199 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700200 * @param ownerView The view that is being zoomed by the zoom controls. The
201 * zoom controls will be displayed aligned with this view.
202 */
203 public ZoomButtonsController(View ownerView) {
204 mContext = ownerView.getContext();
205 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 mOwnerView = ownerView;
207
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700208 mTouchPaddingScaledSq = (int)
209 (ZOOM_CONTROLS_TOUCH_PADDING * mContext.getResources().getDisplayMetrics().density);
210 mTouchPaddingScaledSq *= mTouchPaddingScaledSq;
The Android Open Source Project10592532009-03-18 17:39:46 -0700211
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800212 mContainer = createContainer();
213 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700214
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700215 /**
216 * Whether to enable the zoom in control.
The Android Open Source Project10592532009-03-18 17:39:46 -0700217 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700218 * @param enabled Whether to enable the zoom in control.
219 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700220 public void setZoomInEnabled(boolean enabled) {
221 mControls.setIsZoomInEnabled(enabled);
222 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700223
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700224 /**
225 * Whether to enable the zoom out control.
The Android Open Source Project10592532009-03-18 17:39:46 -0700226 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700227 * @param enabled Whether to enable the zoom out control.
228 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700229 public void setZoomOutEnabled(boolean enabled) {
230 mControls.setIsZoomOutEnabled(enabled);
231 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700232
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700233 /**
234 * Sets the delay between zoom callbacks as the user holds a zoom button.
The Android Open Source Project10592532009-03-18 17:39:46 -0700235 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700236 * @param speed The delay in milliseconds between zoom callbacks.
237 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700238 public void setZoomSpeed(long speed) {
239 mControls.setZoomSpeed(speed);
240 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700241
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 private FrameLayout createContainer() {
243 LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700244 // Controls are positioned BOTTOM | CENTER with respect to the owner view.
245 lp.gravity = Gravity.TOP | Gravity.LEFT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700247 LayoutParams.FLAG_NOT_FOCUSABLE |
The Android Open Source Project10592532009-03-18 17:39:46 -0700248 LayoutParams.FLAG_LAYOUT_NO_LIMITS |
249 LayoutParams.FLAG_ALT_FOCUSABLE_IM;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 lp.height = LayoutParams.WRAP_CONTENT;
Romain Guy980a9382010-01-08 15:06:28 -0800251 lp.width = LayoutParams.MATCH_PARENT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 lp.type = LayoutParams.TYPE_APPLICATION_PANEL;
Dianne Hackborn9767e412009-09-15 18:45:34 -0700253 lp.format = PixelFormat.TRANSLUCENT;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700254 lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 mContainerLayoutParams = lp;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700256
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700257 FrameLayout container = new Container(mContext);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 container.setLayoutParams(lp);
259 container.setMeasureAllChildren(true);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700260
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 LayoutInflater inflater = (LayoutInflater) mContext
262 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700263 inflater.inflate(com.android.internal.R.layout.zoom_container, container);
264
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 mControls = (ZoomControls) container.findViewById(com.android.internal.R.id.zoomControls);
266 mControls.setOnZoomInClickListener(new OnClickListener() {
267 public void onClick(View v) {
268 dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
269 if (mCallback != null) mCallback.onZoom(true);
270 }
271 });
272 mControls.setOnZoomOutClickListener(new OnClickListener() {
273 public void onClick(View v) {
274 dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
275 if (mCallback != null) mCallback.onZoom(false);
276 }
277 });
278
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279 return container;
280 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700281
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700282 /**
283 * Sets the {@link OnZoomListener} listener that receives callbacks to zoom.
The Android Open Source Project10592532009-03-18 17:39:46 -0700284 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700285 * @param listener The listener that will be told to zoom.
286 */
287 public void setOnZoomListener(OnZoomListener listener) {
288 mCallback = listener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 }
290
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700291 /**
292 * Sets whether the zoom controls should be focusable. If the controls are
293 * focusable, then trackball and arrow key interactions are possible.
294 * Otherwise, only touch interactions are possible.
The Android Open Source Project10592532009-03-18 17:39:46 -0700295 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700296 * @param focusable Whether the zoom controls should be focusable.
297 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 public void setFocusable(boolean focusable) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700299 int oldFlags = mContainerLayoutParams.flags;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 if (focusable) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700301 mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 } else {
303 mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
304 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700305
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700306 if ((mContainerLayoutParams.flags != oldFlags) && mIsVisible) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
308 }
309 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800310
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700311 /**
The Android Open Source Project10592532009-03-18 17:39:46 -0700312 * Whether the zoom controls will be automatically dismissed after showing.
313 *
314 * @return Whether the zoom controls will be auto dismissed after showing.
315 */
316 public boolean isAutoDismissed() {
317 return mAutoDismissControls;
318 }
319
320 /**
321 * Sets whether the zoom controls will be automatically dismissed after
322 * showing.
323 */
324 public void setAutoDismissed(boolean autoDismiss) {
325 if (mAutoDismissControls == autoDismiss) return;
326 mAutoDismissControls = autoDismiss;
327 }
328
329 /**
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700330 * Whether the zoom controls are visible to the user.
The Android Open Source Project10592532009-03-18 17:39:46 -0700331 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700332 * @return Whether the zoom controls are visible to the user.
333 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334 public boolean isVisible() {
335 return mIsVisible;
336 }
337
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700338 /**
339 * Sets whether the zoom controls should be visible to the user.
The Android Open Source Project10592532009-03-18 17:39:46 -0700340 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700341 * @param visible Whether the zoom controls should be visible to the user.
342 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 public void setVisible(boolean visible) {
344
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 if (visible) {
346 if (mOwnerView.getWindowToken() == null) {
347 /*
348 * We need a window token to show ourselves, maybe the owner's
349 * window hasn't been created yet but it will have been by the
350 * time the looper is idle, so post the setVisible(true) call.
351 */
352 if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) {
353 mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE);
354 }
355 return;
356 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700357
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
359 }
360
361 if (mIsVisible == visible) {
362 return;
363 }
364 mIsVisible = visible;
365
366 if (visible) {
367 if (mContainerLayoutParams.token == null) {
368 mContainerLayoutParams.token = mOwnerView.getWindowToken();
369 }
370
371 mWindowManager.addView(mContainer, mContainerLayoutParams);
372
373 if (mPostedVisibleInitializer == null) {
374 mPostedVisibleInitializer = new Runnable() {
375 public void run() {
376 refreshPositioningVariables();
377
378 if (mCallback != null) {
379 mCallback.onVisibilityChanged(true);
380 }
381 }
382 };
383 }
384
385 mHandler.post(mPostedVisibleInitializer);
386
387 // Handle configuration changes when visible
388 mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
389
390 // Steal touches events from the owner
391 mOwnerView.setOnTouchListener(this);
392 mReleaseTouchListenerOnUp = false;
393
394 } else {
395 // Don't want to steal any more touches
396 if (mTouchTargetView != null) {
397 // We are still stealing the touch events for this touch
398 // sequence, so release the touch listener later
399 mReleaseTouchListenerOnUp = true;
400 } else {
401 mOwnerView.setOnTouchListener(null);
402 }
403
404 // No longer care about configuration changes
405 mContext.unregisterReceiver(mConfigurationChangedReceiver);
406
407 mWindowManager.removeView(mContainer);
408 mHandler.removeCallbacks(mPostedVisibleInitializer);
409
410 if (mCallback != null) {
411 mCallback.onVisibilityChanged(false);
412 }
413 }
414
415 }
416
417 /**
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700418 * Gets the container that is the parent of the zoom controls.
419 * <p>
420 * The client can add other views to this container to link them with the
421 * zoom controls.
The Android Open Source Project10592532009-03-18 17:39:46 -0700422 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700423 * @return The container of the zoom controls. It will be a layout that
424 * respects the gravity of a child's layout parameters.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 */
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700426 public ViewGroup getContainer() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 return mContainer;
428 }
429
The Android Open Source Project10592532009-03-18 17:39:46 -0700430 /**
431 * Gets the view for the zoom controls.
432 *
433 * @return The zoom controls view.
434 */
435 public View getZoomControls() {
436 return mControls;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 }
438
The Android Open Source Project10592532009-03-18 17:39:46 -0700439 private void dismissControlsDelayed(int delay) {
440 if (mAutoDismissControls) {
441 mHandler.removeMessages(MSG_DISMISS_ZOOM_CONTROLS);
442 mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_CONTROLS, delay);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444 }
445
446 private void refreshPositioningVariables() {
Owen Lin470681e2009-05-27 17:44:57 -0700447 // if the mOwnerView is detached from window then skip.
448 if (mOwnerView.getWindowToken() == null) return;
449
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700450 // Position the zoom controls on the bottom of the owner view.
451 int ownerHeight = mOwnerView.getHeight();
452 int ownerWidth = mOwnerView.getWidth();
453 // The gap between the top of the owner and the top of the container
454 int containerOwnerYOffset = ownerHeight - mContainer.getHeight();
455
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 // Calculate the owner view's bounds
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700457 mOwnerView.getLocationOnScreen(mOwnerViewRawLocation);
458 mContainerRawLocation[0] = mOwnerViewRawLocation[0];
459 mContainerRawLocation[1] = mOwnerViewRawLocation[1] + containerOwnerYOffset;
The Android Open Source Project10592532009-03-18 17:39:46 -0700460
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700461 int[] ownerViewWindowLoc = mTempIntArray;
462 mOwnerView.getLocationInWindow(ownerViewWindowLoc);
463
464 // lp.x and lp.y should be relative to the owner's window top-left
465 mContainerLayoutParams.x = ownerViewWindowLoc[0];
466 mContainerLayoutParams.width = ownerWidth;
467 mContainerLayoutParams.y = ownerViewWindowLoc[1] + containerOwnerYOffset;
468 if (mIsVisible) {
469 mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
470 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 }
473
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700474 /* This will only be called when the container has focus. */
475 private boolean onContainerKey(KeyEvent event) {
476 int keyCode = event.getKeyCode();
477 if (isInterestingKey(keyCode)) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700478
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700479 if (keyCode == KeyEvent.KEYCODE_BACK) {
Dianne Hackborn8d374262009-09-14 21:21:52 -0700480 if (event.getAction() == KeyEvent.ACTION_DOWN
481 && event.getRepeatCount() == 0) {
482 if (mOwnerView != null) {
483 KeyEvent.DispatcherState ds = mOwnerView.getKeyDispatcherState();
484 if (ds != null) {
485 ds.startTracking(event, this);
486 }
487 }
488 return true;
489 } else if (event.getAction() == KeyEvent.ACTION_UP
490 && event.isTracking() && !event.isCanceled()) {
491 setVisible(false);
492 return true;
493 }
494
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700495 } else {
496 dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
497 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700498
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700499 // Let the container handle the key
500 return false;
The Android Open Source Project10592532009-03-18 17:39:46 -0700501
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700502 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700503
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700504 ViewRoot viewRoot = getOwnerViewRoot();
505 if (viewRoot != null) {
506 viewRoot.dispatchKey(event);
507 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700508
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700509 // We gave the key to the owner, don't let the container handle this key
510 return true;
511 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700512 }
513
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700514 private boolean isInterestingKey(int keyCode) {
515 switch (keyCode) {
516 case KeyEvent.KEYCODE_DPAD_CENTER:
517 case KeyEvent.KEYCODE_DPAD_UP:
518 case KeyEvent.KEYCODE_DPAD_DOWN:
519 case KeyEvent.KEYCODE_DPAD_LEFT:
520 case KeyEvent.KEYCODE_DPAD_RIGHT:
521 case KeyEvent.KEYCODE_ENTER:
522 case KeyEvent.KEYCODE_BACK:
523 return true;
524 default:
525 return false;
526 }
527 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700528
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700529 private ViewRoot getOwnerViewRoot() {
530 View rootViewOfOwner = mOwnerView.getRootView();
531 if (rootViewOfOwner == null) {
532 return null;
533 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700534
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700535 ViewParent parentOfRootView = rootViewOfOwner.getParent();
536 if (parentOfRootView instanceof ViewRoot) {
537 return (ViewRoot) parentOfRootView;
538 } else {
539 return null;
540 }
541 }
542
543 /**
544 * @hide The ZoomButtonsController implements the OnTouchListener, but this
545 * does not need to be shown in its public API.
546 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800547 public boolean onTouch(View v, MotionEvent event) {
548 int action = event.getAction();
549
Grace Klobad4d1d6e2010-01-18 10:43:59 -0800550 if (event.getPointerCount() > 1) {
551 // ZoomButtonsController doesn't handle mutitouch. Give up control.
552 return false;
553 }
554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 if (mReleaseTouchListenerOnUp) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700556 // The controls were dismissed but we need to throw away all events until the up
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
558 mOwnerView.setOnTouchListener(null);
559 setTouchTargetView(null);
560 mReleaseTouchListenerOnUp = false;
561 }
562
563 // Eat this event
564 return true;
565 }
566
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700568
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 View targetView = mTouchTargetView;
570
571 switch (action) {
572 case MotionEvent.ACTION_DOWN:
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700573 targetView = findViewForTouch((int) event.getRawX(), (int) event.getRawY());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 setTouchTargetView(targetView);
575 break;
576
577 case MotionEvent.ACTION_UP:
578 case MotionEvent.ACTION_CANCEL:
579 setTouchTargetView(null);
580 break;
581 }
582
583 if (targetView != null) {
584 // The upperleft corner of the target view in raw coordinates
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700585 int targetViewRawX = mContainerRawLocation[0] + mTouchTargetWindowLocation[0];
586 int targetViewRawY = mContainerRawLocation[1] + mTouchTargetWindowLocation[1];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800587
588 MotionEvent containerEvent = MotionEvent.obtain(event);
589 // Convert the motion event into the target view's coordinates (from
590 // owner view's coordinates)
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700591 containerEvent.offsetLocation(mOwnerViewRawLocation[0] - targetViewRawX,
592 mOwnerViewRawLocation[1] - targetViewRawY);
593 /* Disallow negative coordinates (which can occur due to
594 * ZOOM_CONTROLS_TOUCH_PADDING) */
The Android Open Source Project10592532009-03-18 17:39:46 -0700595 // These are floats because we need to potentially offset away this exact amount
596 float containerX = containerEvent.getX();
597 float containerY = containerEvent.getY();
598 if (containerX < 0 && containerX > -ZOOM_CONTROLS_TOUCH_PADDING) {
599 containerEvent.offsetLocation(-containerX, 0);
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700600 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700601 if (containerY < 0 && containerY > -ZOOM_CONTROLS_TOUCH_PADDING) {
602 containerEvent.offsetLocation(0, -containerY);
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700603 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 boolean retValue = targetView.dispatchTouchEvent(containerEvent);
605 containerEvent.recycle();
The Android Open Source Project10592532009-03-18 17:39:46 -0700606 return retValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607
608 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700609 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610 }
611 }
612
613 private void setTouchTargetView(View view) {
614 mTouchTargetView = view;
615 if (view != null) {
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700616 view.getLocationInWindow(mTouchTargetWindowLocation);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617 }
618 }
619
620 /**
621 * Returns the View that should receive a touch at the given coordinates.
622 *
623 * @param rawX The raw X.
624 * @param rawY The raw Y.
625 * @return The view that should receive the touches, or null if there is not one.
626 */
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700627 private View findViewForTouch(int rawX, int rawY) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 // Reverse order so the child drawn on top gets first dibs.
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700629 int containerCoordsX = rawX - mContainerRawLocation[0];
630 int containerCoordsY = rawY - mContainerRawLocation[1];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 Rect frame = mTempRect;
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700632
633 View closestChild = null;
634 int closestChildDistanceSq = Integer.MAX_VALUE;
The Android Open Source Project10592532009-03-18 17:39:46 -0700635
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636 for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
637 View child = mContainer.getChildAt(i);
638 if (child.getVisibility() != View.VISIBLE) {
639 continue;
640 }
641
642 child.getHitRect(frame);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 if (frame.contains(containerCoordsX, containerCoordsY)) {
644 return child;
645 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700646
647 int distanceX;
648 if (containerCoordsX >= frame.left && containerCoordsX <= frame.right) {
649 distanceX = 0;
650 } else {
651 distanceX = Math.min(Math.abs(frame.left - containerCoordsX),
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700652 Math.abs(containerCoordsX - frame.right));
The Android Open Source Project10592532009-03-18 17:39:46 -0700653 }
654 int distanceY;
655 if (containerCoordsY >= frame.top && containerCoordsY <= frame.bottom) {
656 distanceY = 0;
657 } else {
658 distanceY = Math.min(Math.abs(frame.top - containerCoordsY),
659 Math.abs(containerCoordsY - frame.bottom));
660 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700661 int distanceSq = distanceX * distanceX + distanceY * distanceY;
The Android Open Source Project10592532009-03-18 17:39:46 -0700662
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700663 if ((distanceSq < mTouchPaddingScaledSq) &&
664 (distanceSq < closestChildDistanceSq)) {
665 closestChild = child;
666 closestChildDistanceSq = distanceSq;
667 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668 }
669
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700670 return closestChild;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671 }
672
673 private void onPostConfigurationChanged() {
674 dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
675 refreshPositioningVariables();
676 }
677
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700678 /**
679 * Interface that will be called when the user performs an interaction that
680 * triggers some action, for example zooming.
681 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682 public interface OnZoomListener {
The Android Open Source Project10592532009-03-18 17:39:46 -0700683
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700684 /**
685 * Called when the zoom controls' visibility changes.
The Android Open Source Project10592532009-03-18 17:39:46 -0700686 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700687 * @param visible Whether the zoom controls are visible.
688 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 void onVisibilityChanged(boolean visible);
The Android Open Source Project10592532009-03-18 17:39:46 -0700690
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700691 /**
692 * Called when the owner view needs to be zoomed.
The Android Open Source Project10592532009-03-18 17:39:46 -0700693 *
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700694 * @param zoomIn The direction of the zoom: true to zoom in, false to zoom out.
695 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 void onZoom(boolean zoomIn);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700698
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700699 private class Container extends FrameLayout {
700 public Container(Context context) {
701 super(context);
702 }
703
704 /*
705 * Need to override this to intercept the key events. Otherwise, we
706 * would attach a key listener to the container but its superclass
707 * ViewGroup gives it to the focused View instead of calling the key
708 * listener, and so we wouldn't get the events.
709 */
710 @Override
711 public boolean dispatchKeyEvent(KeyEvent event) {
712 return onContainerKey(event) ? true : super.dispatchKeyEvent(event);
713 }
714 }
715
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716}