FreeRDP
Loading...
Searching...
No Matches
SessionActivity.java
1/*
2 Android Session Activity
3
4 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5 Copyright 2026 Ibrahim Sevinc <ibrahim.sevinc.mail@gmail.com>
6
7 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
8 If a copy of the MPL was not distributed with this file, You can obtain one at
9 http://mozilla.org/MPL/2.0/.
10 */
11
12package com.freerdp.freerdpcore.presentation;
13
14import android.Manifest;
15import android.content.Context;
16import android.content.Intent;
17import android.content.pm.PackageManager;
18import android.content.res.Configuration;
19import android.graphics.Bitmap;
20import android.graphics.Bitmap.Config;
21import android.graphics.Rect;
22import android.graphics.drawable.BitmapDrawable;
23import android.net.Uri;
24import android.os.Build;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29
30import androidx.activity.OnBackPressedCallback;
31import androidx.annotation.NonNull;
32import androidx.annotation.RequiresApi;
33import androidx.appcompat.app.AppCompatActivity;
34import androidx.core.graphics.Insets;
35import androidx.core.view.ViewCompat;
36import androidx.core.view.WindowCompat;
37import androidx.core.view.WindowInsetsCompat;
38import androidx.core.view.WindowInsetsControllerCompat;
39import androidx.lifecycle.ViewModelProvider;
40
41import android.util.Log;
42import android.view.KeyEvent;
43import android.view.MotionEvent;
44import android.view.ScaleGestureDetector;
45import android.view.View;
46import android.view.ViewGroup;
47import android.view.ViewTreeObserver.OnGlobalLayoutListener;
48import android.view.RoundedCorner;
49import android.view.WindowInsets;
50import android.view.WindowManager;
51import android.widget.Toast;
52
53import com.freerdp.freerdpcore.R;
54import com.freerdp.freerdpcore.application.GlobalApp;
55import com.freerdp.freerdpcore.application.SessionState;
56import com.freerdp.freerdpcore.domain.BookmarkBase;
57import com.freerdp.freerdpcore.domain.ConnectionReference;
58import com.freerdp.freerdpcore.services.LibFreeRDP;
59import com.freerdp.freerdpcore.utils.ClipboardManagerProxy;
60
61public class SessionActivity extends AppCompatActivity
62 implements LibFreeRDP.UIEventListener, ClipboardManagerProxy.OnClipboardChangedListener
63{
64 public static final String PARAM_CONNECTION_REFERENCE = "conRef";
65 public static final String PARAM_INSTANCE = "instance";
66 private static final String TAG = "FreeRDP.SessionActivity";
67 static volatile SessionActivity activeSession;
68 private Bitmap bitmap;
69 private SessionState session;
70 private SessionView sessionView;
71 private TouchPointerView touchPointerView;
72
73 private static final int REFRESH_SESSIONVIEW = 1;
74 private static final int DISPLAY_TOAST = 2;
75 private static final int GRAPHICS_CHANGED = 6;
76 private static final int POINTER_SET = 7;
77 private static final int REQUEST_MEDIA_PERMISSIONS = 100;
78
79 private RailWindowManager railManager;
80
81 private final Handler uiHandler = new Handler(Looper.getMainLooper()) {
82 @Override public void handleMessage(Message msg)
83 {
84 switch (msg.what)
85 {
86 case GRAPHICS_CHANGED:
87 {
88 sessionView.onSurfaceChange(session);
89 scrollView.requestLayout();
90 break;
91 }
92 case REFRESH_SESSIONVIEW:
93 {
94 sessionView.invalidateRegion();
95 break;
96 }
97 case DISPLAY_TOAST:
98 {
99 Toast errorToast = Toast.makeText(getApplicationContext(), msg.obj.toString(),
100 Toast.LENGTH_LONG);
101 errorToast.show();
102 break;
103 }
104 case POINTER_SET:
105 {
106 Bundle data = msg.getData();
107 if (data != null && data.containsKey("pixels"))
108 {
109 int[] pixels = data.getIntArray("pixels");
110 int width = data.getInt("width");
111 int height = data.getInt("height");
112 int hotX = data.getInt("hotX");
113 int hotY = data.getInt("hotY");
114 sessionView.setRemoteCursor(pixels, width, height, hotX, hotY);
115 if (touchPointerView != null)
116 touchPointerView.setRemoteCursor(pixels, width, height, hotX, hotY);
117 }
118 else
119 {
120 sessionView.setRemoteCursor(null, 0, 0, 0, 0);
121 if (touchPointerView != null)
122 touchPointerView.setRemoteCursor(null, 0, 0, 0, 0);
123 }
124 break;
125 }
126 }
127 }
128 };
129
130 private int screen_width;
131 private int screen_height;
132
133 private BookmarkBase pendingConnectBookmark = null;
134 private boolean connectCancelledByUser = false;
135 private boolean sessionRunning = false;
136 private long backPressedTime = 0;
137
138 private SessionViewModel sessionViewModel;
139 private ScrollView2D scrollView;
140 private ClipboardManagerProxy mClipboardManager;
141 private SessionInputManager inputManager;
142 private SessionDialogs dialogs;
143
144 private FloatingToolbar floatingToolbar;
145
146 void hideSystemBars()
147 {
148 boolean hideStatusBar = ApplicationSettingsActivity.getHideStatusBar(this);
149 boolean hideNavBar = ApplicationSettingsActivity.getHideNavigationBar(this);
150
151 if (inputManager != null && inputManager.isSoftInputActive())
152 {
153 // out of immersive mode while the IME is up, so the back gesture works right away
154 hideNavBar = false;
155 }
156
157 WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
158
159 if (getSupportActionBar() != null)
160 getSupportActionBar().hide();
161
162 WindowInsetsControllerCompat controller =
163 WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
164 controller.setAppearanceLightStatusBars(false);
165 controller.setAppearanceLightNavigationBars(false);
166
167 getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT);
168 getWindow().setNavigationBarColor(android.graphics.Color.TRANSPARENT);
169 getWindow().setNavigationBarContrastEnforced(false);
170
171 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
172 {
173 int toHide = 0;
174 int toShow = 0;
175 if (hideStatusBar)
176 toHide |= WindowInsetsCompat.Type.statusBars();
177 else
178 toShow |= WindowInsetsCompat.Type.statusBars();
179
180 if (hideNavBar)
181 toHide |= WindowInsetsCompat.Type.navigationBars();
182 else
183 toShow |= WindowInsetsCompat.Type.navigationBars();
184
185 if (toHide != 0)
186 {
187 // whatever stays hidden keeps immersive behaviour; BEHAVIOR_DEFAULT would let any
188 // tap on the session pull the bar back in
189 controller.setSystemBarsBehavior(
190 WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
191 controller.hide(toHide);
192 }
193
194 if (toShow != 0)
195 {
196 controller.show(toShow);
197 }
198 }
199 else
200 {
201 // API 29: layout flags must be set explicitly to keep drawing behind system bars.
202 int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
203 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
204 if (hideStatusBar)
205 flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
206 if (hideNavBar)
207 flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
208 if ((flags & (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)) !=
209 0)
210 flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
211
212 getWindow().getDecorView().setSystemUiVisibility(flags);
213 }
214
215 WindowManager.LayoutParams lp = getWindow().getAttributes();
216 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
217 lp.layoutInDisplayCutoutMode =
218 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
219 else
220 lp.layoutInDisplayCutoutMode =
221 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
222 getWindow().setAttributes(lp);
223 }
224
225 @Override public void onCreate(Bundle savedInstanceState)
226 {
227 super.onCreate(savedInstanceState);
228
229 hideSystemBars();
230
231 this.setContentView(R.layout.session);
232
233 Log.v(TAG, "Session.onCreate");
234
235 // ATTENTION: We use the onGlobalLayout notification to start our
236 // session.
237 // This is because only then we can know the exact size of our session
238 // when using fit screen
239 // accounting for any status bars etc. that Android might throws on us.
240 // A bit weird looking
241 // but this is the only way ...
242 final View activityRootView = findViewById(R.id.session_root_view);
243 activityRootView.setFitsSystemWindows(false);
244 ViewCompat.setOnApplyWindowInsetsListener(activityRootView,
245 (v, insets) -> onWindowInsetsChanged(v, insets));
246 activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
247 new OnGlobalLayoutListener() {
248 @Override public void onGlobalLayout()
249 {
250 screen_width = scrollView.getWidth() - scrollView.getPaddingLeft() -
251 scrollView.getPaddingRight();
252 screen_height = scrollView.getHeight() - scrollView.getPaddingTop() -
253 scrollView.getPaddingBottom();
254
255 // start session
256 if (!sessionRunning && getIntent() != null)
257 {
258 processIntent(getIntent());
259 sessionRunning = true;
260 }
261 }
262 });
263
264 sessionView = findViewById(R.id.sessionView);
265 sessionView.requestFocus();
266
267 touchPointerView = findViewById(R.id.touchPointerView);
268
269 floatingToolbar = new FloatingToolbar(this, new FloatingToolbar.Listener() {
270 @Override public void onToggleTouchPointer()
271 {
272 if (inputManager != null)
273 inputManager.toggleTouchPointer();
274 }
275 @Override public void onToggleKeyboard()
276 {
277 if (inputManager != null)
278 inputManager.toggleKeyboard();
279 }
280 });
281
282 ExtendedKeyboardView keyboard = findViewById(R.id.extended_keyboard);
283
284 scrollView = findViewById(R.id.sessionScrollView);
285 scrollView.setScrollViewListener(null);
286 railManager = new RailWindowManager(this, findViewById(R.id.railContainer), sessionView);
287 sessionViewModel = new ViewModelProvider(this).get(SessionViewModel.class);
288 sessionViewModel.getState().observe(this, this::onConnectionStateChanged);
289
290 dialogs = new SessionDialogs(this, new SessionDialogs.OnUserCancelListener() {
291 @Override public void onUserCancel()
292 {
293 connectCancelledByUser = true;
294 }
295 });
296
297 // Wire up the input manager (instance is attached later in bindSession()).
298 inputManager =
299 new SessionInputManager(this, scrollView, sessionView, touchPointerView, keyboard);
300 sessionView.setSessionViewListener(inputManager);
301 touchPointerView.setTouchPointerListener(inputManager);
302 sessionView.setScaleGestureDetector(
303 new ScaleGestureDetector(this, inputManager.getPinchZoomListener()));
304
305 mClipboardManager = ClipboardManagerProxy.getClipboardManager(this);
306 mClipboardManager.addClipboardChangedListener(this);
307
308 getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
309 @Override public void handleOnBackPressed()
310 {
311 handleBackPressed();
312 }
313 });
314
315 hideSystemBars();
316 }
317
318 @Override public void onWindowFocusChanged(boolean hasFocus)
319 {
320 super.onWindowFocusChanged(hasFocus);
321 if (hasFocus)
322 {
323 hideSystemBars();
324 mClipboardManager.getPrimaryClipManually();
325 }
326 }
327
328 @Override protected void onStart()
329 {
330 super.onStart();
331 Log.v(TAG, "Session.onStart");
332 }
333
334 @Override protected void onRestart()
335 {
336 super.onRestart();
337 Log.v(TAG, "Session.onRestart");
338 }
339
340 @Override protected void onResume()
341 {
342 super.onResume();
343 Log.v(TAG, "Session.onResume");
344 activeSession = this;
345 }
346
347 @Override protected void onPause()
348 {
349 super.onPause();
350 Log.v(TAG, "Session.onPause");
351 if (activeSession == this)
352 activeSession = null;
353 // hide any visible keyboards
354 inputManager.hideKeyboards();
355 }
356
357 @Override protected void onStop()
358 {
359 super.onStop();
360 Log.v(TAG, "Session.onStop");
361 }
362
363 @Override protected void onDestroy()
364 {
365 if (connectThread != null)
366 {
367 connectThread.interrupt();
368 }
369 super.onDestroy();
370 Log.v(TAG, "Session.onDestroy");
371
372 // Cancel running disconnect timers.
373 GlobalApp.cancelDisconnectTimer();
374
375 // Disconnect only this activity's session.
376 if (session != null)
377 LibFreeRDP.disconnect(session.getInstance());
378
379 // unregister freerdp session listener
380 sessionViewModel.unregister();
381
382 // remove clipboard listener
383 mClipboardManager.removeClipboardboardChangedListener(this);
384
385 // free session
386 GlobalApp.freeSession(session.getInstance());
387
388 session = null;
389 }
390
391 @Override public void onConfigurationChanged(Configuration newConfig)
392 {
393 super.onConfigurationChanged(newConfig);
394
395 hideSystemBars();
396
397 // screen_width/screen_height will be updated by the next onGlobalLayout callback;
398 if (session != null && session.getBookmark() != null &&
399 session.getBookmark().getActiveScreenSettings().isFitScreen())
400 {
401 scrollView.post(() -> {
402 if (screen_width > 0 && screen_height > 0)
403 LibFreeRDP.sendMonitorLayout(session.getInstance(), screen_width,
404 screen_height);
405 });
406 }
407 }
408
409 private WindowInsetsCompat onWindowInsetsChanged(View rootView, WindowInsetsCompat windowInsets)
410 {
411 boolean fitSafeArea = ApplicationSettingsActivity.getFitRoundedCorners(this);
412 boolean hideStatusBar = ApplicationSettingsActivity.getHideStatusBar(this);
413 boolean hideNavBar = ApplicationSettingsActivity.getHideNavigationBar(this);
414
415 int insetsTop = windowInsets
416 .getInsets(WindowInsetsCompat.Type.statusBars() |
417 WindowInsetsCompat.Type.displayCutout())
418 .top;
419 rootView.setPadding(0, hideStatusBar ? 0 : insetsTop, 0, 0);
420 Insets navInsets = hideNavBar
421 ? Insets.NONE
422 : windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
423 if (floatingToolbar != null)
424 floatingToolbar.setInsets(navInsets.left, hideStatusBar ? 0 : insetsTop,
425 navInsets.right, navInsets.bottom);
426
427 int safeLeft = 0, safeTop = 0, safeRight = 0, safeBottom = 0;
428 if (fitSafeArea && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
429 {
430 WindowInsets platformInsets = windowInsets.toWindowInsets();
431 if (platformInsets != null)
432 {
433 boolean landscape = getResources().getConfiguration().orientation ==
434 Configuration.ORIENTATION_LANDSCAPE;
435
436 int radTL = cornerRadius(platformInsets, RoundedCorner.POSITION_TOP_LEFT);
437 int radBL = cornerRadius(platformInsets, RoundedCorner.POSITION_BOTTOM_LEFT);
438 int radTR = cornerRadius(platformInsets, RoundedCorner.POSITION_TOP_RIGHT);
439 int radBR = cornerRadius(platformInsets, RoundedCorner.POSITION_BOTTOM_RIGHT);
440
441 if (landscape)
442 {
443 safeLeft = Math.max(0, Math.max(radTL, radBL) - rootView.getPaddingLeft());
444 safeRight = Math.max(0, Math.max(radTR, radBR) - rootView.getPaddingRight());
445 }
446 else
447 {
448 safeTop = Math.max(0, Math.max(radTL, radTR) - rootView.getPaddingTop());
449 safeBottom = Math.max(0, Math.max(radBL, radBR) - rootView.getPaddingBottom());
450 }
451 }
452 }
453
454 int imeBottom = windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
455
456 // the only reliable account of whether the IME is really on screen: it lets the input
457 // manager tell an external dismissal from a show request that has not animated in yet
458 if (inputManager != null)
459 inputManager.onImeVisibilityChanged(imeBottom > 0);
460
461 View extKeyboard = findViewById(R.id.extended_keyboard);
462 if (extKeyboard instanceof ExtendedKeyboardView)
463 {
464 // imeBottom already covers the nav bar, so don't pad for it twice
465 int kbdBottom = imeBottom > 0 ? 0 : navInsets.bottom;
466 ((ExtendedKeyboardView)extKeyboard)
467 .setInsets(Math.max(navInsets.left, safeLeft), Math.max(navInsets.right, safeRight),
468 kbdBottom);
469 }
470
471 // the keyboard reserves the bottom inset through its own margin, so the scroll view
472 // above it must not pad for the nav bar again
473 boolean kbdVisible = extKeyboard != null && extKeyboard.getVisibility() == View.VISIBLE;
474 int scrollBottom = kbdVisible ? 0 : Math.max(safeBottom, navInsets.bottom);
475 scrollView.setPadding(Math.max(safeLeft, navInsets.left), safeTop,
476 Math.max(safeRight, navInsets.right), scrollBottom);
477
478 // insets are consumed here, so lift the extended keyboard above the IME
479 if (extKeyboard != null)
480 {
481 ViewGroup.MarginLayoutParams lp =
482 (ViewGroup.MarginLayoutParams)extKeyboard.getLayoutParams();
483 if (lp.bottomMargin != imeBottom)
484 {
485 lp.bottomMargin = imeBottom;
486 extKeyboard.setLayoutParams(lp);
487 }
488 }
489
490 return WindowInsetsCompat.CONSUMED;
491 }
492
493 @RequiresApi(Build.VERSION_CODES.S)
494 private static int cornerRadius(WindowInsets insets, int position)
495 {
496 RoundedCorner corner = insets.getRoundedCorner(position);
497 return (corner != null) ? corner.getRadius() : 0;
498 }
499
500 private void processIntent(Intent intent)
501 {
502 // get either session instance or create one from a bookmark/uri
503 Bundle bundle = intent.getExtras();
504 Uri openUri = intent.getData();
505 if (openUri != null)
506 {
507 // Launched from URI, e.g:
508 // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=-
509 connect(openUri);
510 }
511 else if (bundle.containsKey(PARAM_INSTANCE))
512 {
513 int inst = bundle.getInt(PARAM_INSTANCE);
514 session = GlobalApp.getSession(inst);
515 bitmap = session.getSurface().getBitmap();
516 bindSession();
517 }
518 else if (bundle.containsKey(PARAM_CONNECTION_REFERENCE))
519 {
520 String refStr = bundle.getString(PARAM_CONNECTION_REFERENCE);
521 if (ConnectionReference.isHostnameReference(refStr))
522 {
523 BookmarkBase bookmark = new BookmarkBase();
524 bookmark.setHostname(ConnectionReference.getHostname(refStr));
525 connect(bookmark);
526 }
527 else if (ConnectionReference.isBookmarkReference(refStr))
528 {
529 sessionViewModel.loadBookmarkById(ConnectionReference.getBookmarkId(refStr),
530 bookmark -> {
531 if (bookmark != null)
532 connect(bookmark);
533 else
534 closeSessionActivity(RESULT_CANCELED);
535 });
536 }
537 else
538 {
539 closeSessionActivity(RESULT_CANCELED);
540 }
541 }
542 else
543 {
544 // no session found - exit
545 closeSessionActivity(RESULT_CANCELED);
546 }
547 }
548
549 private void connect(BookmarkBase bookmark)
550 {
551 session = GlobalApp.createSession(bookmark, getApplicationContext());
552
553 BookmarkBase.ScreenSettings screenSettings =
554 session.getBookmark().getActiveScreenSettings();
555 Log.v(TAG, "Screen Resolution: " + screenSettings.getResolutionString());
556 if (screenSettings.isAutomatic())
557 {
558 // Instead of enforcing obsolete ratios based on screen categories,
559 // directly map to actual device metrics without arbitrary multi-scaling.
560 screenSettings.setHeight(screen_height);
561 screenSettings.setWidth(screen_width);
562 }
563 if (screenSettings.isFitScreen())
564 {
565 screenSettings.setHeight(screen_height);
566 screenSettings.setWidth(screen_width);
567 }
568
569 // RECORD_AUDIO / CAMERA: only if the matching redirect is enabled.
570 java.util.ArrayList<String> needed = new java.util.ArrayList<>();
571 if (bookmark.getAdvancedSettings().getRedirectMicrophone() &&
572 checkSelfPermission(Manifest.permission.RECORD_AUDIO) !=
573 PackageManager.PERMISSION_GRANTED)
574 needed.add(Manifest.permission.RECORD_AUDIO);
575 if (bookmark.getAdvancedSettings().getRedirectCamera() &&
576 checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)
577 needed.add(Manifest.permission.CAMERA);
578
579 if (!needed.isEmpty())
580 {
581 pendingConnectBookmark = bookmark;
582 requestPermissions(needed.toArray(new String[0]), REQUEST_MEDIA_PERMISSIONS);
583 return;
584 }
585
586 connectWithTitle(bookmark.getLabel());
587 }
588
589 @Override
590 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
591 @NonNull int[] grantResults)
592 {
593 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
594 if (requestCode == REQUEST_MEDIA_PERMISSIONS && pendingConnectBookmark != null)
595 {
596 BookmarkBase bookmark = pendingConnectBookmark;
597 pendingConnectBookmark = null;
598 connectWithTitle(bookmark.getLabel());
599 }
600 }
601
602 private void connect(Uri openUri)
603 {
604 session = GlobalApp.createSession(openUri, getApplicationContext());
605
606 connectWithTitle(openUri.getAuthority());
607 }
608
609 static class ConnectThread extends Thread
610 {
611 private final SessionState runnableSession;
612 private final Context context;
613
614 public ConnectThread(@NonNull Context context, @NonNull SessionState session)
615 {
616 this.context = context;
617 runnableSession = session;
618 }
619
620 public void run()
621 {
622 runnableSession.connect(context.getApplicationContext());
623 }
624 }
625
626 private ConnectThread connectThread = null;
627
628 private void connectWithTitle(String title)
629 {
630 session.setUIEventListener(this);
631
632 sessionViewModel.register(session.getInstance());
633
634 dialogs.showProgress(title, () -> {
635 connectCancelledByUser = true;
636 LibFreeRDP.cancelConnection(session.getInstance());
637 });
638
639 connectThread = new ConnectThread(getApplicationContext(), session);
640 connectThread.start();
641 }
642
643 // binds the current session to the activity by wiring it up with the
644 // sessionView and updating all internal objects accordingly
645 private void bindSession()
646 {
647 Log.v(TAG, "bindSession called");
648 session.setUIEventListener(this);
649 sessionView.onSurfaceChange(session);
650 scrollView.requestLayout();
651
652 Bitmap surface = session.getSurface() != null ? session.getSurface().getBitmap() : null;
653 inputManager.attachSession(session.getInstance(), surface);
654 inputManager.setScreenSize(screen_width, screen_height);
655 hideSystemBars();
656 View rootView = findViewById(R.id.session_root_view);
657 if (rootView != null)
658 ViewCompat.requestApplyInsets(rootView);
659 }
660
661 private void closeSessionActivity(int resultCode)
662 {
663 // Go back to home activity (and send intent data back to home)
664 setResult(resultCode, getIntent());
665 finish();
666 }
667
668 public void handleBackPressed()
669 {
670 // hide keyboards (if any visible) or send alt+f4 to the session
671 if (inputManager != null)
672 {
673 if (inputManager.handleKeyboardBack())
674 return;
675 if (inputManager.handleBackAsAltF4())
676 return;
677 }
678 if (System.currentTimeMillis() - backPressedTime < 2000)
679 {
680 connectCancelledByUser = true;
681 LibFreeRDP.disconnect(session.getInstance());
682 }
683 else
684 {
685 backPressedTime = System.currentTimeMillis();
686 Toast.makeText(this, R.string.session_double_back_to_exit, Toast.LENGTH_SHORT).show();
687 }
688 }
689
690 @Override public boolean onKeyLongPress(int keyCode, KeyEvent event)
691 {
692 if (inputManager.onAndroidKeyLongPress(keyCode))
693 return true;
694 return super.onKeyLongPress(keyCode, event);
695 }
696
697 boolean handleKeyEvent(KeyEvent event)
698 {
699 return inputManager != null && inputManager.onAndroidKeyEvent(event);
700 }
701
702 // android keyboard input handling
703 // We always use the unicode value to process input from the android
704 // keyboard except if key modifiers
705 // (like Win, Alt, Ctrl) are activated. In this case we will send the
706 // virtual key code to allow key
707 // combinations (like Win + E to open the explorer).
708 @Override public boolean onKeyDown(int keycode, KeyEvent event)
709 {
710 if (keycode == KeyEvent.KEYCODE_BACK)
711 return super.onKeyDown(keycode, event);
712 return inputManager.onAndroidKeyEvent(event);
713 }
714
715 @Override public boolean onKeyUp(int keycode, KeyEvent event)
716 {
717 if (keycode == KeyEvent.KEYCODE_BACK)
718 return super.onKeyUp(keycode, event);
719 return inputManager.onAndroidKeyEvent(event);
720 }
721
722 // onKeyMultiple is called for input of some special characters like umlauts
723 // and some symbol characters
724 @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)
725 {
726 return inputManager.onAndroidKeyEvent(event);
727 }
728
729 // ****************************************************************************
730 // KeyboardMapper.KeyProcessingListener — delegated to SessionInputManager
731
732 // ****************************************************************************
733 // LibFreeRDP UI event listener implementation
734 @Override public void OnSettingsChanged(int width, int height, int bpp)
735 {
736
737 if (bpp > 16)
738 bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
739 else
740 bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
741
742 session.setSurface(new BitmapDrawable(getResources(), bitmap));
743
744 if (inputManager != null)
745 inputManager.setBitmap(bitmap);
746
747 if (session.getBookmark() == null)
748 {
749 // Return immediately if we launch from URI
750 return;
751 }
752 // check this settings and initial settings - if they are not equal the
753 // server doesn't support our settings
754 // FIXME: the additional check (settings.getWidth() != width + 1) is for
755 // the RDVH bug fix to avoid accidental notifications
756 // (refer to android_freerdp.c for more info on this problem)
757 BookmarkBase.ScreenSettings settings = session.getBookmark().getActiveScreenSettings();
758 if ((settings.getWidth() != width && settings.getWidth() != width + 1) ||
759 settings.getHeight() != height || settings.getColors() != bpp)
760 uiHandler.sendMessage(Message.obtain(
761 null, DISPLAY_TOAST, getResources().getText(R.string.info_capabilities_changed)));
762 }
763
764 @Override public void OnGraphicsUpdate(int x, int y, int width, int height)
765 {
766 LibFreeRDP.updateGraphics(session.getInstance(), bitmap, x, y, width, height);
767
768 sessionView.addInvalidRegion(new Rect(x, y, x + width, y + height));
769
770 /*
771 * since sessionView can only be modified from the UI thread any
772 * modifications to it need to be scheduled
773 */
774
775 uiHandler.sendEmptyMessage(REFRESH_SESSIONVIEW);
776 }
777
778 @Override public void OnGraphicsResize(int width, int height, int bpp)
779 {
780 // replace bitmap
781 if (bpp > 16)
782 bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
783 else
784 bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
785 session.setSurface(new BitmapDrawable(getResources(), bitmap));
786
787 if (inputManager != null)
788 inputManager.setBitmap(bitmap);
789
790 /*
791 * since sessionView can only be modified from the UI thread any
792 * modifications to it need to be scheduled
793 */
794 uiHandler.sendEmptyMessage(GRAPHICS_CHANGED);
795 }
796
797 @Override
798 public boolean OnAuthenticate(StringBuilder username, StringBuilder domain,
799 StringBuilder password)
800 {
801 return dialogs.promptCredentials(username, domain, password);
802 }
803
804 @Override
805 public boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain,
806 StringBuilder password)
807 {
808 return dialogs.promptCredentials(username, domain, password);
809 }
810
811 @Override
812 public int OnVerifiyCertificateEx(String host, long port, String commonName, String subject,
813 String issuer, String fingerprint, long flags)
814 {
815 if (ApplicationSettingsActivity.getAcceptAllCertificates(this))
816 return 0;
817 return dialogs.verifyCertificate(host, port, subject, issuer, fingerprint, flags);
818 }
819
820 @Override
821 public int OnVerifyChangedCertificateEx(String host, long port, String commonName,
822 String subject, String issuer, String fingerprint,
823 String oldSubject, String oldIssuer,
824 String oldFingerprint, long flags)
825 {
826 if (ApplicationSettingsActivity.getAcceptAllCertificates(this))
827 return 0;
828 return dialogs.verifyChangedCertificate(host, port, subject, issuer, fingerprint, flags);
829 }
830
831 @Override public boolean OnExperimentalFeature(int feature)
832 {
833 final String featureKey;
834 final String displayName;
835 switch (feature)
836 {
837 case LibFreeRDP.EXPERIMENTAL_REMOTEAPP:
838 featureKey = "remoteapp";
839 displayName = getString(R.string.experimental_feature_remoteapp);
840 break;
841 case LibFreeRDP.EXPERIMENTAL_CAMERA:
842 featureKey = "camera";
843 displayName = getString(R.string.experimental_feature_camera);
844 break;
845 default:
846 return true;
847 }
848 if (ApplicationSettingsActivity.isExperimentalEnabled(this, featureKey))
849 return true;
850 // suppress the generic failure toast; the dialog explains the abort
851 connectCancelledByUser = true;
852 dialogs.showExperimentalBlocked(displayName);
853 return false;
854 }
855
856 @Override public void OnRemoteClipboardChanged(String data)
857 {
858 Log.v(TAG, "OnRemoteClipboardChanged: " + data);
859 mClipboardManager.setClipboardData(data);
860 }
861
862 @Override public void OnRemoteClipboardImageChanged(byte[] data)
863 {
864 Log.v(TAG, "OnRemoteClipboardImageChanged: " + data.length + " bytes");
865 mClipboardManager.setClipboardImage(data);
866 }
867
868 @Override public void OnPointerSet(int[] pixels, int width, int height, int hotX, int hotY)
869 {
870 Bundle data = new Bundle();
871 data.putIntArray("pixels", pixels);
872 data.putInt("width", width);
873 data.putInt("height", height);
874 data.putInt("hotX", hotX);
875 data.putInt("hotY", hotY);
876 Message msg = uiHandler.obtainMessage(POINTER_SET);
877 msg.setData(data);
878 uiHandler.sendMessage(msg);
879 }
880
881 @Override public void OnPointerSetNull()
882 {
883 uiHandler.sendEmptyMessage(POINTER_SET);
884 }
885
886 @Override public void OnPointerSetDefault()
887 {
888 sessionView.setDefaultCursor();
889 }
890
891 @Override public void OnRailWindowUpdate(long windowId, int width, int height, int[] pixels)
892 {
893 railManager.onWindowUpdate(windowId, width, height, pixels);
894 }
895
896 @Override public void OnRailWindowMove(long windowId, int x, int y, int w, int h)
897 {
898 railManager.onWindowMove(windowId, x, y, w, h);
899 }
900
901 @Override public void OnRailWindowHide(long windowId)
902 {
903 railManager.onWindowHide(windowId);
904 }
905
906 @Override public void OnRailWindowDestroy(long windowId)
907 {
908 railManager.onWindowDestroy(windowId);
909 }
910
911 @Override public void OnRailSessionEnd()
912 {
913 railManager.onSessionEnd();
914 }
915
916 @Override public void OnRailMonitoredDesktop(long[] windowIds, long activeWindowId)
917 {
918 railManager.onMonitoredDesktop(windowIds, activeWindowId);
919 }
920
921 // ****************************************************************************
922 // SessionView.SessionViewListener and TouchPointerView.TouchPointerListener
923 // — delegated to SessionInputManager
924
925 @Override public boolean onGenericMotionEvent(MotionEvent e)
926 {
927 super.onGenericMotionEvent(e);
928 return inputManager != null && inputManager.onGenericMotionEvent(e);
929 }
930
931 // ****************************************************************************
932 // ClipboardManagerProxy.OnClipboardChangedListener
933 @Override public void onClipboardChanged(String data)
934 {
935 Log.v(TAG, "onClipboardChanged: " + data);
936 if (session != null)
937 LibFreeRDP.sendClipboardData(session.getInstance(), data);
938 }
939
940 @Override public void onClipboardImageChanged(byte[] data, String mimeType)
941 {
942 if (session != null && data != null)
943 LibFreeRDP.sendClipboardImageData(session.getInstance(), data, mimeType);
944 }
945
946 private void onConnectionStateChanged(SessionViewModel.ConnectionState state)
947 {
948 if (session == null)
949 return;
950 switch (state)
951 {
952 case CONNECTED:
953 onSessionConnected();
954 break;
955 case FAILED:
956 onSessionFailed();
957 break;
958 case DISCONNECTED:
959 onSessionDisconnected();
960 break;
961 default:
962 break;
963 }
964 }
965
966 private void onSessionConnected()
967 {
968 Log.v(TAG, "onSessionConnected");
969
970 if (connectCancelledByUser)
971 {
972 LibFreeRDP.disconnect(session.getInstance());
973 closeSessionActivity(RESULT_CANCELED);
974 return;
975 }
976
977 // bind session
978 bindSession();
979
980 if (ApplicationSettingsActivity.getKeepScreenOnWhenConnected(this))
981 {
982 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
983 }
984
985 dialogs.dismissProgress();
986
987 if (session.getBookmark() == null)
988 {
989 // Return immediately if we launch from URI
990 return;
991 }
992
993 // add hostname to history if quick connect was used
994 Bundle bundle = getIntent().getExtras();
995 if (bundle != null && bundle.containsKey(PARAM_CONNECTION_REFERENCE))
996 {
997 if (ConnectionReference.isHostnameReference(
998 bundle.getString(PARAM_CONNECTION_REFERENCE)))
999 {
1000 assert session.getBookmark().getType() == BookmarkBase.TYPE_MANUAL;
1001 sessionViewModel.recordQuickConnectHistory(session.getBookmark().getHostname());
1002 }
1003 }
1004 }
1005
1006 private void onSessionFailed()
1007 {
1008 Log.v(TAG, "onSessionFailed");
1009
1010 // cancel any pending input events
1011 if (inputManager != null)
1012 inputManager.cancelPendingEvents();
1013
1014 dialogs.dismissProgress();
1015
1016 // post error message on UI thread
1017 if (!connectCancelledByUser)
1018 uiHandler.sendMessage(Message.obtain(
1019 null, DISPLAY_TOAST, getResources().getText(R.string.error_connection_failure)));
1020
1021 closeSessionActivity(RESULT_CANCELED);
1022 }
1023
1024 private void onSessionDisconnected()
1025 {
1026 Log.v(TAG, "onSessionDisconnected");
1027
1028 // cancel any pending input events
1029 if (inputManager != null)
1030 inputManager.cancelPendingEvents();
1031
1032 if (ApplicationSettingsActivity.getKeepScreenOnWhenConnected(this))
1033 {
1034 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1035 }
1036
1037 dialogs.dismissProgress();
1038
1039 railManager.clear();
1040
1041 session.setUIEventListener(null);
1042 closeSessionActivity(RESULT_OK);
1043 }
1044}
boolean promptCredentials(StringBuilder username, StringBuilder domain, StringBuilder password)