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