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