FreeRDP
Loading...
Searching...
No Matches
SessionView.java
1/*
2 Android Session 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
11package com.freerdp.freerdpcore.presentation;
12
13import android.content.Context;
14import android.graphics.Bitmap;
15import android.graphics.Canvas;
16import android.graphics.Color;
17import android.graphics.Matrix;
18import android.graphics.Rect;
19import android.graphics.RectF;
20import android.graphics.drawable.BitmapDrawable;
21import android.text.InputType;
22import android.util.AttributeSet;
23import android.util.Log;
24import android.view.KeyEvent;
25import android.view.MotionEvent;
26import android.view.ScaleGestureDetector;
27import android.view.View;
28import android.view.inputmethod.EditorInfo;
29import android.view.inputmethod.InputConnection;
30
31import androidx.annotation.NonNull;
32
33import com.freerdp.freerdpcore.application.SessionState;
34import com.freerdp.freerdpcore.services.LibFreeRDP;
35import com.freerdp.freerdpcore.utils.DoubleGestureDetector;
36import com.freerdp.freerdpcore.utils.GestureDetector;
37import com.freerdp.freerdpcore.utils.Mouse;
38
39import java.util.Stack;
40
41public class SessionView extends View
42{
43 public static final float MAX_SCALE_FACTOR = 3.0f;
44 public static final float MIN_SCALE_FACTOR = 1.0f;
45 private static final String TAG = "SessionView";
46 private static final float SCALE_FACTOR_DELTA = 0.0001f;
47 private static final float TOUCH_SCROLL_DELTA = 10.0f;
48 private int width;
49 private int height;
50 private BitmapDrawable surface;
51 private Stack<Rect> invalidRegions;
52 private int touchPointerPaddingWidth = 0;
53 private int touchPointerPaddingHeight = 0;
54 private SessionViewListener sessionViewListener = null;
55 // helpers for scaling gesture handling
56 private float scaleFactor = 1.0f;
57 private Matrix scaleMatrix;
58 private Matrix invScaleMatrix;
59 private RectF invalidRegionF;
60 private GestureDetector gestureDetector;
61 private SessionState currentSession;
62
63 // private static final String TAG = "FreeRDP.SessionView";
64 private DoubleGestureDetector doubleGestureDetector;
65 public SessionView(Context context)
66 {
67 super(context);
68 initSessionView(context);
69 }
70
71 public SessionView(Context context, AttributeSet attrs)
72 {
73 super(context, attrs);
74 initSessionView(context);
75 }
76
77 public SessionView(Context context, AttributeSet attrs, int defStyle)
78 {
79 super(context, attrs, defStyle);
80 initSessionView(context);
81 }
82
83 private void initSessionView(Context context)
84 {
85 invalidRegions = new Stack<>();
86 gestureDetector = new GestureDetector(context, new SessionGestureListener(), null, true);
87 doubleGestureDetector =
88 new DoubleGestureDetector(context, null, new SessionDoubleGestureListener());
89
90 scaleFactor = 1.0f;
91 scaleMatrix = new Matrix();
92 invScaleMatrix = new Matrix();
93 invalidRegionF = new RectF();
94
95 setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
96 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
97 }
98
99 /* External Mouse Hover */
100 @Override public boolean onHoverEvent(MotionEvent event)
101 {
102 if (event.getAction() == MotionEvent.ACTION_HOVER_MOVE)
103 {
104 // Handle hover move event
105 float x = event.getX();
106 float y = event.getY();
107 // Perform actions based on the hover position (x, y)
108 MotionEvent mappedEvent = mapTouchEvent(event);
109 LibFreeRDP.sendCursorEvent(currentSession.getInstance(), (int)mappedEvent.getX(),
110 (int)mappedEvent.getY(), Mouse.getMoveEvent());
111 }
112 // Return true to indicate that you've handled the event
113 return true;
114 }
115
116 public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector)
117 {
118 doubleGestureDetector.setScaleGestureDetector(scaleGestureDetector);
119 }
120
121 public void setSessionViewListener(SessionViewListener sessionViewListener)
122 {
123 this.sessionViewListener = sessionViewListener;
124 }
125
126 public void addInvalidRegion(Rect invalidRegion)
127 {
128 // correctly transform invalid region depending on current scaling
129 invalidRegionF.set(invalidRegion);
130 scaleMatrix.mapRect(invalidRegionF);
131 invalidRegionF.roundOut(invalidRegion);
132
133 invalidRegions.add(invalidRegion);
134 }
135
136 public void invalidateRegion()
137 {
138 invalidate(invalidRegions.pop());
139 }
140
141 public void onSurfaceChange(SessionState session)
142 {
143 surface = session.getSurface();
144 Bitmap bitmap = surface.getBitmap();
145 width = bitmap.getWidth();
146 height = bitmap.getHeight();
147 surface.setBounds(0, 0, width, height);
148
149 setMinimumWidth(width);
150 setMinimumHeight(height);
151
152 requestLayout();
153 currentSession = session;
154 }
155
156 public float getZoom()
157 {
158 return scaleFactor;
159 }
160
161 public void setZoom(float factor)
162 {
163 // calc scale matrix and inverse scale matrix (to correctly transform the view and moues
164 // coordinates)
165 scaleFactor = factor;
166 scaleMatrix.setScale(scaleFactor, scaleFactor);
167 invScaleMatrix.setScale(1.0f / scaleFactor, 1.0f / scaleFactor);
168
169 // update layout
170 requestLayout();
171 }
172
173 public boolean isAtMaxZoom()
174 {
175 return (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA));
176 }
177
178 public boolean isAtMinZoom()
179 {
180 return (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA));
181 }
182
183 public boolean zoomIn(float factor)
184 {
185 boolean res = true;
186 scaleFactor += factor;
187 if (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA))
188 {
189 scaleFactor = MAX_SCALE_FACTOR;
190 res = false;
191 }
192 setZoom(scaleFactor);
193 return res;
194 }
195
196 public boolean zoomOut(float factor)
197 {
198 boolean res = true;
199 scaleFactor -= factor;
200 if (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA))
201 {
202 scaleFactor = MIN_SCALE_FACTOR;
203 res = false;
204 }
205 setZoom(scaleFactor);
206 return res;
207 }
208
209 public void setTouchPointerPadding(int width, int height)
210 {
211 touchPointerPaddingWidth = width;
212 touchPointerPaddingHeight = height;
213 requestLayout();
214 }
215
216 public int getTouchPointerPaddingWidth()
217 {
218 return touchPointerPaddingWidth;
219 }
220
221 public int getTouchPointerPaddingHeight()
222 {
223 return touchPointerPaddingHeight;
224 }
225
226 @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
227 {
228 Log.v(TAG, width + "x" + height);
229 this.setMeasuredDimension((int)(width * scaleFactor) + touchPointerPaddingWidth,
230 (int)(height * scaleFactor) + touchPointerPaddingHeight);
231 }
232
233 @Override public void onDraw(@NonNull Canvas canvas)
234 {
235 super.onDraw(canvas);
236
237 canvas.save();
238 canvas.concat(scaleMatrix);
239 canvas.drawColor(Color.BLACK);
240 if (surface != null)
241 {
242 surface.draw(canvas);
243 }
244 canvas.restore();
245 }
246
247 // dirty hack: we call back to our activity and call onBackPressed as this doesn't reach us when
248 // the soft keyboard is shown ...
249 @Override public boolean dispatchKeyEventPreIme(KeyEvent event)
250 {
251 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK &&
252 event.getAction() == KeyEvent.ACTION_DOWN)
253 ((SessionActivity)this.getContext()).onBackPressed();
254 return super.dispatchKeyEventPreIme(event);
255 }
256
257 // perform mapping on the touch event's coordinates according to the current scaling
258 private MotionEvent mapTouchEvent(MotionEvent event)
259 {
260 MotionEvent mappedEvent = MotionEvent.obtain(event);
261 float[] coordinates = { mappedEvent.getX(), mappedEvent.getY() };
262 invScaleMatrix.mapPoints(coordinates);
263 mappedEvent.setLocation(coordinates[0], coordinates[1]);
264 return mappedEvent;
265 }
266
267 // perform mapping on the double touch event's coordinates according to the current scaling
268 private MotionEvent mapDoubleTouchEvent(MotionEvent event)
269 {
270 MotionEvent mappedEvent = MotionEvent.obtain(event);
271 float[] coordinates = { (mappedEvent.getX(0) + mappedEvent.getX(1)) / 2,
272 (mappedEvent.getY(0) + mappedEvent.getY(1)) / 2 };
273 invScaleMatrix.mapPoints(coordinates);
274 mappedEvent.setLocation(coordinates[0], coordinates[1]);
275 return mappedEvent;
276 }
277
278 @Override public boolean onTouchEvent(MotionEvent event)
279 {
280 boolean res = gestureDetector.onTouchEvent(event);
281 res |= doubleGestureDetector.onTouchEvent(event);
282 return res;
283 }
284
285 public interface SessionViewListener {
286 void onSessionViewBeginTouch();
287
288 void onSessionViewEndTouch();
289
290 void onSessionViewLeftTouch(int x, int y, boolean down);
291
292 void onSessionViewRightTouch(int x, int y, boolean down);
293
294 void onSessionViewMove(int x, int y);
295
296 void onSessionViewScroll(boolean down);
297 }
298
299 private class SessionGestureListener extends GestureDetector.SimpleOnGestureListener
300 {
301 boolean longPressInProgress = false;
302
303 public boolean onDown(MotionEvent e)
304 {
305 return true;
306 }
307
308 public boolean onUp(MotionEvent e)
309 {
310 sessionViewListener.onSessionViewEndTouch();
311 return true;
312 }
313
314 public void onLongPress(MotionEvent e)
315 {
316 MotionEvent mappedEvent = mapTouchEvent(e);
317 sessionViewListener.onSessionViewBeginTouch();
318 sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
319 (int)mappedEvent.getY(), true);
320 longPressInProgress = true;
321 }
322
323 public void onLongPressUp(MotionEvent e)
324 {
325 MotionEvent mappedEvent = mapTouchEvent(e);
326 sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
327 (int)mappedEvent.getY(), false);
328 longPressInProgress = false;
329 sessionViewListener.onSessionViewEndTouch();
330 }
331
332 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
333 {
334 if (longPressInProgress)
335 {
336 MotionEvent mappedEvent = mapTouchEvent(e2);
337 sessionViewListener.onSessionViewMove((int)mappedEvent.getX(),
338 (int)mappedEvent.getY());
339 return true;
340 }
341
342 return false;
343 }
344
345 public boolean onDoubleTap(MotionEvent e)
346 {
347 // send 2nd click for double click
348 MotionEvent mappedEvent = mapTouchEvent(e);
349 sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
350 (int)mappedEvent.getY(), true);
351 sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
352 (int)mappedEvent.getY(), false);
353 return true;
354 }
355
356 public boolean onSingleTapUp(MotionEvent e)
357 {
358 // send single click
359 MotionEvent mappedEvent = mapTouchEvent(e);
360 sessionViewListener.onSessionViewBeginTouch();
361 switch (e.getButtonState())
362 {
363 case MotionEvent.BUTTON_PRIMARY:
364 sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
365 (int)mappedEvent.getY(), true);
366 sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
367 (int)mappedEvent.getY(), false);
368 break;
369 case MotionEvent.BUTTON_SECONDARY:
370 sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(),
371 (int)mappedEvent.getY(), true);
372 sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(),
373 (int)mappedEvent.getY(), false);
374 sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
375 (int)mappedEvent.getY(), true);
376 sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
377 (int)mappedEvent.getY(), false);
378 break;
379 }
380 sessionViewListener.onSessionViewEndTouch();
381 return true;
382 }
383 }
384
385 private class SessionDoubleGestureListener
386 implements DoubleGestureDetector.OnDoubleGestureListener
387 {
388 private MotionEvent prevEvent = null;
389
390 public boolean onDoubleTouchDown(MotionEvent e)
391 {
392 sessionViewListener.onSessionViewBeginTouch();
393 prevEvent = MotionEvent.obtain(e);
394 return true;
395 }
396
397 public boolean onDoubleTouchUp(MotionEvent e)
398 {
399 if (prevEvent != null)
400 {
401 prevEvent.recycle();
402 prevEvent = null;
403 }
404 sessionViewListener.onSessionViewEndTouch();
405 return true;
406 }
407
408 public boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2)
409 {
410 // calc if user scrolled up or down (or if any scrolling happened at all)
411 float deltaY = e2.getY() - prevEvent.getY();
412 if (deltaY > TOUCH_SCROLL_DELTA)
413 {
414 sessionViewListener.onSessionViewScroll(true);
415 prevEvent.recycle();
416 prevEvent = MotionEvent.obtain(e2);
417 }
418 else if (deltaY < -TOUCH_SCROLL_DELTA)
419 {
420 sessionViewListener.onSessionViewScroll(false);
421 prevEvent.recycle();
422 prevEvent = MotionEvent.obtain(e2);
423 }
424 return true;
425 }
426
427 public boolean onDoubleTouchSingleTap(MotionEvent e)
428 {
429 // send single click
430 MotionEvent mappedEvent = mapDoubleTouchEvent(e);
431 sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(),
432 (int)mappedEvent.getY(), true);
433 sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(),
434 (int)mappedEvent.getY(), false);
435 return true;
436 }
437 }
438
439 @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs)
440 {
441 super.onCreateInputConnection(outAttrs);
442 outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
443 return null;
444 }
445}