FreeRDP
SessionActivity.java
1 /*
2  Android Session Activity
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.app.AlertDialog;
14 import android.app.Dialog;
15 import android.app.ProgressDialog;
16 import android.app.UiModeManager;
17 import android.content.BroadcastReceiver;
18 import android.content.Context;
19 import android.content.DialogInterface;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.SharedPreferences;
23 import android.content.res.Configuration;
24 import android.graphics.Bitmap;
25 import android.graphics.Bitmap.Config;
26 import android.graphics.Point;
27 import android.graphics.Rect;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.inputmethodservice.Keyboard;
30 import android.inputmethodservice.KeyboardView;
31 import android.net.Uri;
32 import android.os.Build;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Message;
36 
37 import androidx.annotation.NonNull;
38 import androidx.appcompat.app.AppCompatActivity;
39 
40 import android.text.InputType;
41 import android.util.Log;
42 import android.view.KeyEvent;
43 import android.view.Menu;
44 import android.view.MenuItem;
45 import android.view.MotionEvent;
46 import android.view.ScaleGestureDetector;
47 import android.view.View;
48 import android.view.ViewConfiguration;
49 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
50 import android.view.WindowManager;
51 import android.view.inputmethod.InputMethodManager;
52 import android.view.inputmethod.InputMethodSubtype;
53 import android.widget.EditText;
54 import android.widget.Toast;
55 import android.widget.ZoomControls;
56 
57 import com.freerdp.freerdpcore.R;
58 import com.freerdp.freerdpcore.application.GlobalApp;
59 import com.freerdp.freerdpcore.application.SessionState;
60 import com.freerdp.freerdpcore.domain.BookmarkBase;
61 import com.freerdp.freerdpcore.domain.ConnectionReference;
62 import com.freerdp.freerdpcore.domain.ManualBookmark;
63 import com.freerdp.freerdpcore.services.LibFreeRDP;
64 import com.freerdp.freerdpcore.utils.ClipboardManagerProxy;
65 import com.freerdp.freerdpcore.utils.KeyboardMapper;
66 import com.freerdp.freerdpcore.utils.Mouse;
67 
68 import java.util.Collection;
69 import java.util.Iterator;
70 import java.util.List;
71 
72 public class SessionActivity extends AppCompatActivity
73  implements LibFreeRDP.UIEventListener, KeyboardView.OnKeyboardActionListener,
74  ScrollView2D.ScrollView2DListener, KeyboardMapper.KeyProcessingListener,
75  SessionView.SessionViewListener, TouchPointerView.TouchPointerListener,
76  ClipboardManagerProxy.OnClipboardChangedListener
77 {
78  public static final String PARAM_CONNECTION_REFERENCE = "conRef";
79  public static final String PARAM_INSTANCE = "instance";
80  private static final float ZOOMING_STEP = 0.5f;
81  private static final int ZOOMCONTROLS_AUTOHIDE_TIMEOUT = 4000;
82  // timeout between subsequent scrolling requests when the touch-pointer is
83  // at the edge of the session view
84  private static final int SCROLLING_TIMEOUT = 50;
85  private static final int SCROLLING_DISTANCE = 20;
86  private static final String TAG = "FreeRDP.SessionActivity";
87  // variables for delayed move event sending
88  private static final int MAX_DISCARDED_MOVE_EVENTS = 3;
89  private static final int SEND_MOVE_EVENT_TIMEOUT = 150;
90  private Bitmap bitmap;
91  private SessionState session;
92  private SessionView sessionView;
93  private TouchPointerView touchPointerView;
94  private ProgressDialog progressDialog;
95  private KeyboardView keyboardView;
96  private KeyboardView modifiersKeyboardView;
97  private ZoomControls zoomControls;
98  private KeyboardMapper keyboardMapper;
99 
100  private Keyboard specialkeysKeyboard;
101  private Keyboard numpadKeyboard;
102  private Keyboard cursorKeyboard;
103  private Keyboard modifiersKeyboard;
104 
105  private AlertDialog dlgVerifyCertificate;
106  private AlertDialog dlgUserCredentials;
107  private View userCredView;
108 
109  private UIHandler uiHandler;
110 
111  private int screen_width;
112  private int screen_height;
113 
114  private boolean connectCancelledByUser = false;
115  private boolean sessionRunning = false;
116  private boolean toggleMouseButtons = false;
117 
118  private LibFreeRDPBroadcastReceiver libFreeRDPBroadcastReceiver;
119  private ScrollView2D scrollView;
120  // keyboard visibility flags
121  private boolean sysKeyboardVisible = false;
122  private boolean extKeyboardVisible = false;
123  private int discardedMoveEvents = 0;
124  private ClipboardManagerProxy mClipboardManager;
125  private boolean callbackDialogResult;
126  View mDecor;
127 
128  private void createDialogs()
129  {
130  // build verify certificate dialog
131  dlgVerifyCertificate =
132  new AlertDialog.Builder(this)
133  .setTitle(R.string.dlg_title_verify_certificate)
134  .setPositiveButton(android.R.string.yes,
135  new DialogInterface.OnClickListener() {
136  @Override
137  public void onClick(DialogInterface dialog, int which)
138  {
139  callbackDialogResult = true;
140  synchronized (dialog)
141  {
142  dialog.notify();
143  }
144  }
145  })
146  .setNegativeButton(android.R.string.no,
147  new DialogInterface.OnClickListener() {
148  @Override
149  public void onClick(DialogInterface dialog, int which)
150  {
151  callbackDialogResult = false;
152  connectCancelledByUser = true;
153  synchronized (dialog)
154  {
155  dialog.notify();
156  }
157  }
158  })
159  .setCancelable(false)
160  .create();
161 
162  // build the dialog
163  userCredView = getLayoutInflater().inflate(R.layout.credentials, null, true);
164  dlgUserCredentials =
165  new AlertDialog.Builder(this)
166  .setView(userCredView)
167  .setTitle(R.string.dlg_title_credentials)
168  .setPositiveButton(android.R.string.ok,
169  new DialogInterface.OnClickListener() {
170  @Override
171  public void onClick(DialogInterface dialog, int which)
172  {
173  callbackDialogResult = true;
174  synchronized (dialog)
175  {
176  dialog.notify();
177  }
178  }
179  })
180  .setNegativeButton(android.R.string.cancel,
181  new DialogInterface.OnClickListener() {
182  @Override
183  public void onClick(DialogInterface dialog, int which)
184  {
185  callbackDialogResult = false;
186  connectCancelledByUser = true;
187  synchronized (dialog)
188  {
189  dialog.notify();
190  }
191  }
192  })
193  .setCancelable(false)
194  .create();
195  }
196 
197  private boolean hasHardwareMenuButton()
198  {
199  if (Build.VERSION.SDK_INT <= 10)
200  return true;
201 
202  if (Build.VERSION.SDK_INT >= 14)
203  {
204  boolean rc = false;
205  final ViewConfiguration cfg = ViewConfiguration.get(this);
206 
207  return cfg.hasPermanentMenuKey();
208  }
209 
210  return false;
211  }
212 
213  @Override public void onCreate(Bundle savedInstanceState)
214  {
215  super.onCreate(savedInstanceState);
216 
217  // show status bar or make fullscreen?
218  if (ApplicationSettingsActivity.getHideStatusBar(this))
219  {
220  getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
221  WindowManager.LayoutParams.FLAG_FULLSCREEN);
222  }
223 
224  this.setContentView(R.layout.session);
225  if (hasHardwareMenuButton() || ApplicationSettingsActivity.getHideActionBar(this))
226  {
227  this.getSupportActionBar().hide();
228  }
229  else
230  this.getSupportActionBar().show();
231 
232  Log.v(TAG, "Session.onCreate");
233 
234  // ATTENTION: We use the onGlobalLayout notification to start our
235  // session.
236  // This is because only then we can know the exact size of our session
237  // when using fit screen
238  // accounting for any status bars etc. that Android might throws on us.
239  // A bit weird looking
240  // but this is the only way ...
241  final View activityRootView = findViewById(R.id.session_root_view);
242  activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
243  new OnGlobalLayoutListener() {
244  @Override public void onGlobalLayout()
245  {
246  screen_width = activityRootView.getWidth();
247  screen_height = activityRootView.getHeight();
248 
249  // start session
250  if (!sessionRunning && getIntent() != null)
251  {
252  processIntent(getIntent());
253  sessionRunning = true;
254  }
255  }
256  });
257 
258  sessionView = (SessionView)findViewById(R.id.sessionView);
259  sessionView.setScaleGestureDetector(
260  new ScaleGestureDetector(this, new PinchZoomListener()));
261  sessionView.setSessionViewListener(this);
262  sessionView.requestFocus();
263 
264  touchPointerView = (TouchPointerView)findViewById(R.id.touchPointerView);
265  touchPointerView.setTouchPointerListener(this);
266 
267  keyboardMapper = new KeyboardMapper();
268  keyboardMapper.init(this);
269  keyboardMapper.reset(this);
270 
271  modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard);
272  specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard);
273  numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard);
274  cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard);
275 
276  // hide keyboard below the sessionView
277  keyboardView = (KeyboardView)findViewById(R.id.extended_keyboard);
278  keyboardView.setKeyboard(specialkeysKeyboard);
279  keyboardView.setOnKeyboardActionListener(this);
280 
281  modifiersKeyboardView = (KeyboardView)findViewById(R.id.extended_keyboard_header);
282  modifiersKeyboardView.setKeyboard(modifiersKeyboard);
283  modifiersKeyboardView.setOnKeyboardActionListener(this);
284 
285  scrollView = (ScrollView2D)findViewById(R.id.sessionScrollView);
286  scrollView.setScrollViewListener(this);
287  uiHandler = new UIHandler();
288  libFreeRDPBroadcastReceiver = new LibFreeRDPBroadcastReceiver();
289 
290  zoomControls = (ZoomControls)findViewById(R.id.zoomControls);
291  zoomControls.hide();
292  zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
293  @Override public void onClick(View v)
294  {
295  resetZoomControlsAutoHideTimeout();
296  zoomControls.setIsZoomInEnabled(sessionView.zoomIn(ZOOMING_STEP));
297  zoomControls.setIsZoomOutEnabled(true);
298  }
299  });
300  zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
301  @Override public void onClick(View v)
302  {
303  resetZoomControlsAutoHideTimeout();
304  zoomControls.setIsZoomOutEnabled(sessionView.zoomOut(ZOOMING_STEP));
305  zoomControls.setIsZoomInEnabled(true);
306  }
307  });
308 
309  toggleMouseButtons = false;
310 
311  createDialogs();
312 
313  // register freerdp events broadcast receiver
314  IntentFilter filter = new IntentFilter();
315  filter.addAction(GlobalApp.ACTION_EVENT_FREERDP);
316  registerReceiver(libFreeRDPBroadcastReceiver, filter);
317 
318  mClipboardManager = ClipboardManagerProxy.getClipboardManager(this);
319  mClipboardManager.addClipboardChangedListener(this);
320 
321  mDecor = getWindow().getDecorView();
322  mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
323  View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
324  }
325 
326  @Override public void onWindowFocusChanged(boolean hasFocus)
327  {
328  super.onWindowFocusChanged(hasFocus);
329  mClipboardManager.getPrimaryClipManually();
330  }
331 
332  @Override protected void onStart()
333  {
334  super.onStart();
335  Log.v(TAG, "Session.onStart");
336  }
337 
338  @Override protected void onRestart()
339  {
340  super.onRestart();
341  Log.v(TAG, "Session.onRestart");
342  }
343 
344  @Override protected void onResume()
345  {
346  super.onResume();
347  Log.v(TAG, "Session.onResume");
348  }
349 
350  @Override protected void onPause()
351  {
352  super.onPause();
353  Log.v(TAG, "Session.onPause");
354 
355  // hide any visible keyboards
356  showKeyboard(false, false);
357  }
358 
359  @Override protected void onStop()
360  {
361  super.onStop();
362  Log.v(TAG, "Session.onStop");
363  }
364 
365  @Override protected void onDestroy()
366  {
367  if (connectThread != null)
368  {
369  connectThread.interrupt();
370  }
371  super.onDestroy();
372  Log.v(TAG, "Session.onDestroy");
373 
374  // Cancel running disconnect timers.
375  GlobalApp.cancelDisconnectTimer();
376 
377  // Disconnect all remaining sessions.
378  Collection<SessionState> sessions = GlobalApp.getSessions();
379  for (SessionState session : sessions)
380  LibFreeRDP.disconnect(session.getInstance());
381 
382  // unregister freerdp events broadcast receiver
383  unregisterReceiver(libFreeRDPBroadcastReceiver);
384 
385  // remove clipboard listener
386  mClipboardManager.removeClipboardboardChangedListener(this);
387 
388  // free session
389  GlobalApp.freeSession(session.getInstance());
390 
391  session = null;
392  }
393 
394  @Override public void onConfigurationChanged(Configuration newConfig)
395  {
396  super.onConfigurationChanged(newConfig);
397 
398  // reload keyboard resources (changed from landscape)
399  modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard);
400  specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard);
401  numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard);
402  cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard);
403 
404  // apply loaded keyboards
405  keyboardView.setKeyboard(specialkeysKeyboard);
406  modifiersKeyboardView.setKeyboard(modifiersKeyboard);
407 
408  mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
409  View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
410  }
411 
412  private void processIntent(Intent intent)
413  {
414  // get either session instance or create one from a bookmark/uri
415  Bundle bundle = intent.getExtras();
416  Uri openUri = intent.getData();
417  if (openUri != null)
418  {
419  // Launched from URI, e.g:
420  // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=-
421  connect(openUri);
422  }
423  else if (bundle.containsKey(PARAM_INSTANCE))
424  {
425  int inst = bundle.getInt(PARAM_INSTANCE);
426  session = GlobalApp.getSession(inst);
427  bitmap = session.getSurface().getBitmap();
428  bindSession();
429  }
430  else if (bundle.containsKey(PARAM_CONNECTION_REFERENCE))
431  {
432  BookmarkBase bookmark = null;
433  String refStr = bundle.getString(PARAM_CONNECTION_REFERENCE);
434  if (ConnectionReference.isHostnameReference(refStr))
435  {
436  bookmark = new ManualBookmark();
437  bookmark.<ManualBookmark>get().setHostname(ConnectionReference.getHostname(refStr));
438  }
439  else if (ConnectionReference.isBookmarkReference(refStr))
440  {
441  if (ConnectionReference.isManualBookmarkReference(refStr))
442  bookmark = GlobalApp.getManualBookmarkGateway().findById(
443  ConnectionReference.getManualBookmarkId(refStr));
444  else
445  assert false;
446  }
447 
448  if (bookmark != null)
449  connect(bookmark);
450  else
451  closeSessionActivity(RESULT_CANCELED);
452  }
453  else
454  {
455  // no session found - exit
456  closeSessionActivity(RESULT_CANCELED);
457  }
458  }
459 
460  private void connect(BookmarkBase bookmark)
461  {
462  session = GlobalApp.createSession(bookmark, getApplicationContext());
463 
464  BookmarkBase.ScreenSettings screenSettings =
465  session.getBookmark().getActiveScreenSettings();
466  Log.v(TAG, "Screen Resolution: " + screenSettings.getResolutionString());
467  if (screenSettings.isAutomatic())
468  {
469  if ((getResources().getConfiguration().screenLayout &
470  Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE)
471  {
472  // large screen device i.e. tablet: simply use screen info
473  screenSettings.setHeight(screen_height);
474  screenSettings.setWidth(screen_width);
475  }
476  else
477  {
478  // small screen device i.e. phone:
479  // Automatic uses the largest side length of the screen and
480  // makes a 16:10 resolution setting out of it
481  int screenMax = Math.max(screen_width, screen_height);
482  screenSettings.setHeight(screenMax);
483  screenSettings.setWidth((int)((float)screenMax * 1.6f));
484  }
485  }
486  if (screenSettings.isFitScreen())
487  {
488  screenSettings.setHeight(screen_height);
489  screenSettings.setWidth(screen_width);
490  }
491 
492  connectWithTitle(bookmark.getLabel());
493  }
494 
495  private void connect(Uri openUri)
496  {
497  session = GlobalApp.createSession(openUri, getApplicationContext());
498 
499  connectWithTitle(openUri.getAuthority());
500  }
501 
502  static class ConnectThread extends Thread
503  {
504  private SessionState runnableSession;
505  private Context context;
506 
507  public ConnectThread(@NonNull Context context, @NonNull SessionState session)
508  {
509  this.context = context;
510  runnableSession = session;
511  }
512 
513  public void run()
514  {
515  runnableSession.connect(context.getApplicationContext());
516  }
517  }
518 
519  private ConnectThread connectThread = null;
520 
521  private void connectWithTitle(String title)
522  {
523  session.setUIEventListener(this);
524 
525  progressDialog = new ProgressDialog(this);
526  progressDialog.setTitle(title);
527  progressDialog.setMessage(getResources().getText(R.string.dlg_msg_connecting));
528  progressDialog.setButton(
529  ProgressDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
530  @Override public void onClick(DialogInterface dialog, int which)
531  {
532  connectCancelledByUser = true;
533  LibFreeRDP.cancelConnection(session.getInstance());
534  }
535  });
536  progressDialog.setCancelable(false);
537  progressDialog.show();
538 
539  connectThread = new ConnectThread(getApplicationContext(), session);
540  connectThread.start();
541  }
542 
543  // binds the current session to the activity by wiring it up with the
544  // sessionView and updating all internal objects accordingly
545  private void bindSession()
546  {
547  Log.v(TAG, "bindSession called");
548  session.setUIEventListener(this);
549  sessionView.onSurfaceChange(session);
550  scrollView.requestLayout();
551  keyboardMapper.reset(this);
552  mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
553  View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
554  }
555 
556  private void setSoftInputState(boolean state)
557  {
558  InputMethodManager mgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
559 
560  if (state)
561  {
562  mgr.showSoftInput(sessionView, InputMethodManager.SHOW_FORCED);
563  }
564  else
565  {
566  mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0);
567  }
568  }
569 
570  // displays either the system or the extended keyboard or non of them
571  private void showKeyboard(final boolean showSystemKeyboard, final boolean showExtendedKeyboard)
572  {
573  // no matter what we are doing ... hide the zoom controls
574  // onScrollChange notification showing the control again ...
575  // i think check for "preference_key_ui_hide_zoom_controls" preference should be there
576  uiHandler.removeMessages(UIHandler.SHOW_ZOOMCONTROLS);
577  uiHandler.sendEmptyMessage(UIHandler.HIDE_ZOOMCONTROLS);
578 
579  InputMethodManager mgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
580 
581  if (showSystemKeyboard)
582  {
583  // hide extended keyboard
584  keyboardView.setVisibility(View.GONE);
585  // show system keyboard
586  setSoftInputState(true);
587 
588  // show modifiers keyboard
589  modifiersKeyboardView.setVisibility(View.VISIBLE);
590  }
591  else if (showExtendedKeyboard)
592  {
593  // hide system keyboard
594  setSoftInputState(false);
595 
596  // show extended keyboard
597  keyboardView.setKeyboard(specialkeysKeyboard);
598  keyboardView.setVisibility(View.VISIBLE);
599  modifiersKeyboardView.setVisibility(View.VISIBLE);
600  }
601  else
602  {
603  // hide both
604  setSoftInputState(false);
605  keyboardView.setVisibility(View.GONE);
606  modifiersKeyboardView.setVisibility(View.GONE);
607 
608  // clear any active key modifiers)
609  keyboardMapper.clearlAllModifiers();
610  }
611 
612  sysKeyboardVisible = showSystemKeyboard;
613  extKeyboardVisible = showExtendedKeyboard;
614  }
615 
616  private void closeSessionActivity(int resultCode)
617  {
618  // Go back to home activity (and send intent data back to home)
619  setResult(resultCode, getIntent());
620  finish();
621  }
622 
623  // update the state of our modifier keys
624  private void updateModifierKeyStates()
625  {
626  // check if any key is in the keycodes list
627 
628  List<Keyboard.Key> keys = modifiersKeyboard.getKeys();
629  for (Keyboard.Key curKey : keys)
630  {
631  // if the key is a sticky key - just set it to off
632  if (curKey.sticky)
633  {
634  switch (keyboardMapper.getModifierState(curKey.codes[0]))
635  {
636  case KeyboardMapper.KEYSTATE_ON:
637  curKey.on = true;
638  curKey.pressed = false;
639  break;
640 
641  case KeyboardMapper.KEYSTATE_OFF:
642  curKey.on = false;
643  curKey.pressed = false;
644  break;
645 
646  case KeyboardMapper.KEYSTATE_LOCKED:
647  curKey.on = true;
648  curKey.pressed = true;
649  break;
650  }
651  }
652  }
653 
654  // refresh image
655  modifiersKeyboardView.invalidateAllKeys();
656  }
657 
658  private void sendDelayedMoveEvent(int x, int y)
659  {
660  if (uiHandler.hasMessages(UIHandler.SEND_MOVE_EVENT))
661  {
662  uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
663  discardedMoveEvents++;
664  }
665  else
666  discardedMoveEvents = 0;
667 
668  if (discardedMoveEvents > MAX_DISCARDED_MOVE_EVENTS)
669  LibFreeRDP.sendCursorEvent(session.getInstance(), x, y, Mouse.getMoveEvent());
670  else
671  uiHandler.sendMessageDelayed(Message.obtain(null, UIHandler.SEND_MOVE_EVENT, x, y),
672  SEND_MOVE_EVENT_TIMEOUT);
673  }
674 
675  private void cancelDelayedMoveEvent()
676  {
677  uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
678  }
679 
680  @Override public boolean onCreateOptionsMenu(Menu menu)
681  {
682  getMenuInflater().inflate(R.menu.session_menu, menu);
683  return true;
684  }
685 
686  @Override public boolean onOptionsItemSelected(MenuItem item)
687  {
688  // refer to http://tools.android.com/tips/non-constant-fields why we
689  // can't use switch/case here ..
690  int itemId = item.getItemId();
691 
692  if (itemId == R.id.session_touch_pointer)
693  {
694  // toggle touch pointer
695  if (touchPointerView.getVisibility() == View.VISIBLE)
696  {
697  touchPointerView.setVisibility(View.INVISIBLE);
698  sessionView.setTouchPointerPadding(0, 0);
699  }
700  else
701  {
702  touchPointerView.setVisibility(View.VISIBLE);
703  sessionView.setTouchPointerPadding(touchPointerView.getPointerWidth(),
704  touchPointerView.getPointerHeight());
705  }
706  }
707  else if (itemId == R.id.session_sys_keyboard)
708  {
709  showKeyboard(!sysKeyboardVisible, false);
710  }
711  else if (itemId == R.id.session_ext_keyboard)
712  {
713  showKeyboard(false, !extKeyboardVisible);
714  }
715  else if (itemId == R.id.session_disconnect)
716  {
717  showKeyboard(false, false);
718  LibFreeRDP.disconnect(session.getInstance());
719  }
720 
721  return true;
722  }
723 
724  @Override public void onBackPressed()
725  {
726  // hide keyboards (if any visible) or send alt+f4 to the session
727  if (sysKeyboardVisible || extKeyboardVisible)
728  showKeyboard(false, false);
729  else if (ApplicationSettingsActivity.getUseBackAsAltf4(this))
730  {
731  keyboardMapper.sendAltF4();
732  }
733  }
734 
735  @Override public boolean onKeyLongPress(int keyCode, KeyEvent event)
736  {
737  if (keyCode == KeyEvent.KEYCODE_BACK)
738  {
739  LibFreeRDP.disconnect(session.getInstance());
740  return true;
741  }
742  return super.onKeyLongPress(keyCode, event);
743  }
744 
745  // android keyboard input handling
746  // We always use the unicode value to process input from the android
747  // keyboard except if key modifiers
748  // (like Win, Alt, Ctrl) are activated. In this case we will send the
749  // virtual key code to allow key
750  // combinations (like Win + E to open the explorer).
751  @Override public boolean onKeyDown(int keycode, KeyEvent event)
752  {
753  return keyboardMapper.processAndroidKeyEvent(event);
754  }
755 
756  @Override public boolean onKeyUp(int keycode, KeyEvent event)
757  {
758  return keyboardMapper.processAndroidKeyEvent(event);
759  }
760 
761  // onKeyMultiple is called for input of some special characters like umlauts
762  // and some symbol characters
763  @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)
764  {
765  return keyboardMapper.processAndroidKeyEvent(event);
766  }
767 
768  // ****************************************************************************
769  // KeyboardView.KeyboardActionEventListener
770  @Override public void onKey(int primaryCode, int[] keyCodes)
771  {
772  keyboardMapper.processCustomKeyEvent(primaryCode);
773  }
774 
775  @Override public void onText(CharSequence text)
776  {
777  }
778 
779  @Override public void swipeRight()
780  {
781  }
782 
783  @Override public void swipeLeft()
784  {
785  }
786 
787  @Override public void swipeDown()
788  {
789  }
790 
791  @Override public void swipeUp()
792  {
793  }
794 
795  @Override public void onPress(int primaryCode)
796  {
797  }
798 
799  @Override public void onRelease(int primaryCode)
800  {
801  }
802 
803  // ****************************************************************************
804  // KeyboardMapper.KeyProcessingListener implementation
805  @Override public void processVirtualKey(int virtualKeyCode, boolean down)
806  {
807  LibFreeRDP.sendKeyEvent(session.getInstance(), virtualKeyCode, down);
808  }
809 
810  @Override public void processUnicodeKey(int unicodeKey)
811  {
812  LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey, true);
813  LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey, false);
814  }
815 
816  @Override public void switchKeyboard(int keyboardType)
817  {
818  switch (keyboardType)
819  {
820  case KeyboardMapper.KEYBOARD_TYPE_FUNCTIONKEYS:
821  keyboardView.setKeyboard(specialkeysKeyboard);
822  break;
823 
824  case KeyboardMapper.KEYBOARD_TYPE_NUMPAD:
825  keyboardView.setKeyboard(numpadKeyboard);
826  break;
827 
828  case KeyboardMapper.KEYBOARD_TYPE_CURSOR:
829  keyboardView.setKeyboard(cursorKeyboard);
830  break;
831 
832  default:
833  break;
834  }
835  }
836 
837  @Override public void modifiersChanged()
838  {
839  updateModifierKeyStates();
840  }
841 
842  // ****************************************************************************
843  // LibFreeRDP UI event listener implementation
844  @Override public void OnSettingsChanged(int width, int height, int bpp)
845  {
846 
847  if (bpp > 16)
848  bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
849  else
850  bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
851 
852  session.setSurface(new BitmapDrawable(getResources(), bitmap));
853 
854  if (session.getBookmark() == null)
855  {
856  // Return immediately if we launch from URI
857  return;
858  }
859 
860  // check this settings and initial settings - if they are not equal the
861  // server doesn't support our settings
862  // FIXME: the additional check (settings.getWidth() != width + 1) is for
863  // the RDVH bug fix to avoid accidental notifications
864  // (refer to android_freerdp.c for more info on this problem)
865  BookmarkBase.ScreenSettings settings = session.getBookmark().getActiveScreenSettings();
866  if ((settings.getWidth() != width && settings.getWidth() != width + 1) ||
867  settings.getHeight() != height || settings.getColors() != bpp)
868  uiHandler.sendMessage(
869  Message.obtain(null, UIHandler.DISPLAY_TOAST,
870  getResources().getText(R.string.info_capabilities_changed)));
871  }
872 
873  @Override public void OnGraphicsUpdate(int x, int y, int width, int height)
874  {
875  LibFreeRDP.updateGraphics(session.getInstance(), bitmap, x, y, width, height);
876 
877  sessionView.addInvalidRegion(new Rect(x, y, x + width, y + height));
878 
879  /*
880  * since sessionView can only be modified from the UI thread any
881  * modifications to it need to be scheduled
882  */
883 
884  uiHandler.sendEmptyMessage(UIHandler.REFRESH_SESSIONVIEW);
885  }
886 
887  @Override public void OnGraphicsResize(int width, int height, int bpp)
888  {
889  // replace bitmap
890  if (bpp > 16)
891  bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
892  else
893  bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
894  session.setSurface(new BitmapDrawable(getResources(), bitmap));
895 
896  /*
897  * since sessionView can only be modified from the UI thread any
898  * modifications to it need to be scheduled
899  */
900  uiHandler.sendEmptyMessage(UIHandler.GRAPHICS_CHANGED);
901  }
902 
903  @Override
904  public boolean OnAuthenticate(StringBuilder username, StringBuilder domain,
905  StringBuilder password)
906  {
907  // this is where the return code of our dialog will be stored
908  callbackDialogResult = false;
909 
910  // set text fields
911  ((EditText)userCredView.findViewById(R.id.editTextUsername)).setText(username);
912  ((EditText)userCredView.findViewById(R.id.editTextDomain)).setText(domain);
913  ((EditText)userCredView.findViewById(R.id.editTextPassword)).setText(password);
914 
915  // start dialog in UI thread
916  uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgUserCredentials));
917 
918  // wait for result
919  try
920  {
921  synchronized (dlgUserCredentials)
922  {
923  dlgUserCredentials.wait();
924  }
925  }
926  catch (InterruptedException e)
927  {
928  }
929 
930  // clear buffers
931  username.setLength(0);
932  domain.setLength(0);
933  password.setLength(0);
934 
935  // read back user credentials
936  username.append(
937  ((EditText)userCredView.findViewById(R.id.editTextUsername)).getText().toString());
938  domain.append(
939  ((EditText)userCredView.findViewById(R.id.editTextDomain)).getText().toString());
940  password.append(
941  ((EditText)userCredView.findViewById(R.id.editTextPassword)).getText().toString());
942 
943  return callbackDialogResult;
944  }
945 
946  @Override
947  public boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain,
948  StringBuilder password)
949  {
950  // this is where the return code of our dialog will be stored
951  callbackDialogResult = false;
952 
953  // set text fields
954  ((EditText)userCredView.findViewById(R.id.editTextUsername)).setText(username);
955  ((EditText)userCredView.findViewById(R.id.editTextDomain)).setText(domain);
956  ((EditText)userCredView.findViewById(R.id.editTextPassword)).setText(password);
957 
958  // start dialog in UI thread
959  uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgUserCredentials));
960 
961  // wait for result
962  try
963  {
964  synchronized (dlgUserCredentials)
965  {
966  dlgUserCredentials.wait();
967  }
968  }
969  catch (InterruptedException e)
970  {
971  }
972 
973  // clear buffers
974  username.setLength(0);
975  domain.setLength(0);
976  password.setLength(0);
977 
978  // read back user credentials
979  username.append(
980  ((EditText)userCredView.findViewById(R.id.editTextUsername)).getText().toString());
981  domain.append(
982  ((EditText)userCredView.findViewById(R.id.editTextDomain)).getText().toString());
983  password.append(
984  ((EditText)userCredView.findViewById(R.id.editTextPassword)).getText().toString());
985 
986  return callbackDialogResult;
987  }
988 
989  @Override
990  public int OnVerifiyCertificateEx(String host, long port, String commonName, String subject,
991  String issuer, String fingerprint, long flags)
992  {
993  // see if global settings says accept all
994  if (ApplicationSettingsActivity.getAcceptAllCertificates(this))
995  return 0;
996 
997  // this is where the return code of our dialog will be stored
998  callbackDialogResult = false;
999 
1000  // set message
1001  String msg = getResources().getString(R.string.dlg_msg_verify_certificate);
1002  String type = "RDP-Server";
1003  if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_GATEWAY) != 0)
1004  type = "RDP-Gateway";
1005  if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_REDIRECT) != 0)
1006  type = "RDP-Redirect";
1007  msg += "\n\n" + type + ": " + host + ":" + port;
1008 
1009  msg += "\n\nSubject: " + subject + "\nIssuer: " + issuer;
1010 
1011  if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_FP_IS_PEM) != 0)
1012  msg += "\nCertificate: " + fingerprint;
1013  else
1014  msg += "\nFingerprint: " + fingerprint;
1015  dlgVerifyCertificate.setMessage(msg);
1016 
1017  // start dialog in UI thread
1018  uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgVerifyCertificate));
1019 
1020  // wait for result
1021  try
1022  {
1023  synchronized (dlgVerifyCertificate)
1024  {
1025  dlgVerifyCertificate.wait();
1026  }
1027  }
1028  catch (InterruptedException e)
1029  {
1030  }
1031 
1032  return callbackDialogResult ? 1 : 0;
1033  }
1034 
1035  @Override
1036  public int OnVerifyChangedCertificateEx(String host, long port, String commonName,
1037  String subject, String issuer, String fingerprint,
1038  String oldSubject, String oldIssuer,
1039  String oldFingerprint, long flags)
1040  {
1041  // see if global settings says accept all
1042  if (ApplicationSettingsActivity.getAcceptAllCertificates(this))
1043  return 0;
1044 
1045  // this is where the return code of our dialog will be stored
1046  callbackDialogResult = false;
1047 
1048  // set message
1049  String msg = getResources().getString(R.string.dlg_msg_verify_certificate);
1050  String type = "RDP-Server";
1051  if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_GATEWAY) != 0)
1052  type = "RDP-Gateway";
1053  if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_REDIRECT) != 0)
1054  type = "RDP-Redirect";
1055  msg += "\n\n" + type + ": " + host + ":" + port;
1056  msg += "\n\nSubject: " + subject + "\nIssuer: " + issuer;
1057  if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_FP_IS_PEM) != 0)
1058  msg += "\nCertificate: " + fingerprint;
1059  else
1060  msg += "\nFingerprint: " + fingerprint;
1061  dlgVerifyCertificate.setMessage(msg);
1062 
1063  // start dialog in UI thread
1064  uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgVerifyCertificate));
1065 
1066  // wait for result
1067  try
1068  {
1069  synchronized (dlgVerifyCertificate)
1070  {
1071  dlgVerifyCertificate.wait();
1072  }
1073  }
1074  catch (InterruptedException e)
1075  {
1076  }
1077 
1078  return callbackDialogResult ? 1 : 0;
1079  }
1080 
1081  @Override public void OnRemoteClipboardChanged(String data)
1082  {
1083  Log.v(TAG, "OnRemoteClipboardChanged: " + data);
1084  mClipboardManager.setClipboardData(data);
1085  }
1086 
1087  // ****************************************************************************
1088  // ScrollView2DListener implementation
1089  private void resetZoomControlsAutoHideTimeout()
1090  {
1091  uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS);
1092  uiHandler.sendEmptyMessageDelayed(UIHandler.HIDE_ZOOMCONTROLS,
1093  ZOOMCONTROLS_AUTOHIDE_TIMEOUT);
1094  }
1095 
1096  @Override public void onScrollChanged(ScrollView2D scrollView, int x, int y, int oldx, int oldy)
1097  {
1098  zoomControls.setIsZoomInEnabled(!sessionView.isAtMaxZoom());
1099  zoomControls.setIsZoomOutEnabled(!sessionView.isAtMinZoom());
1100 
1101  if (sysKeyboardVisible || extKeyboardVisible)
1102  return;
1103 
1104  if (!ApplicationSettingsActivity.getHideZoomControls(this))
1105  {
1106  uiHandler.sendEmptyMessage(UIHandler.SHOW_ZOOMCONTROLS);
1107  resetZoomControlsAutoHideTimeout();
1108  }
1109  }
1110 
1111  // ****************************************************************************
1112  // SessionView.SessionViewListener
1113  @Override public void onSessionViewBeginTouch()
1114  {
1115  scrollView.setScrollEnabled(false);
1116  }
1117 
1118  @Override public void onSessionViewEndTouch()
1119  {
1120  scrollView.setScrollEnabled(true);
1121  }
1122 
1123  @Override public void onSessionViewLeftTouch(int x, int y, boolean down)
1124  {
1125  if (!down)
1126  cancelDelayedMoveEvent();
1127 
1128  LibFreeRDP.sendCursorEvent(session.getInstance(), x, y,
1129  toggleMouseButtons ? Mouse.getRightButtonEvent(this, down)
1130  : Mouse.getLeftButtonEvent(this, down));
1131 
1132  if (!down)
1133  toggleMouseButtons = false;
1134  }
1135 
1136  public void onSessionViewRightTouch(int x, int y, boolean down)
1137  {
1138  if (!down)
1139  toggleMouseButtons = !toggleMouseButtons;
1140  }
1141 
1142  @Override public void onSessionViewMove(int x, int y)
1143  {
1144  sendDelayedMoveEvent(x, y);
1145  }
1146 
1147  @Override public void onSessionViewScroll(boolean down)
1148  {
1149  LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(this, down));
1150  }
1151 
1152  // ****************************************************************************
1153  // TouchPointerView.TouchPointerListener
1154  @Override public void onTouchPointerClose()
1155  {
1156  touchPointerView.setVisibility(View.INVISIBLE);
1157  sessionView.setTouchPointerPadding(0, 0);
1158  }
1159 
1160  private Point mapScreenCoordToSessionCoord(int x, int y)
1161  {
1162  int mappedX = (int)((float)(x + scrollView.getScrollX()) / sessionView.getZoom());
1163  int mappedY = (int)((float)(y + scrollView.getScrollY()) / sessionView.getZoom());
1164  if (mappedX > bitmap.getWidth())
1165  mappedX = bitmap.getWidth();
1166  if (mappedY > bitmap.getHeight())
1167  mappedY = bitmap.getHeight();
1168  return new Point(mappedX, mappedY);
1169  }
1170 
1171  @Override public void onTouchPointerLeftClick(int x, int y, boolean down)
1172  {
1173  Point p = mapScreenCoordToSessionCoord(x, y);
1174  LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y,
1175  Mouse.getLeftButtonEvent(this, down));
1176  }
1177 
1178  @Override public void onTouchPointerRightClick(int x, int y, boolean down)
1179  {
1180  Point p = mapScreenCoordToSessionCoord(x, y);
1181  LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y,
1182  Mouse.getRightButtonEvent(this, down));
1183  }
1184 
1185  @Override public void onTouchPointerMove(int x, int y)
1186  {
1187  Point p = mapScreenCoordToSessionCoord(x, y);
1188  LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, Mouse.getMoveEvent());
1189 
1190  if (ApplicationSettingsActivity.getAutoScrollTouchPointer(this) &&
1191  !uiHandler.hasMessages(UIHandler.SCROLLING_REQUESTED))
1192  {
1193  Log.v(TAG, "Starting auto-scroll");
1194  uiHandler.sendEmptyMessageDelayed(UIHandler.SCROLLING_REQUESTED, SCROLLING_TIMEOUT);
1195  }
1196  }
1197 
1198  @Override public void onTouchPointerScroll(boolean down)
1199  {
1200  LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(this, down));
1201  }
1202 
1203  @Override public void onTouchPointerToggleKeyboard()
1204  {
1205  showKeyboard(!sysKeyboardVisible, false);
1206  }
1207 
1208  @Override public void onTouchPointerToggleExtKeyboard()
1209  {
1210  showKeyboard(false, !extKeyboardVisible);
1211  }
1212 
1213  @Override public void onTouchPointerResetScrollZoom()
1214  {
1215  sessionView.setZoom(1.0f);
1216  scrollView.scrollTo(0, 0);
1217  }
1218 
1219  @Override public boolean onGenericMotionEvent(MotionEvent e)
1220  {
1221  super.onGenericMotionEvent(e);
1222  switch (e.getAction())
1223  {
1224  case MotionEvent.ACTION_SCROLL:
1225  final float vScroll = e.getAxisValue(MotionEvent.AXIS_VSCROLL);
1226  if (vScroll < 0)
1227  {
1228  LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0,
1229  Mouse.getScrollEvent(this, false));
1230  }
1231  if (vScroll > 0)
1232  {
1233  LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0,
1234  Mouse.getScrollEvent(this, true));
1235  }
1236  break;
1237  }
1238  return true;
1239  }
1240 
1241  // ****************************************************************************
1242  // ClipboardManagerProxy.OnClipboardChangedListener
1243  @Override public void onClipboardChanged(String data)
1244  {
1245  Log.v(TAG, "onClipboardChanged: " + data);
1246  LibFreeRDP.sendClipboardData(session.getInstance(), data);
1247  }
1248 
1249  private class UIHandler extends Handler
1250  {
1251 
1252  public static final int REFRESH_SESSIONVIEW = 1;
1253  public static final int DISPLAY_TOAST = 2;
1254  public static final int HIDE_ZOOMCONTROLS = 3;
1255  public static final int SEND_MOVE_EVENT = 4;
1256  public static final int SHOW_DIALOG = 5;
1257  public static final int GRAPHICS_CHANGED = 6;
1258  public static final int SCROLLING_REQUESTED = 7;
1259  public static final int SHOW_ZOOMCONTROLS = 8;
1260 
1261  UIHandler()
1262  {
1263  super();
1264  }
1265 
1266  @Override public void handleMessage(Message msg)
1267  {
1268  switch (msg.what)
1269  {
1270  case GRAPHICS_CHANGED:
1271  {
1272  sessionView.onSurfaceChange(session);
1273  scrollView.requestLayout();
1274  break;
1275  }
1276  case REFRESH_SESSIONVIEW:
1277  {
1278  sessionView.invalidateRegion();
1279  break;
1280  }
1281  case DISPLAY_TOAST:
1282  {
1283  Toast errorToast = Toast.makeText(getApplicationContext(), msg.obj.toString(),
1284  Toast.LENGTH_LONG);
1285  errorToast.show();
1286  break;
1287  }
1288  case HIDE_ZOOMCONTROLS:
1289  {
1290  if (zoomControls.isShown())
1291  zoomControls.hide();
1292  break;
1293  }
1294  case SHOW_ZOOMCONTROLS:
1295  {
1296  if (!zoomControls.isShown())
1297  zoomControls.show();
1298 
1299  break;
1300  }
1301  case SEND_MOVE_EVENT:
1302  {
1303  LibFreeRDP.sendCursorEvent(session.getInstance(), msg.arg1, msg.arg2,
1304  Mouse.getMoveEvent());
1305  break;
1306  }
1307  case SHOW_DIALOG:
1308  {
1309  // create and show the dialog
1310  ((Dialog)msg.obj).show();
1311  break;
1312  }
1313  case SCROLLING_REQUESTED:
1314  {
1315  int scrollX = 0;
1316  int scrollY = 0;
1317  float[] pointerPos = touchPointerView.getPointerPosition();
1318 
1319  if (pointerPos[0] > (screen_width - touchPointerView.getPointerWidth()))
1320  scrollX = SCROLLING_DISTANCE;
1321  else if (pointerPos[0] < 0)
1322  scrollX = -SCROLLING_DISTANCE;
1323 
1324  if (pointerPos[1] > (screen_height - touchPointerView.getPointerHeight()))
1325  scrollY = SCROLLING_DISTANCE;
1326  else if (pointerPos[1] < 0)
1327  scrollY = -SCROLLING_DISTANCE;
1328 
1329  scrollView.scrollBy(scrollX, scrollY);
1330 
1331  // see if we reached the min/max scroll positions
1332  if (scrollView.getScrollX() == 0 ||
1333  scrollView.getScrollX() == (sessionView.getWidth() - scrollView.getWidth()))
1334  scrollX = 0;
1335  if (scrollView.getScrollY() == 0 ||
1336  scrollView.getScrollY() ==
1337  (sessionView.getHeight() - scrollView.getHeight()))
1338  scrollY = 0;
1339 
1340  if (scrollX != 0 || scrollY != 0)
1341  uiHandler.sendEmptyMessageDelayed(SCROLLING_REQUESTED, SCROLLING_TIMEOUT);
1342  else
1343  Log.v(TAG, "Stopping auto-scroll");
1344  break;
1345  }
1346  }
1347  }
1348  }
1349 
1350  private class PinchZoomListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
1351  {
1352  private float scaleFactor = 1.0f;
1353 
1354  @Override public boolean onScaleBegin(ScaleGestureDetector detector)
1355  {
1356  scrollView.setScrollEnabled(false);
1357  return true;
1358  }
1359 
1360  @Override public boolean onScale(ScaleGestureDetector detector)
1361  {
1362 
1363  // calc scale factor
1364  scaleFactor *= detector.getScaleFactor();
1365  scaleFactor = Math.max(SessionView.MIN_SCALE_FACTOR,
1366  Math.min(scaleFactor, SessionView.MAX_SCALE_FACTOR));
1367  sessionView.setZoom(scaleFactor);
1368 
1369  if (!sessionView.isAtMinZoom() && !sessionView.isAtMaxZoom())
1370  {
1371  // transform scroll origin to the new zoom space
1372  float transOriginX = scrollView.getScrollX() * detector.getScaleFactor();
1373  float transOriginY = scrollView.getScrollY() * detector.getScaleFactor();
1374 
1375  // transform center point to the zoomed space
1376  float transCenterX =
1377  (scrollView.getScrollX() + detector.getFocusX()) * detector.getScaleFactor();
1378  float transCenterY =
1379  (scrollView.getScrollY() + detector.getFocusY()) * detector.getScaleFactor();
1380 
1381  // scroll by the difference between the distance of the
1382  // transformed center/origin point and their old distance
1383  // (focusX/Y)
1384  scrollView.scrollBy((int)((transCenterX - transOriginX) - detector.getFocusX()),
1385  (int)((transCenterY - transOriginY) - detector.getFocusY()));
1386  }
1387 
1388  return true;
1389  }
1390 
1391  @Override public void onScaleEnd(ScaleGestureDetector de)
1392  {
1393  scrollView.setScrollEnabled(true);
1394  }
1395  }
1396 
1397  private class LibFreeRDPBroadcastReceiver extends BroadcastReceiver
1398  {
1399  @Override public void onReceive(Context context, Intent intent)
1400  {
1401  // still got a valid session?
1402  if (session == null)
1403  return;
1404 
1405  // is this event for the current session?
1406  if (session.getInstance() != intent.getExtras().getLong(GlobalApp.EVENT_PARAM, -1))
1407  return;
1408 
1409  switch (intent.getExtras().getInt(GlobalApp.EVENT_TYPE, -1))
1410  {
1411  case GlobalApp.FREERDP_EVENT_CONNECTION_SUCCESS:
1412  OnConnectionSuccess(context);
1413  break;
1414 
1415  case GlobalApp.FREERDP_EVENT_CONNECTION_FAILURE:
1416  OnConnectionFailure(context);
1417  break;
1418  case GlobalApp.FREERDP_EVENT_DISCONNECTED:
1419  OnDisconnected(context);
1420  break;
1421  }
1422  }
1423 
1424  private void OnConnectionSuccess(Context context)
1425  {
1426  Log.v(TAG, "OnConnectionSuccess");
1427 
1428  // bind session
1429  bindSession();
1430 
1431  if (progressDialog != null)
1432  {
1433  progressDialog.dismiss();
1434  progressDialog = null;
1435  }
1436 
1437  if (session.getBookmark() == null)
1438  {
1439  // Return immediately if we launch from URI
1440  return;
1441  }
1442 
1443  // add hostname to history if quick connect was used
1444  Bundle bundle = getIntent().getExtras();
1445  if (bundle != null && bundle.containsKey(PARAM_CONNECTION_REFERENCE))
1446  {
1447  if (ConnectionReference.isHostnameReference(
1448  bundle.getString(PARAM_CONNECTION_REFERENCE)))
1449  {
1450  assert session.getBookmark().getType() == BookmarkBase.TYPE_MANUAL;
1451  String item = session.getBookmark().<ManualBookmark>get().getHostname();
1452  if (!GlobalApp.getQuickConnectHistoryGateway().historyItemExists(item))
1453  GlobalApp.getQuickConnectHistoryGateway().addHistoryItem(item);
1454  }
1455  }
1456  }
1457 
1458  private void OnConnectionFailure(Context context)
1459  {
1460  Log.v(TAG, "OnConnectionFailure");
1461 
1462  // remove pending move events
1463  uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
1464 
1465  if (progressDialog != null)
1466  {
1467  progressDialog.dismiss();
1468  progressDialog = null;
1469  }
1470 
1471  // post error message on UI thread
1472  if (!connectCancelledByUser)
1473  uiHandler.sendMessage(
1474  Message.obtain(null, UIHandler.DISPLAY_TOAST,
1475  getResources().getText(R.string.error_connection_failure)));
1476 
1477  closeSessionActivity(RESULT_CANCELED);
1478  }
1479 
1480  private void OnDisconnected(Context context)
1481  {
1482  Log.v(TAG, "OnDisconnected");
1483 
1484  // remove pending move events
1485  uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT);
1486 
1487  if (progressDialog != null)
1488  {
1489  progressDialog.dismiss();
1490  progressDialog = null;
1491  }
1492 
1493  session.setUIEventListener(null);
1494  closeSessionActivity(RESULT_OK);
1495  }
1496  }
1497 }