FreeRDP
Loading...
Searching...
No Matches
SessionInputManager.java
1/*
2 Android Session Input Manager
3
4 */
5
6package com.freerdp.freerdpcore.presentation;
7
8import android.content.Context;
9import android.graphics.Bitmap;
10import android.graphics.Point;
11import android.inputmethodservice.Keyboard;
12import android.inputmethodservice.KeyboardView;
13import android.os.Handler;
14import android.os.Looper;
15import android.os.Message;
16import android.util.Log;
17import android.view.KeyEvent;
18import android.view.MotionEvent;
19import android.view.ScaleGestureDetector;
20import android.view.View;
21import android.view.inputmethod.InputMethodManager;
22
23import com.freerdp.freerdpcore.R;
24import com.freerdp.freerdpcore.services.LibFreeRDP;
25import com.freerdp.freerdpcore.utils.KeyboardMapper;
26import com.freerdp.freerdpcore.utils.Mouse;
27
28import java.util.List;
29
31 implements SessionView.SessionViewListener, TouchPointerView.TouchPointerListener,
32 KeyboardMapper.KeyProcessingListener, KeyboardView.OnKeyboardActionListener
33{
34 private static final String TAG = "FreeRDP.SessionInputManager";
35
36 private static final int SCROLLING_TIMEOUT = 50;
37 private static final int SCROLLING_DISTANCE = 20;
38 private static final int MAX_DISCARDED_MOVE_EVENTS = 3;
39 private static final int SEND_MOVE_EVENT_TIMEOUT = 150;
40
41 private static final int MSG_SEND_MOVE_EVENT = 1;
42 private static final int MSG_SCROLLING_REQUESTED = 2;
43
44 private final Context context;
45 private final KeyboardMapper keyboardMapper;
46 private final ScrollView2D scrollView;
47 private final SessionView sessionView;
48 private final TouchPointerView touchPointerView;
49 private final KeyboardView keyboardView;
50 private final KeyboardView modifiersKeyboardView;
51 private final PinchZoomListener pinchZoomListener = new PinchZoomListener();
52
53 private Keyboard modifiersKeyboard;
54 private Keyboard specialkeysKeyboard;
55 private Keyboard numpadKeyboard;
56 private Keyboard cursorKeyboard;
57
58 // Native FreeRDP instance handle. 0 until attachSession() is called (i.e. before connect).
59 private long instance = 0;
60 private Bitmap bitmap;
61 private int screenWidth;
62 private int screenHeight;
63 private int discardedMoveEvents = 0;
64
65 // keyboard visibility flags
66 private boolean sysKeyboardVisible = false;
67 private boolean extKeyboardVisible = false;
68
69 private final Handler handler;
70
71 public SessionInputManager(Context context, ScrollView2D scrollView, SessionView sessionView,
72 TouchPointerView touchPointerView, KeyboardView keyboardView,
73 KeyboardView modifiersKeyboardView)
74 {
75 this.context = context;
76 this.scrollView = scrollView;
77 this.sessionView = sessionView;
78 this.touchPointerView = touchPointerView;
79 this.keyboardView = keyboardView;
80 this.modifiersKeyboardView = modifiersKeyboardView;
81 this.handler = new InputHandler();
82
83 this.keyboardMapper = new KeyboardMapper();
84 this.keyboardMapper.init(context);
85
86 loadKeyboards();
87 keyboardView.setKeyboard(specialkeysKeyboard);
88 modifiersKeyboardView.setKeyboard(modifiersKeyboard);
89
90 keyboardView.setOnKeyboardActionListener(this);
91 modifiersKeyboardView.setOnKeyboardActionListener(this);
92 }
93
94 private void loadKeyboards()
95 {
96 Context app = context.getApplicationContext();
97 modifiersKeyboard = new Keyboard(app, R.xml.modifiers_keyboard);
98 specialkeysKeyboard = new Keyboard(app, R.xml.specialkeys_keyboard);
99 numpadKeyboard = new Keyboard(app, R.xml.numpad_keyboard);
100 cursorKeyboard = new Keyboard(app, R.xml.cursor_keyboard);
101 }
102
103 // Binds this manager to a live FreeRDP session. Until called, all input events are dropped.
104 public void attachSession(long instance, Bitmap surface)
105 {
106 this.instance = instance;
107 this.bitmap = surface;
108 keyboardMapper.reset(this);
109 }
110
111 // Called when the session bitmap is created or replaced (OnSettingsChanged / OnGraphicsResize).
112 public void setBitmap(Bitmap bitmap)
113 {
114 this.bitmap = bitmap;
115 }
116
117 // Returns a listener that can be wired into a ScaleGestureDetector for pinch-to-zoom.
118 public ScaleGestureDetector.OnScaleGestureListener getPinchZoomListener()
119 {
120 return pinchZoomListener;
121 }
122
123 // Called once the screen dimensions are known (onGlobalLayout) and on bindSession.
124 public void setScreenSize(int width, int height)
125 {
126 this.screenWidth = width;
127 this.screenHeight = height;
128 }
129
130 // Called from onConfigurationChanged when keyboard resources need to be reloaded
131 // (e.g. after orientation change).
132 public void reloadKeyboards()
133 {
134 loadKeyboards();
135 keyboardView.setKeyboard(specialkeysKeyboard);
136 modifiersKeyboardView.setKeyboard(modifiersKeyboard);
137 }
138
139 // Toggles the system soft-keyboard (and accompanying modifiers row).
140 public void toggleSystemKeyboard()
141 {
142 showKeyboard(!sysKeyboardVisible, false);
143 }
144
145 // Toggles the extended (special keys / function / numpad / cursor) keyboard.
146 public void toggleExtendedKeyboard()
147 {
148 showKeyboard(false, !extKeyboardVisible);
149 }
150
151 // Hides any visible keyboards (called from onPause and back-press handling).
152 public void hideKeyboards()
153 {
154 showKeyboard(false, false);
155 }
156
157 // True if either the system or extended keyboard is currently shown.
158 public boolean isAnyKeyboardVisible()
159 {
160 return sysKeyboardVisible || extKeyboardVisible;
161 }
162
163 // displays either the system or the extended keyboard or none of them
164 private void showKeyboard(boolean showSystemKeyboard, boolean showExtendedKeyboard)
165 {
166 if (showSystemKeyboard)
167 {
168 // hide extended keyboard
169 keyboardView.setVisibility(View.GONE);
170 // show system keyboard
171 setSoftInputState(true);
172
173 // show modifiers keyboard
174 modifiersKeyboardView.setVisibility(View.VISIBLE);
175 }
176 else if (showExtendedKeyboard)
177 {
178 // hide system keyboard
179 setSoftInputState(false);
180
181 // show extended keyboard
182 keyboardView.setKeyboard(specialkeysKeyboard);
183 keyboardView.setVisibility(View.VISIBLE);
184 modifiersKeyboardView.setVisibility(View.VISIBLE);
185 }
186 else
187 {
188 // hide both
189 setSoftInputState(false);
190 keyboardView.setVisibility(View.GONE);
191 modifiersKeyboardView.setVisibility(View.GONE);
192
193 // clear any active key modifiers
194 keyboardMapper.clearlAllModifiers();
195 }
196
197 sysKeyboardVisible = showSystemKeyboard;
198 extKeyboardVisible = showExtendedKeyboard;
199 }
200
201 private void setSoftInputState(boolean state)
202 {
203 InputMethodManager mgr =
204 (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
205
206 if (state)
207 {
208 sessionView.requestFocus();
209 mgr.showSoftInput(sessionView, InputMethodManager.SHOW_IMPLICIT);
210 }
211 else
212 {
213 mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0);
214 }
215 }
216
217 // Cancels any pending delayed-move events; called on connection failure / disconnect.
218 public void cancelPendingEvents()
219 {
220 handler.removeMessages(MSG_SEND_MOVE_EVENT);
221 }
222
223 // Forwards a physical-mouse scroll event (e.g. external mouse wheel) into the session.
224 public boolean onGenericMotionEvent(MotionEvent e)
225 {
226 if (instance == 0)
227 return false;
228 if (e.getAction() != MotionEvent.ACTION_SCROLL)
229 return false;
230
231 final float vScroll = e.getAxisValue(MotionEvent.AXIS_VSCROLL);
232 if (vScroll < 0)
233 LibFreeRDP.sendCursorEvent(instance, 0, 0, Mouse.getScrollEvent(context, false));
234 else if (vScroll > 0)
235 LibFreeRDP.sendCursorEvent(instance, 0, 0, Mouse.getScrollEvent(context, true));
236 return true;
237 }
238
239 // Forwards an Android hardware-keyboard event into the session.
240 public boolean onAndroidKeyEvent(KeyEvent event)
241 {
242 if (instance == 0)
243 return false;
244 return keyboardMapper.processAndroidKeyEvent(event);
245 }
246
247 // Handles a long-press on the BACK key by disconnecting the active session.
248 // Returns true if the event was consumed.
249 public boolean onAndroidKeyLongPress(int keyCode)
250 {
251 if (instance == 0)
252 return false;
253 if (keyCode == KeyEvent.KEYCODE_BACK)
254 {
255 LibFreeRDP.disconnect(instance);
256 return true;
257 }
258 return false;
259 }
260
261 // If the "use back as Alt+F4" preference is enabled, sends Alt+F4 and returns true.
262 public boolean handleBackAsAltF4()
263 {
264 if (instance == 0)
265 return false;
266 if (!ApplicationSettingsActivity.getUseBackAsAltf4(context))
267 return false;
268 keyboardMapper.sendAltF4();
269 return true;
270 }
271
272 // Toggles touch-pointer overlay visibility (driven by the menu).
273 public void toggleTouchPointer()
274 {
275 if (touchPointerView.getVisibility() == View.VISIBLE)
276 {
277 touchPointerView.setVisibility(View.INVISIBLE);
278 sessionView.setTouchPointerPadding(0, 0);
279 }
280 else
281 {
282 touchPointerView.setVisibility(View.VISIBLE);
283 sessionView.setTouchPointerPadding(touchPointerView.getPointerWidth(),
284 touchPointerView.getPointerHeight());
285 }
286 }
287
288 // ****************************************************************************
289 // Private helpers
290
291 private void sendDelayedMoveEvent(int x, int y)
292 {
293 if (handler.hasMessages(MSG_SEND_MOVE_EVENT))
294 {
295 handler.removeMessages(MSG_SEND_MOVE_EVENT);
296 discardedMoveEvents++;
297 }
298 else
299 discardedMoveEvents = 0;
300
301 if (discardedMoveEvents > MAX_DISCARDED_MOVE_EVENTS)
302 LibFreeRDP.sendCursorEvent(instance, x, y, Mouse.getMoveEvent());
303 else
304 handler.sendMessageDelayed(Message.obtain(null, MSG_SEND_MOVE_EVENT, x, y),
305 SEND_MOVE_EVENT_TIMEOUT);
306 }
307
308 private void cancelDelayedMoveEvent()
309 {
310 handler.removeMessages(MSG_SEND_MOVE_EVENT);
311 }
312
313 private Point mapScreenCoordToSessionCoord(int x, int y)
314 {
315 int mappedX = (int)((float)(x + scrollView.getScrollX()) / sessionView.getZoom());
316 int mappedY = (int)((float)(y + scrollView.getScrollY()) / sessionView.getZoom());
317 if (bitmap != null)
318 {
319 if (mappedX > bitmap.getWidth())
320 mappedX = bitmap.getWidth();
321 if (mappedY > bitmap.getHeight())
322 mappedY = bitmap.getHeight();
323 }
324 return new Point(mappedX, mappedY);
325 }
326
327 private void updateModifierKeyStates()
328 {
329 List<Keyboard.Key> keys = modifiersKeyboard.getKeys();
330 for (Keyboard.Key curKey : keys)
331 {
332 if (curKey.sticky)
333 {
334 switch (keyboardMapper.getModifierState(curKey.codes[0]))
335 {
336 case KeyboardMapper.KEYSTATE_ON:
337 curKey.on = true;
338 curKey.pressed = false;
339 break;
340
341 case KeyboardMapper.KEYSTATE_OFF:
342 curKey.on = false;
343 curKey.pressed = false;
344 break;
345
346 case KeyboardMapper.KEYSTATE_LOCKED:
347 curKey.on = true;
348 curKey.pressed = true;
349 break;
350 }
351 }
352 }
353 modifiersKeyboardView.invalidateAllKeys();
354 }
355
356 // ****************************************************************************
357 // SessionView.SessionViewListener
358
359 @Override public void onSessionViewBeginTouch()
360 {
361 scrollView.setScrollEnabled(false);
362 }
363
364 @Override public void onSessionViewEndTouch()
365 {
366 scrollView.setScrollEnabled(true);
367 }
368
369 @Override public void onSessionViewLeftTouch(int x, int y, boolean down)
370 {
371 if (instance == 0)
372 return;
373 if (!down)
374 cancelDelayedMoveEvent();
375 LibFreeRDP.sendCursorEvent(instance, x, y, Mouse.getLeftButtonEvent(context, down));
376 }
377
378 @Override public void onSessionViewMiddleTouch(int x, int y, boolean down)
379 {
380 if (instance == 0)
381 return;
382 LibFreeRDP.sendCursorEvent(instance, x, y, Mouse.getMiddleButtonEvent(down));
383 }
384
385 @Override public void onSessionViewRightTouch(int x, int y, boolean down)
386 {
387 if (instance == 0)
388 return;
389 LibFreeRDP.sendCursorEvent(instance, x, y, Mouse.getRightButtonEvent(context, down));
390 }
391
392 @Override public void onSessionViewMove(int x, int y)
393 {
394 if (instance == 0)
395 return;
396 sendDelayedMoveEvent(x, y);
397 }
398
399 @Override public void onSessionViewMouseMove(int x, int y)
400 {
401 if (instance == 0)
402 return;
403 LibFreeRDP.sendCursorEvent(instance, x, y, Mouse.getMoveEvent());
404 }
405
406 @Override public void onSessionViewScroll(boolean down)
407 {
408 if (instance == 0)
409 return;
410 LibFreeRDP.sendCursorEvent(instance, 0, 0, Mouse.getScrollEvent(context, down));
411 }
412
413 @Override public void onSessionViewHScroll(boolean right)
414 {
415 if (instance == 0)
416 return;
417 LibFreeRDP.sendCursorEvent(instance, 0, 0, Mouse.getHScrollEvent(context, right));
418 }
419
420 // ****************************************************************************
421 // TouchPointerView.TouchPointerListener
422
423 @Override public void onTouchPointerClose()
424 {
425 touchPointerView.setVisibility(View.INVISIBLE);
426 sessionView.setTouchPointerPadding(0, 0);
427 }
428
429 @Override public void onTouchPointerLeftClick(int x, int y, boolean down)
430 {
431 if (instance == 0)
432 return;
433 Point p = mapScreenCoordToSessionCoord(x, y);
434 LibFreeRDP.sendCursorEvent(instance, p.x, p.y, Mouse.getLeftButtonEvent(context, down));
435 }
436
437 @Override public void onTouchPointerRightClick(int x, int y, boolean down)
438 {
439 if (instance == 0)
440 return;
441 Point p = mapScreenCoordToSessionCoord(x, y);
442 LibFreeRDP.sendCursorEvent(instance, p.x, p.y, Mouse.getRightButtonEvent(context, down));
443 }
444
445 @Override public void onTouchPointerMove(int x, int y)
446 {
447 if (instance == 0)
448 return;
449 Point p = mapScreenCoordToSessionCoord(x, y);
450 LibFreeRDP.sendCursorEvent(instance, p.x, p.y, Mouse.getMoveEvent());
451
452 if (ApplicationSettingsActivity.getAutoScrollTouchPointer(context) &&
453 !handler.hasMessages(MSG_SCROLLING_REQUESTED))
454 {
455 Log.v(TAG, "Starting auto-scroll");
456 handler.sendEmptyMessageDelayed(MSG_SCROLLING_REQUESTED, SCROLLING_TIMEOUT);
457 }
458 }
459
460 @Override public void onTouchPointerScroll(boolean down)
461 {
462 if (instance == 0)
463 return;
464 LibFreeRDP.sendCursorEvent(instance, 0, 0, Mouse.getScrollEvent(context, down));
465 }
466
467 @Override public void onTouchPointerToggleKeyboard()
468 {
469 toggleSystemKeyboard();
470 }
471
472 @Override public void onTouchPointerToggleExtKeyboard()
473 {
474 toggleExtendedKeyboard();
475 }
476
477 @Override public void onTouchPointerResetScrollZoom()
478 {
479 sessionView.setZoom(1.0f);
480 scrollView.scrollTo(0, 0);
481 }
482
483 // ****************************************************************************
484 // KeyboardMapper.KeyProcessingListener
485
486 @Override public void processVirtualKey(int virtualKeyCode, boolean down)
487 {
488 if (instance == 0)
489 return;
490 LibFreeRDP.sendKeyEvent(instance, virtualKeyCode, down);
491 }
492
493 @Override public void processUnicodeKey(int unicodeKey)
494 {
495 if (instance == 0)
496 return;
497 LibFreeRDP.sendUnicodeKeyEvent(instance, unicodeKey, true);
498 LibFreeRDP.sendUnicodeKeyEvent(instance, unicodeKey, false);
499 }
500
501 @Override public void switchKeyboard(int keyboardType)
502 {
503 switch (keyboardType)
504 {
505 case KeyboardMapper.KEYBOARD_TYPE_FUNCTIONKEYS:
506 keyboardView.setKeyboard(specialkeysKeyboard);
507 break;
508
509 case KeyboardMapper.KEYBOARD_TYPE_NUMPAD:
510 keyboardView.setKeyboard(numpadKeyboard);
511 break;
512
513 case KeyboardMapper.KEYBOARD_TYPE_CURSOR:
514 keyboardView.setKeyboard(cursorKeyboard);
515 break;
516
517 default:
518 break;
519 }
520 }
521
522 @Override public void modifiersChanged()
523 {
524 updateModifierKeyStates();
525 }
526
527 // ****************************************************************************
528 // KeyboardView.OnKeyboardActionListener (extended/modifiers keyboards)
529
530 @Override public void onKey(int primaryCode, int[] keyCodes)
531 {
532 keyboardMapper.processCustomKeyEvent(primaryCode);
533 }
534
535 @Override public void onText(CharSequence text)
536 {
537 }
538
539 @Override public void swipeRight()
540 {
541 }
542
543 @Override public void swipeLeft()
544 {
545 }
546
547 @Override public void swipeDown()
548 {
549 }
550
551 @Override public void swipeUp()
552 {
553 }
554
555 @Override public void onPress(int primaryCode)
556 {
557 }
558
559 @Override public void onRelease(int primaryCode)
560 {
561 }
562
563 // ****************************************************************************
564 // Internal delayed-event handler
565
566 private class InputHandler extends Handler
567 {
568 InputHandler()
569 {
570 super(Looper.getMainLooper());
571 }
572
573 @Override public void handleMessage(Message msg)
574 {
575 switch (msg.what)
576 {
577 case MSG_SEND_MOVE_EVENT:
578 if (instance == 0)
579 break;
580 LibFreeRDP.sendCursorEvent(instance, msg.arg1, msg.arg2, Mouse.getMoveEvent());
581 break;
582
583 case MSG_SCROLLING_REQUESTED:
584 {
585 int scrollX = 0;
586 int scrollY = 0;
587 float[] pointerPos = touchPointerView.getPointerPosition();
588
589 if (pointerPos[0] > (screenWidth - touchPointerView.getPointerWidth()))
590 scrollX = SCROLLING_DISTANCE;
591 else if (pointerPos[0] < 0)
592 scrollX = -SCROLLING_DISTANCE;
593
594 if (pointerPos[1] > (screenHeight - touchPointerView.getPointerHeight()))
595 scrollY = SCROLLING_DISTANCE;
596 else if (pointerPos[1] < 0)
597 scrollY = -SCROLLING_DISTANCE;
598
599 scrollView.scrollBy(scrollX, scrollY);
600
601 if (scrollView.getScrollX() == 0 ||
602 scrollView.getScrollX() == (sessionView.getWidth() - scrollView.getWidth()))
603 scrollX = 0;
604 if (scrollView.getScrollY() == 0 ||
605 scrollView.getScrollY() ==
606 (sessionView.getHeight() - scrollView.getHeight()))
607 scrollY = 0;
608
609 if (scrollX != 0 || scrollY != 0)
610 handler.sendEmptyMessageDelayed(MSG_SCROLLING_REQUESTED, SCROLLING_TIMEOUT);
611 else
612 Log.v(TAG, "Stopping auto-scroll");
613 break;
614 }
615 }
616 }
617 }
618
619 // ****************************************************************************
620 // Pinch-to-zoom listener (wired into SessionView's ScaleGestureDetector)
621
622 private class PinchZoomListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
623 {
624 private float scaleFactor = 1.0f;
625
626 @Override public boolean onScaleBegin(ScaleGestureDetector detector)
627 {
628 scrollView.setScrollEnabled(false);
629 return true;
630 }
631
632 @Override public boolean onScale(ScaleGestureDetector detector)
633 {
634 // calc scale factor
635 scaleFactor *= detector.getScaleFactor();
636 scaleFactor = Math.max(SessionView.MIN_SCALE_FACTOR,
637 Math.min(scaleFactor, SessionView.MAX_SCALE_FACTOR));
638 sessionView.setZoom(scaleFactor);
639
640 if (!sessionView.isAtMinZoom() && !sessionView.isAtMaxZoom())
641 {
642 // transform scroll origin to the new zoom space
643 float transOriginX = scrollView.getScrollX() * detector.getScaleFactor();
644 float transOriginY = scrollView.getScrollY() * detector.getScaleFactor();
645
646 // transform center point to the zoomed space
647 float transCenterX =
648 (scrollView.getScrollX() + detector.getFocusX()) * detector.getScaleFactor();
649 float transCenterY =
650 (scrollView.getScrollY() + detector.getFocusY()) * detector.getScaleFactor();
651
652 // scroll by the difference between the distance of the
653 // transformed center/origin point and their old distance
654 // (focusX/Y)
655 scrollView.scrollBy((int)((transCenterX - transOriginX) - detector.getFocusX()),
656 (int)((transCenterY - transOriginY) - detector.getFocusY()));
657 }
658
659 return true;
660 }
661
662 @Override public void onScaleEnd(ScaleGestureDetector de)
663 {
664 scrollView.setScrollEnabled(true);
665 }
666 }
667}