FreeRDP
Loading...
Searching...
No Matches
LibFreeRDP.java
1/*
2 Android FreeRDP JNI Wrapper
3
4 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5
6 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7 If a copy of the MPL was not distributed with this file, You can obtain one at
8 http://mozilla.org/MPL/2.0/.
9*/
10
11package com.freerdp.freerdpcore.services;
12
13import android.content.Context;
14import android.graphics.Bitmap;
15import android.net.Uri;
16import android.util.Log;
17
18import androidx.collection.LongSparseArray;
19
20import com.freerdp.freerdpcore.application.GlobalApp;
21import com.freerdp.freerdpcore.application.SessionState;
22import com.freerdp.freerdpcore.domain.BookmarkBase;
23import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity;
24
25import java.util.ArrayList;
26import java.util.List;
27import java.util.Objects;
28import java.util.regex.Matcher;
29import java.util.regex.Pattern;
30
31public class LibFreeRDP
32{
33 private static final String TAG = "LibFreeRDP";
34 private static EventListener listener;
35 private static boolean mHasH264 = false;
36
37 private static final LongSparseArray<Boolean> mInstanceState = new LongSparseArray<>();
38
39 public static final long VERIFY_CERT_FLAG_NONE = 0x00;
40 public static final long VERIFY_CERT_FLAG_LEGACY = 0x02;
41 public static final long VERIFY_CERT_FLAG_REDIRECT = 0x10;
42 public static final long VERIFY_CERT_FLAG_GATEWAY = 0x20;
43 public static final long VERIFY_CERT_FLAG_CHANGED = 0x40;
44 public static final long VERIFY_CERT_FLAG_MISMATCH = 0x80;
45 public static final long VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1 = 0x100;
46 public static final long VERIFY_CERT_FLAG_FP_IS_PEM = 0x200;
47
48 private static boolean tryLoad(String[] libraries)
49 {
50 boolean success = false;
51 final String LD_PATH = System.getProperty("java.library.path");
52 for (String lib : libraries)
53 {
54 try
55 {
56 Log.v(TAG, "Trying to load library " + lib + " from LD_PATH: " + LD_PATH);
57 System.loadLibrary(lib);
58 success = true;
59 }
60 catch (UnsatisfiedLinkError e)
61 {
62 Log.e(TAG, "Failed to load library " + lib + ": " + e);
63 success = false;
64 break;
65 }
66 }
67
68 return success;
69 }
70
71 private static boolean tryLoad(String library)
72 {
73 return tryLoad(new String[] { library });
74 }
75
76 static
77 {
78 try
79 {
80 System.loadLibrary("freerdp-android");
81
82 /* Load dependent libraries too to trigger JNI_OnLoad calls */
83 String version = freerdp_get_jni_version();
84 String[] versions = version.split("[\\.-]");
85 if (versions.length > 0)
86 {
87 System.loadLibrary("freerdp-client" + versions[0]);
88 System.loadLibrary("freerdp" + versions[0]);
89 System.loadLibrary("winpr" + versions[0]);
90 }
91 Pattern pattern = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+).*");
92 Matcher matcher = pattern.matcher(version);
93 if (!matcher.matches() || (matcher.groupCount() < 3))
94 throw new RuntimeException("APK broken: native library version " + version +
95 " does not meet requirements!");
96 int major = Integer.parseInt(Objects.requireNonNull(matcher.group(1)));
97 int minor = Integer.parseInt(Objects.requireNonNull(matcher.group(2)));
98 int patch = Integer.parseInt(Objects.requireNonNull(matcher.group(3)));
99
100 if (major > 2)
101 mHasH264 = freerdp_has_h264();
102 else if (minor > 5)
103 mHasH264 = freerdp_has_h264();
104 else if ((minor == 5) && (patch >= 1))
105 mHasH264 = freerdp_has_h264();
106 else
107 throw new RuntimeException("APK broken: native library version " + version +
108 " does not meet requirements!");
109 Log.i(TAG, "Successfully loaded native library. H264 is " +
110 (mHasH264 ? "supported" : "not available"));
111 }
112 catch (UnsatisfiedLinkError e)
113 {
114 Log.e(TAG, "Failed to load library: " + e);
115 throw e;
116 }
117 }
118
119 public static boolean hasH264Support()
120 {
121 return mHasH264;
122 }
123
124 private static native boolean freerdp_has_h264();
125
126 private static native String freerdp_get_jni_version();
127
128 private static native String freerdp_get_version();
129
130 private static native String freerdp_get_build_revision();
131
132 private static native String freerdp_get_build_config();
133
134 private static native long freerdp_new(Context context);
135
136 private static native void freerdp_free(long inst);
137
138 private static native boolean freerdp_parse_arguments(long inst, String[] args);
139
140 private static native boolean freerdp_connect(long inst);
141
142 private static native boolean freerdp_disconnect(long inst);
143
144 private static native boolean freerdp_update_graphics(long inst, Bitmap bitmap, int x, int y,
145 int width, int height);
146
147 private static native boolean freerdp_send_cursor_event(long inst, int x, int y, int flags);
148
149 private static native boolean freerdp_send_key_event(long inst, int keycode, boolean down);
150
151 private static native boolean freerdp_send_unicodekey_event(long inst, int keycode,
152 boolean down);
153
154 private static native boolean freerdp_is_unicode_input_supported(long inst);
155
156 private static native boolean freerdp_send_clipboard_data(long inst, String data);
157
158 private static native boolean freerdp_send_clipboard_image_data(long inst, byte[] data,
159 String mimeType);
160
161 private static native boolean freerdp_send_monitor_layout(long inst, int width, int height);
162
163 private static native String freerdp_get_last_error_string(long inst);
164
165 public static void setEventListener(EventListener l)
166 {
167 listener = l;
168 }
169
170 public static long newInstance(Context context)
171 {
172 return freerdp_new(context);
173 }
174
175 public static void freeInstance(long inst)
176 {
177 synchronized (mInstanceState)
178 {
179 if (mInstanceState.get(inst, false))
180 {
181 freerdp_disconnect(inst);
182 }
183 while (mInstanceState.get(inst, false))
184 {
185 try
186 {
187 mInstanceState.wait();
188 }
189 catch (InterruptedException e)
190 {
191 throw new RuntimeException();
192 }
193 }
194 }
195 freerdp_free(inst);
196 }
197
198 public static boolean connect(long inst)
199 {
200 synchronized (mInstanceState)
201 {
202 if (mInstanceState.get(inst, false))
203 {
204 throw new RuntimeException("instance already connected");
205 }
206 }
207 return freerdp_connect(inst);
208 }
209
210 public static boolean disconnect(long inst)
211 {
212 synchronized (mInstanceState)
213 {
214 if (mInstanceState.get(inst, false))
215 {
216 return freerdp_disconnect(inst);
217 }
218 return true;
219 }
220 }
221
222 public static boolean cancelConnection(long inst)
223 {
224 return freerdp_disconnect(inst);
225 }
226
227 private static String addFlag(String name, boolean enabled)
228 {
229 if (enabled)
230 {
231 return "+" + name;
232 }
233 return "-" + name;
234 }
235
236 public static boolean setConnectionInfo(Context context, long inst, BookmarkBase bookmark)
237 {
238 BookmarkBase.ScreenSettings screenSettings = bookmark.getActiveScreenSettings();
239 BookmarkBase.AdvancedSettings advanced = bookmark.getAdvancedSettings();
240 BookmarkBase.DebugSettings debug = bookmark.getDebugSettings();
241
242 String arg;
243 ArrayList<String> args = new ArrayList<>();
244
245 args.add(TAG);
246 args.add("/gdi:sw");
247
248 final String clientName = ApplicationSettingsActivity.getClientName(context);
249 if (!clientName.isEmpty())
250 {
251 args.add("/client-hostname:" + clientName);
252 }
253 String certName = "";
254 if (bookmark.getType() != BookmarkBase.TYPE_MANUAL)
255 {
256 return false;
257 }
258
259 int port = bookmark.getPort();
260 String hostname = bookmark.getHostname();
261
262 args.add("/v:" + hostname);
263 args.add("/port:" + port);
264
265 final int level = advanced.getTlsSecLevel();
266 List<String> tls = new ArrayList<>();
267
268 if (level >= 0)
269 {
270 tls.add("seclevel:" + level);
271 }
272
273 final int tlsMinLevel = advanced.getTlsMinLevel();
274 if (tlsMinLevel >= 0)
275 {
276 tls.add("enforce:" + tlsMinLevel);
277 }
278
279 if (!tls.isEmpty())
280 {
281 StringBuilder sb = new StringBuilder();
282 for (String s : tls)
283 {
284 if (sb.length() > 0)
285 {
286 sb.append(',');
287 }
288 sb.append(s);
289 }
290 args.add("/tls:" + sb);
291 }
292
293 arg = bookmark.getUsername();
294 if (!arg.isEmpty())
295 {
296 args.add("/u:" + arg);
297 }
298 arg = bookmark.getDomain();
299 if (!arg.isEmpty())
300 {
301 args.add("/d:" + arg);
302 }
303 arg = bookmark.getPassword();
304 if (!arg.isEmpty())
305 {
306 args.add("/p:" + arg);
307 }
308
309 args.add(String.format(java.util.Locale.US, "/size:%dx%d", screenSettings.getWidth(),
310 screenSettings.getHeight()));
311 args.add("/bpp:" + screenSettings.getColors());
312
313 if (screenSettings.isCustomScale())
314 {
315 args.add("/scale-desktop:" + screenSettings.getScaleDesktop());
316 args.add("/scale-device:" + screenSettings.getScaleDevice());
317 }
318 else
319 {
320 args.add("/scale:" + screenSettings.getScalePreset());
321 }
322
323 if (advanced.getConsoleMode())
324 {
325 args.add("/admin");
326 }
327
328 if (advanced.getVmConnectMode())
329 {
330 String guid = advanced.getVmConnectGuid();
331 if (!guid.isEmpty())
332 args.add("/vmconnect:" + guid);
333 else
334 args.add("/vmconnect");
335 }
336
337 switch (advanced.getSecurity())
338 {
339 case 3: // NLA
340 args.add("/sec:nla");
341 break;
342 case 2: // TLS
343 args.add("/sec:tls");
344 break;
345 case 1: // RDP
346 args.add("/sec:rdp");
347 break;
348 default:
349 break;
350 }
351
352 if (!certName.isEmpty())
353 {
354 args.add("/cert-name:" + certName);
355 }
356
357 BookmarkBase.PerformanceFlags flags = bookmark.getActivePerformanceFlags();
358 if (flags.getRemoteFX())
359 {
360 args.add("/rfx");
361 args.add("/network:auto");
362 }
363
364 if (flags.getGfx())
365 {
366 args.add("/gfx");
367 args.add("/network:auto");
368 }
369
370 if (flags.getH264() && mHasH264)
371 {
372 args.add("/gfx:AVC444");
373 args.add("/network:auto");
374 }
375
376 args.add(addFlag("wallpaper", flags.getWallpaper()));
377 args.add(addFlag("window-drag", flags.getFullWindowDrag()));
378 args.add(addFlag("menu-anims", flags.getMenuAnimations()));
379 args.add(addFlag("themes", flags.getTheming()));
380 args.add(addFlag("fonts", flags.getFontSmoothing()));
381 args.add(addFlag("aero", flags.getDesktopComposition()));
382
383 if (!advanced.getRemoteProgram().isEmpty())
384 {
385 args.add("/shell:" + advanced.getRemoteProgram());
386 }
387
388 if (!advanced.getWorkDir().isEmpty())
389 {
390 args.add("/shell-dir:" + advanced.getWorkDir());
391 }
392
393 args.add(addFlag("async-channels", debug.getAsyncChannel()));
394 args.add(addFlag("async-update", debug.getAsyncUpdate()));
395
396 if (advanced.getRedirectSDCard())
397 {
398 String path = android.os.Environment.getExternalStorageDirectory().getPath();
399 args.add("/drive:sdcard," + path);
400 }
401
402 String info = advanced.getLoadBalanceInfo();
403 if (!info.isEmpty())
404 {
405 args.add("/load-balance-info:" + info);
406 }
407 args.add("/clipboard");
408 args.add("/disp");
409
410 if (advanced.getRedirectPrinter())
411 args.add("/printer:aFreeRDP Print,Microsoft Print to PDF,default");
412
413 // Gateway enabled?
414 if (bookmark.getType() == BookmarkBase.TYPE_MANUAL && bookmark.getEnableGatewaySettings())
415 {
416 BookmarkBase.GatewaySettings gateway = bookmark.getGatewaySettings();
417
418 StringBuilder carg = new StringBuilder();
419 carg.append(String.format(java.util.Locale.US, "/gateway:g:%s:%d",
420 gateway.getHostname(), gateway.getPort()));
421
422 arg = gateway.getUsername();
423 if (!arg.isEmpty())
424 {
425 carg.append(",u:" + arg);
426 }
427 arg = gateway.getDomain();
428 if (!arg.isEmpty())
429 {
430 carg.append(",d:" + arg);
431 }
432 arg = gateway.getPassword();
433 if (!arg.isEmpty())
434 {
435 carg.append(",p:" + arg);
436 }
437 args.add(carg.toString());
438 }
439
440 /* 0 ... local
441 1 ... remote
442 2 ... disable */
443 args.add("/audio-mode:" + advanced.getRedirectSound());
444 if (advanced.getRedirectSound() == 0)
445 {
446 args.add("/sound");
447 }
448
449 if (advanced.getRedirectMicrophone())
450 {
451 args.add("/microphone");
452 }
453
454 args.add("/kbd:unicode:on");
455 args.add("/cert:ignore");
456 args.add("/log-level:" + debug.getDebugLevel());
457 String[] arrayArgs = args.toArray(new String[0]);
458 return freerdp_parse_arguments(inst, arrayArgs);
459 }
460
461 public static boolean setConnectionInfo(Context context, long inst, Uri openUri)
462 {
463 ArrayList<String> args = new ArrayList<>();
464
465 // Parse URI from query string. Same key overwrite previous one
466 // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=-
467
468 // Now we only support Software GDI
469 args.add(TAG);
470 args.add("/gdi:sw");
471
472 final String clientName = ApplicationSettingsActivity.getClientName(context);
473 if (!clientName.isEmpty())
474 {
475 args.add("/client-hostname:" + clientName);
476 }
477
478 // Parse hostname and port. Set to 'v' argument
479 String hostname = openUri.getHost();
480 int port = openUri.getPort();
481 if (hostname != null)
482 {
483 hostname = hostname + ((port == -1) ? "" : (":" + port));
484 args.add("/v:" + hostname);
485 }
486
487 String user = openUri.getUserInfo();
488 if (user != null)
489 {
490 args.add("/u:" + user);
491 }
492
493 for (String key : openUri.getQueryParameterNames())
494 {
495 String value = openUri.getQueryParameter(key);
496
497 if (value.isEmpty())
498 {
499 // Query: key=
500 // To freerdp argument: /key
501 args.add("/" + key);
502 }
503 else if (value.equals("-") || value.equals("+"))
504 {
505 // Query: key=- or key=+
506 // To freerdp argument: -key or +key
507 args.add(value + key);
508 }
509 else
510 {
511 // Query: key=value
512 // To freerdp argument: /key:value
513 if (key.equals("drive") && value.equals("sdcard"))
514 {
515 // Special for sdcard redirect
516 String path = android.os.Environment.getExternalStorageDirectory().getPath();
517 value = "sdcard," + path;
518 }
519
520 args.add("/" + key + ":" + value);
521 }
522 }
523
524 String[] arrayArgs = args.toArray(new String[0]);
525 return freerdp_parse_arguments(inst, arrayArgs);
526 }
527
528 public static boolean updateGraphics(long inst, Bitmap bitmap, int x, int y, int width,
529 int height)
530 {
531 return freerdp_update_graphics(inst, bitmap, x, y, width, height);
532 }
533
534 public static boolean sendCursorEvent(long inst, int x, int y, int flags)
535 {
536 return freerdp_send_cursor_event(inst, x, y, flags);
537 }
538
539 public static boolean sendKeyEvent(long inst, int keycode, boolean down)
540 {
541 return freerdp_send_key_event(inst, keycode, down);
542 }
543
544 public static boolean sendUnicodeKeyEvent(long inst, int keycode, boolean down)
545 {
546 return freerdp_send_unicodekey_event(inst, keycode, down);
547 }
548
549 public static boolean isUnicodeInputSupported(long inst)
550 {
551 return freerdp_is_unicode_input_supported(inst);
552 }
553
554 public static boolean sendClipboardData(long inst, String data)
555 {
556 return freerdp_send_clipboard_data(inst, data);
557 }
558
559 public static boolean sendClipboardImageData(long inst, byte[] data, String mimeType)
560 {
561 return freerdp_send_clipboard_image_data(inst, data, mimeType);
562 }
563
564 public static boolean sendMonitorLayout(long inst, int width, int height)
565 {
566 return freerdp_send_monitor_layout(inst, width, height);
567 }
568
569 private static void OnConnectionSuccess(long inst)
570 {
571 if (listener != null)
572 listener.OnConnectionSuccess(inst);
573 synchronized (mInstanceState)
574 {
575 mInstanceState.append(inst, true);
576 mInstanceState.notifyAll();
577 }
578 }
579
580 private static void OnConnectionFailure(long inst)
581 {
582 if (listener != null)
583 listener.OnConnectionFailure(inst);
584 synchronized (mInstanceState)
585 {
586 mInstanceState.remove(inst);
587 mInstanceState.notifyAll();
588 }
589 }
590
591 private static void OnPreConnect(long inst)
592 {
593 if (listener != null)
594 listener.OnPreConnect(inst);
595 }
596
597 private static void OnDisconnecting(long inst)
598 {
599 if (listener != null)
600 listener.OnDisconnecting(inst);
601 }
602
603 private static void OnDisconnected(long inst)
604 {
605 if (listener != null)
606 listener.OnDisconnected(inst);
607 synchronized (mInstanceState)
608 {
609 mInstanceState.remove(inst);
610 mInstanceState.notifyAll();
611 }
612 }
613
614 private static void OnSettingsChanged(long inst, int width, int height, int bpp)
615 {
616 SessionState s = GlobalApp.getSession(inst);
617 if (s == null)
618 return;
619 UIEventListener uiEventListener = s.getUIEventListener();
620 if (uiEventListener != null)
621 uiEventListener.OnSettingsChanged(width, height, bpp);
622 }
623
624 private static boolean OnAuthenticate(long inst, StringBuilder username, StringBuilder domain,
625 StringBuilder password)
626 {
627 SessionState s = GlobalApp.getSession(inst);
628 if (s == null)
629 return false;
630 UIEventListener uiEventListener = s.getUIEventListener();
631 if (uiEventListener != null)
632 return uiEventListener.OnAuthenticate(username, domain, password);
633 return false;
634 }
635
636 private static boolean OnGatewayAuthenticate(long inst, StringBuilder username,
637 StringBuilder domain, StringBuilder password)
638 {
639 SessionState s = GlobalApp.getSession(inst);
640 if (s == null)
641 return false;
642 UIEventListener uiEventListener = s.getUIEventListener();
643 if (uiEventListener != null)
644 return uiEventListener.OnGatewayAuthenticate(username, domain, password);
645 return false;
646 }
647
648 private static int OnVerifyCertificateEx(long inst, String host, long port, String commonName,
649 String subject, String issuer, String fingerprint,
650 long flags)
651 {
652 SessionState s = GlobalApp.getSession(inst);
653 if (s == null)
654 return 0;
655 UIEventListener uiEventListener = s.getUIEventListener();
656 if (uiEventListener != null)
657 return uiEventListener.OnVerifiyCertificateEx(host, port, commonName, subject, issuer,
658 fingerprint, flags);
659 return 0;
660 }
661
662 private static int OnVerifyChangedCertificateEx(long inst, String host, long port,
663 String commonName, String subject,
664 String issuer, String fingerprint,
665 String oldSubject, String oldIssuer,
666 String oldFingerprint, long flags)
667 {
668 SessionState s = GlobalApp.getSession(inst);
669 if (s == null)
670 return 0;
671 UIEventListener uiEventListener = s.getUIEventListener();
672 if (uiEventListener != null)
673 return uiEventListener.OnVerifyChangedCertificateEx(host, port, commonName, subject,
674 issuer, fingerprint, oldSubject,
675 oldIssuer, oldFingerprint, flags);
676 return 0;
677 }
678
679 private static void OnGraphicsUpdate(long inst, int x, int y, int width, int height)
680 {
681 SessionState s = GlobalApp.getSession(inst);
682 if (s == null)
683 return;
684 UIEventListener uiEventListener = s.getUIEventListener();
685 if (uiEventListener != null)
686 uiEventListener.OnGraphicsUpdate(x, y, width, height);
687 }
688
689 private static void OnGraphicsResize(long inst, int width, int height, int bpp)
690 {
691 SessionState s = GlobalApp.getSession(inst);
692 if (s == null)
693 return;
694 UIEventListener uiEventListener = s.getUIEventListener();
695 if (uiEventListener != null)
696 uiEventListener.OnGraphicsResize(width, height, bpp);
697 }
698
699 private static void OnRemoteClipboardChanged(long inst, String data)
700 {
701 SessionState s = GlobalApp.getSession(inst);
702 if (s == null)
703 return;
704 UIEventListener uiEventListener = s.getUIEventListener();
705 if (uiEventListener != null)
706 uiEventListener.OnRemoteClipboardChanged(data);
707 }
708
709 private static void OnRemoteClipboardImageChanged(long inst, byte[] data)
710 {
711 SessionState s = GlobalApp.getSession(inst);
712 if (s == null)
713 return;
714 UIEventListener uiEventListener = s.getUIEventListener();
715 if (uiEventListener != null)
716 uiEventListener.OnRemoteClipboardImageChanged(data);
717 }
718
719 private static void OnPointerSet(long inst, int[] pixels, int width, int height, int hotX,
720 int hotY)
721 {
722 SessionState s = GlobalApp.getSession(inst);
723 if (s == null)
724 return;
725 UIEventListener uiEventListener = s.getUIEventListener();
726 if (uiEventListener != null)
727 uiEventListener.OnPointerSet(pixels, width, height, hotX, hotY);
728 }
729
730 private static void OnPointerSetNull(long inst)
731 {
732 SessionState s = GlobalApp.getSession(inst);
733 if (s == null)
734 return;
735 UIEventListener uiEventListener = s.getUIEventListener();
736 if (uiEventListener != null)
737 uiEventListener.OnPointerSetNull();
738 }
739
740 private static void OnPointerSetDefault(long inst)
741 {
742 SessionState s = GlobalApp.getSession(inst);
743 if (s == null)
744 return;
745 UIEventListener uiEventListener = s.getUIEventListener();
746 if (uiEventListener != null)
747 uiEventListener.OnPointerSetDefault();
748 }
749
750 public static String getVersion()
751 {
752 return freerdp_get_version();
753 }
754
755 public interface EventListener
756 {
757 void OnPreConnect(long instance);
758
759 void OnConnectionSuccess(long instance);
760
761 void OnConnectionFailure(long instance);
762
763 void OnDisconnecting(long instance);
764
765 void OnDisconnected(long instance);
766 }
767
768 public interface UIEventListener
769 {
770 void OnSettingsChanged(int width, int height, int bpp);
771
772 boolean OnAuthenticate(StringBuilder username, StringBuilder domain,
773 StringBuilder password);
774
775 boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain,
776 StringBuilder password);
777
778 int OnVerifiyCertificateEx(String host, long port, String commonName, String subject, String issuer,
779 String fingerprint, long flags);
780
781 int OnVerifyChangedCertificateEx(String host, long port, String commonName, String subject, String issuer,
782 String fingerprint, String oldSubject, String oldIssuer,
783 String oldFingerprint, long flags);
784
785 void OnGraphicsUpdate(int x, int y, int width, int height);
786
787 void OnGraphicsResize(int width, int height, int bpp);
788
789 void OnRemoteClipboardChanged(String data);
790
791 void OnRemoteClipboardImageChanged(byte[] data);
792
793 void OnPointerSet(int[] pixels, int width, int height, int hotX, int hotY);
794
795 void OnPointerSetNull();
796
797 void OnPointerSetDefault();
798 }
799}