FreeRDP
TouchPointerView.java
1 /*
2  Android Touch Pointer view
3 
4  Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5 
6  This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7  If a copy of the MPL was not distributed with this file, You can obtain one at
8  http://mozilla.org/MPL/2.0/.
9 */
10 
11 package com.freerdp.freerdpcore.presentation;
12 
13 import android.content.Context;
14 import android.graphics.Matrix;
15 import android.graphics.RectF;
16 import android.os.Handler;
17 import android.os.Message;
18 import android.util.AttributeSet;
19 import android.view.MotionEvent;
20 import android.widget.ImageView;
21 
22 import com.freerdp.freerdpcore.R;
23 import com.freerdp.freerdpcore.utils.GestureDetector;
24 
25 public class TouchPointerView extends ImageView
26 {
27 
28  private static final int POINTER_ACTION_CURSOR = 0;
29  private static final int POINTER_ACTION_CLOSE = 3;
30 
31  // the touch pointer consists of 9 quadrants with the following functionality:
32  //
33  // -------------
34  // | 0 | 1 | 2 |
35  // -------------
36  // | 3 | 4 | 5 |
37  // -------------
38  // | 6 | 7 | 8 |
39  // -------------
40  //
41  // 0 ... contains the actual pointer (the tip must be centered in the quadrant)
42  // 1 ... is left empty
43  // 2, 3, 5, 6, 7, 8 ... function quadrants that issue a callback
44  // 4 ... pointer center used for left clicks and to drag the pointer
45  private static final int POINTER_ACTION_RCLICK = 2;
46  private static final int POINTER_ACTION_LCLICK = 4;
47  private static final int POINTER_ACTION_MOVE = 4;
48  private static final int POINTER_ACTION_SCROLL = 5;
49  private static final int POINTER_ACTION_RESET = 6;
50  private static final int POINTER_ACTION_KEYBOARD = 7;
51  private static final int POINTER_ACTION_EXTKEYBOARD = 8;
52  private static final float SCROLL_DELTA = 10.0f;
53  private static final int DEFAULT_TOUCH_POINTER_RESTORE_DELAY = 150;
54  private RectF pointerRect;
55  private RectF pointerAreaRects[] = new RectF[9];
56  private Matrix translationMatrix;
57  private boolean pointerMoving = false;
58  private boolean pointerScrolling = false;
59  private TouchPointerListener listener = null;
60  private UIHandler uiHandler = new UIHandler();
61  // gesture detection
62  private GestureDetector gestureDetector;
63  public TouchPointerView(Context context)
64  {
65  super(context);
66  initTouchPointer(context);
67  }
68 
69  public TouchPointerView(Context context, AttributeSet attrs)
70  {
71  super(context, attrs);
72  initTouchPointer(context);
73  }
74 
75  public TouchPointerView(Context context, AttributeSet attrs, int defStyle)
76  {
77  super(context, attrs, defStyle);
78  initTouchPointer(context);
79  }
80 
81  private void initTouchPointer(Context context)
82  {
83  gestureDetector =
84  new GestureDetector(context, new TouchPointerGestureListener(), null, true);
85  gestureDetector.setLongPressTimeout(500);
86  translationMatrix = new Matrix();
87  setScaleType(ScaleType.MATRIX);
88  setImageMatrix(translationMatrix);
89 
90  // init rects
91  final float rectSizeWidth = (float)getDrawable().getIntrinsicWidth() / 3.0f;
92  final float rectSizeHeight = (float)getDrawable().getIntrinsicWidth() / 3.0f;
93  for (int i = 0; i < 3; i++)
94  {
95  for (int j = 0; j < 3; j++)
96  {
97  int left = (int)(j * rectSizeWidth);
98  int top = (int)(i * rectSizeHeight);
99  int right = left + (int)rectSizeWidth;
100  int bottom = top + (int)rectSizeHeight;
101  pointerAreaRects[i * 3 + j] = new RectF(left, top, right, bottom);
102  }
103  }
104  pointerRect =
105  new RectF(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());
106  }
107 
108  public void setTouchPointerListener(TouchPointerListener listener)
109  {
110  this.listener = listener;
111  }
112 
113  public int getPointerWidth()
114  {
115  return getDrawable().getIntrinsicWidth();
116  }
117 
118  public int getPointerHeight()
119  {
120  return getDrawable().getIntrinsicHeight();
121  }
122 
123  public float[] getPointerPosition()
124  {
125  float[] curPos = new float[2];
126  translationMatrix.mapPoints(curPos);
127  return curPos;
128  }
129 
130  private void movePointer(float deltaX, float deltaY)
131  {
132  translationMatrix.postTranslate(deltaX, deltaY);
133  setImageMatrix(translationMatrix);
134  }
135 
136  private void ensureVisibility(int screen_width, int screen_height)
137  {
138  float[] curPos = new float[2];
139  translationMatrix.mapPoints(curPos);
140 
141  if (curPos[0] > (screen_width - pointerRect.width()))
142  curPos[0] = screen_width - pointerRect.width();
143  if (curPos[0] < 0)
144  curPos[0] = 0;
145  if (curPos[1] > (screen_height - pointerRect.height()))
146  curPos[1] = screen_height - pointerRect.height();
147  if (curPos[1] < 0)
148  curPos[1] = 0;
149 
150  translationMatrix.setTranslate(curPos[0], curPos[1]);
151  setImageMatrix(translationMatrix);
152  }
153 
154  private void displayPointerImageAction(int resId)
155  {
156  setPointerImage(resId);
157  uiHandler.sendEmptyMessageDelayed(0, DEFAULT_TOUCH_POINTER_RESTORE_DELAY);
158  }
159 
160  private void setPointerImage(int resId)
161  {
162  setImageResource(resId);
163  }
164 
165  // returns the pointer area with the current translation matrix applied
166  private RectF getCurrentPointerArea(int area)
167  {
168  RectF transRect = new RectF(pointerAreaRects[area]);
169  translationMatrix.mapRect(transRect);
170  return transRect;
171  }
172 
173  private boolean pointerAreaTouched(MotionEvent event, int area)
174  {
175  RectF transRect = new RectF(pointerAreaRects[area]);
176  translationMatrix.mapRect(transRect);
177  if (transRect.contains(event.getX(), event.getY()))
178  return true;
179  return false;
180  }
181 
182  private boolean pointerTouched(MotionEvent event)
183  {
184  RectF transRect = new RectF(pointerRect);
185  translationMatrix.mapRect(transRect);
186  if (transRect.contains(event.getX(), event.getY()))
187  return true;
188  return false;
189  }
190 
191  @Override public boolean onTouchEvent(MotionEvent event)
192  {
193  // check if pointer is being moved or if we are in scroll mode or if the pointer is touched
194  if (!pointerMoving && !pointerScrolling && !pointerTouched(event))
195  return false;
196  return gestureDetector.onTouchEvent(event);
197  }
198 
199  @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom)
200  {
201  // ensure touch pointer is visible
202  if (changed)
203  ensureVisibility(right - left, bottom - top);
204  }
205 
206  // touch pointer listener - is triggered if an action field is
207  public interface TouchPointerListener {
208  abstract void onTouchPointerClose();
209 
210  abstract void onTouchPointerLeftClick(int x, int y, boolean down);
211 
212  abstract void onTouchPointerRightClick(int x, int y, boolean down);
213 
214  abstract void onTouchPointerMove(int x, int y);
215 
216  abstract void onTouchPointerScroll(boolean down);
217 
218  abstract void onTouchPointerToggleKeyboard();
219 
220  abstract void onTouchPointerToggleExtKeyboard();
221 
222  abstract void onTouchPointerResetScrollZoom();
223  }
224 
225  private class UIHandler extends Handler
226  {
227 
228  UIHandler()
229  {
230  super();
231  }
232 
233  @Override public void handleMessage(Message msg)
234  {
235  setPointerImage(R.drawable.touch_pointer_default);
236  }
237  }
238 
239  private class TouchPointerGestureListener extends GestureDetector.SimpleOnGestureListener
240  {
241 
242  private MotionEvent prevEvent = null;
243 
244  public boolean onDown(MotionEvent e)
245  {
246  if (pointerAreaTouched(e, POINTER_ACTION_MOVE))
247  {
248  prevEvent = MotionEvent.obtain(e);
249  pointerMoving = true;
250  }
251  else if (pointerAreaTouched(e, POINTER_ACTION_SCROLL))
252  {
253  prevEvent = MotionEvent.obtain(e);
254  pointerScrolling = true;
255  setPointerImage(R.drawable.touch_pointer_scroll);
256  }
257 
258  return true;
259  }
260 
261  public boolean onUp(MotionEvent e)
262  {
263  if (prevEvent != null)
264  {
265  prevEvent.recycle();
266  prevEvent = null;
267  }
268 
269  if (pointerScrolling)
270  setPointerImage(R.drawable.touch_pointer_default);
271 
272  pointerMoving = false;
273  pointerScrolling = false;
274  return true;
275  }
276 
277  public void onLongPress(MotionEvent e)
278  {
279  if (pointerAreaTouched(e, POINTER_ACTION_LCLICK))
280  {
281  setPointerImage(R.drawable.touch_pointer_active);
282  pointerMoving = true;
283  RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
284  listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
285  }
286  }
287 
288  public void onLongPressUp(MotionEvent e)
289  {
290  if (pointerMoving)
291  {
292  setPointerImage(R.drawable.touch_pointer_default);
293  pointerMoving = false;
294  RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
295  listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
296  }
297  }
298 
299  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
300  {
301  if (pointerMoving)
302  {
303  // move pointer graphics
304  movePointer((int)(e2.getX() - prevEvent.getX()),
305  (int)(e2.getY() - prevEvent.getY()));
306  prevEvent.recycle();
307  prevEvent = MotionEvent.obtain(e2);
308 
309  // send move notification
310  RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
311  listener.onTouchPointerMove((int)rect.centerX(), (int)rect.centerY());
312  return true;
313  }
314  else if (pointerScrolling)
315  {
316  // calc if user scrolled up or down (or if any scrolling happened at all)
317  float deltaY = e2.getY() - prevEvent.getY();
318  if (deltaY > SCROLL_DELTA)
319  {
320  listener.onTouchPointerScroll(true);
321  prevEvent.recycle();
322  prevEvent = MotionEvent.obtain(e2);
323  }
324  else if (deltaY < -SCROLL_DELTA)
325  {
326  listener.onTouchPointerScroll(false);
327  prevEvent.recycle();
328  prevEvent = MotionEvent.obtain(e2);
329  }
330  return true;
331  }
332  return false;
333  }
334 
335  public boolean onSingleTapUp(MotionEvent e)
336  {
337  // look what area got touched and fire actions accordingly
338  if (pointerAreaTouched(e, POINTER_ACTION_CLOSE))
339  listener.onTouchPointerClose();
340  else if (pointerAreaTouched(e, POINTER_ACTION_LCLICK))
341  {
342  displayPointerImageAction(R.drawable.touch_pointer_lclick);
343  RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
344  listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
345  listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
346  }
347  else if (pointerAreaTouched(e, POINTER_ACTION_RCLICK))
348  {
349  displayPointerImageAction(R.drawable.touch_pointer_rclick);
350  RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
351  listener.onTouchPointerRightClick((int)rect.centerX(), (int)rect.centerY(), true);
352  listener.onTouchPointerRightClick((int)rect.centerX(), (int)rect.centerY(), false);
353  }
354  else if (pointerAreaTouched(e, POINTER_ACTION_KEYBOARD))
355  {
356  displayPointerImageAction(R.drawable.touch_pointer_keyboard);
357  listener.onTouchPointerToggleKeyboard();
358  }
359  else if (pointerAreaTouched(e, POINTER_ACTION_EXTKEYBOARD))
360  {
361  displayPointerImageAction(R.drawable.touch_pointer_extkeyboard);
362  listener.onTouchPointerToggleExtKeyboard();
363  }
364  else if (pointerAreaTouched(e, POINTER_ACTION_RESET))
365  {
366  displayPointerImageAction(R.drawable.touch_pointer_reset);
367  listener.onTouchPointerResetScrollZoom();
368  }
369 
370  return true;
371  }
372 
373  public boolean onDoubleTap(MotionEvent e)
374  {
375  // issue a double click notification if performed in center quadrant
376  if (pointerAreaTouched(e, POINTER_ACTION_LCLICK))
377  {
378  RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
379  listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
380  listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
381  }
382  return true;
383  }
384  }
385 }