FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
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.domain.ManualBookmark;
24import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity;
25
26import java.util.ArrayList;
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_send_clipboard_data(long inst, String data);
155
156 private static native String freerdp_get_last_error_string(long inst);
157
158 public static void setEventListener(EventListener l)
159 {
160 listener = l;
161 }
162
163 public static long newInstance(Context context)
164 {
165 return freerdp_new(context);
166 }
167
168 public static void freeInstance(long inst)
169 {
170 synchronized (mInstanceState)
171 {
172 if (mInstanceState.get(inst, false))
173 {
174 freerdp_disconnect(inst);
175 }
176 while (mInstanceState.get(inst, false))
177 {
178 try
179 {
180 mInstanceState.wait();
181 }
182 catch (InterruptedException e)
183 {
184 throw new RuntimeException();
185 }
186 }
187 }
188 freerdp_free(inst);
189 }
190
191 public static boolean connect(long inst)
192 {
193 synchronized (mInstanceState)
194 {
195 if (mInstanceState.get(inst, false))
196 {
197 throw new RuntimeException("instance already connected");
198 }
199 }
200 return freerdp_connect(inst);
201 }
202
203 public static boolean disconnect(long inst)
204 {
205 synchronized (mInstanceState)
206 {
207 if (mInstanceState.get(inst, false))
208 {
209 return freerdp_disconnect(inst);
210 }
211 return true;
212 }
213 }
214
215 public static boolean cancelConnection(long inst)
216 {
217 synchronized (mInstanceState)
218 {
219 if (mInstanceState.get(inst, false))
220 {
221 return freerdp_disconnect(inst);
222 }
223 return true;
224 }
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.<ManualBookmark>get().getPort();
260 String hostname = bookmark.<ManualBookmark>get().getHostname();
261
262 args.add("/v:" + hostname);
263 args.add("/port:" + port);
264
265 arg = bookmark.getUsername();
266 if (!arg.isEmpty())
267 {
268 args.add("/u:" + arg);
269 }
270 arg = bookmark.getDomain();
271 if (!arg.isEmpty())
272 {
273 args.add("/d:" + arg);
274 }
275 arg = bookmark.getPassword();
276 if (!arg.isEmpty())
277 {
278 args.add("/p:" + arg);
279 }
280
281 args.add(
282 String.format("/size:%dx%d", screenSettings.getWidth(), screenSettings.getHeight()));
283 args.add("/bpp:" + screenSettings.getColors());
284
285 if (advanced.getConsoleMode())
286 {
287 args.add("/admin");
288 }
289
290 switch (advanced.getSecurity())
291 {
292 case 3: // NLA
293 args.add("/sec:nla");
294 break;
295 case 2: // TLS
296 args.add("/sec:tls");
297 break;
298 case 1: // RDP
299 args.add("/sec:rdp");
300 break;
301 default:
302 break;
303 }
304
305 if (!certName.isEmpty())
306 {
307 args.add("/cert-name:" + certName);
308 }
309
310 BookmarkBase.PerformanceFlags flags = bookmark.getActivePerformanceFlags();
311 if (flags.getRemoteFX())
312 {
313 args.add("/rfx");
314 }
315
316 if (flags.getGfx())
317 {
318 args.add("/gfx");
319 }
320
321 if (flags.getH264() && mHasH264)
322 {
323 args.add("/gfx:AVC444");
324 }
325
326 args.add(addFlag("wallpaper", flags.getWallpaper()));
327 args.add(addFlag("window-drag", flags.getFullWindowDrag()));
328 args.add(addFlag("menu-anims", flags.getMenuAnimations()));
329 args.add(addFlag("themes", flags.getTheming()));
330 args.add(addFlag("fonts", flags.getFontSmoothing()));
331 args.add(addFlag("aero", flags.getDesktopComposition()));
332
333 if (!advanced.getRemoteProgram().isEmpty())
334 {
335 args.add("/shell:" + advanced.getRemoteProgram());
336 }
337
338 if (!advanced.getWorkDir().isEmpty())
339 {
340 args.add("/shell-dir:" + advanced.getWorkDir());
341 }
342
343 args.add(addFlag("async-channels", debug.getAsyncChannel()));
344 args.add(addFlag("async-update", debug.getAsyncUpdate()));
345
346 if (advanced.getRedirectSDCard())
347 {
348 String path = android.os.Environment.getExternalStorageDirectory().getPath();
349 args.add("/drive:sdcard," + path);
350 }
351
352 args.add("/clipboard");
353
354 // Gateway enabled?
355 if (bookmark.getType() == BookmarkBase.TYPE_MANUAL &&
356 bookmark.<ManualBookmark>get().getEnableGatewaySettings())
357 {
358 ManualBookmark.GatewaySettings gateway =
359 bookmark.<ManualBookmark>get().getGatewaySettings();
360
361 args.add(String.format("/g:%s:%d", gateway.getHostname(), gateway.getPort()));
362
363 arg = gateway.getUsername();
364 if (!arg.isEmpty())
365 {
366 args.add("/gu:" + arg);
367 }
368 arg = gateway.getDomain();
369 if (!arg.isEmpty())
370 {
371 args.add("/gd:" + arg);
372 }
373 arg = gateway.getPassword();
374 if (!arg.isEmpty())
375 {
376 args.add("/gp:" + arg);
377 }
378 }
379
380 /* 0 ... local
381 1 ... remote
382 2 ... disable */
383 args.add("/audio-mode:" + advanced.getRedirectSound());
384 if (advanced.getRedirectSound() == 0)
385 {
386 args.add("/sound");
387 }
388
389 if (advanced.getRedirectMicrophone())
390 {
391 args.add("/microphone");
392 }
393
394 args.add("/kbd:unicode:on");
395 args.add("/cert:ignore");
396 args.add("/log-level:" + debug.getDebugLevel());
397 String[] arrayArgs = args.toArray(new String[0]);
398 return freerdp_parse_arguments(inst, arrayArgs);
399 }
400
401 public static boolean setConnectionInfo(Context context, long inst, Uri openUri)
402 {
403 ArrayList<String> args = new ArrayList<>();
404
405 // Parse URI from query string. Same key overwrite previous one
406 // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=-
407
408 // Now we only support Software GDI
409 args.add(TAG);
410 args.add("/gdi:sw");
411
412 final String clientName = ApplicationSettingsActivity.getClientName(context);
413 if (!clientName.isEmpty())
414 {
415 args.add("/client-hostname:" + clientName);
416 }
417
418 // Parse hostname and port. Set to 'v' argument
419 String hostname = openUri.getHost();
420 int port = openUri.getPort();
421 if (hostname != null)
422 {
423 hostname = hostname + ((port == -1) ? "" : (":" + port));
424 args.add("/v:" + hostname);
425 }
426
427 String user = openUri.getUserInfo();
428 if (user != null)
429 {
430 args.add("/u:" + user);
431 }
432
433 for (String key : openUri.getQueryParameterNames())
434 {
435 String value = openUri.getQueryParameter(key);
436
437 if (value.isEmpty())
438 {
439 // Query: key=
440 // To freerdp argument: /key
441 args.add("/" + key);
442 }
443 else if (value.equals("-") || value.equals("+"))
444 {
445 // Query: key=- or key=+
446 // To freerdp argument: -key or +key
447 args.add(value + key);
448 }
449 else
450 {
451 // Query: key=value
452 // To freerdp argument: /key:value
453 if (key.equals("drive") && value.equals("sdcard"))
454 {
455 // Special for sdcard redirect
456 String path = android.os.Environment.getExternalStorageDirectory().getPath();
457 value = "sdcard," + path;
458 }
459
460 args.add("/" + key + ":" + value);
461 }
462 }
463
464 String[] arrayArgs = args.toArray(new String[0]);
465 return freerdp_parse_arguments(inst, arrayArgs);
466 }
467
468 public static boolean updateGraphics(long inst, Bitmap bitmap, int x, int y, int width,
469 int height)
470 {
471 return freerdp_update_graphics(inst, bitmap, x, y, width, height);
472 }
473
474 public static boolean sendCursorEvent(long inst, int x, int y, int flags)
475 {
476 return freerdp_send_cursor_event(inst, x, y, flags);
477 }
478
479 public static boolean sendKeyEvent(long inst, int keycode, boolean down)
480 {
481 return freerdp_send_key_event(inst, keycode, down);
482 }
483
484 public static boolean sendUnicodeKeyEvent(long inst, int keycode, boolean down)
485 {
486 return freerdp_send_unicodekey_event(inst, keycode, down);
487 }
488
489 public static boolean sendClipboardData(long inst, String data)
490 {
491 return freerdp_send_clipboard_data(inst, data);
492 }
493
494 private static void OnConnectionSuccess(long inst)
495 {
496 if (listener != null)
497 listener.OnConnectionSuccess(inst);
498 synchronized (mInstanceState)
499 {
500 mInstanceState.append(inst, true);
501 mInstanceState.notifyAll();
502 }
503 }
504
505 private static void OnConnectionFailure(long inst)
506 {
507 if (listener != null)
508 listener.OnConnectionFailure(inst);
509 synchronized (mInstanceState)
510 {
511 mInstanceState.remove(inst);
512 mInstanceState.notifyAll();
513 }
514 }
515
516 private static void OnPreConnect(long inst)
517 {
518 if (listener != null)
519 listener.OnPreConnect(inst);
520 }
521
522 private static void OnDisconnecting(long inst)
523 {
524 if (listener != null)
525 listener.OnDisconnecting(inst);
526 }
527
528 private static void OnDisconnected(long inst)
529 {
530 if (listener != null)
531 listener.OnDisconnected(inst);
532 synchronized (mInstanceState)
533 {
534 mInstanceState.remove(inst);
535 mInstanceState.notifyAll();
536 }
537 }
538
539 private static void OnSettingsChanged(long inst, int width, int height, int bpp)
540 {
541 SessionState s = GlobalApp.getSession(inst);
542 if (s == null)
543 return;
544 UIEventListener uiEventListener = s.getUIEventListener();
545 if (uiEventListener != null)
546 uiEventListener.OnSettingsChanged(width, height, bpp);
547 }
548
549 private static boolean OnAuthenticate(long inst, StringBuilder username, StringBuilder domain,
550 StringBuilder password)
551 {
552 SessionState s = GlobalApp.getSession(inst);
553 if (s == null)
554 return false;
555 UIEventListener uiEventListener = s.getUIEventListener();
556 if (uiEventListener != null)
557 return uiEventListener.OnAuthenticate(username, domain, password);
558 return false;
559 }
560
561 private static boolean OnGatewayAuthenticate(long inst, StringBuilder username,
562 StringBuilder domain, StringBuilder password)
563 {
564 SessionState s = GlobalApp.getSession(inst);
565 if (s == null)
566 return false;
567 UIEventListener uiEventListener = s.getUIEventListener();
568 if (uiEventListener != null)
569 return uiEventListener.OnGatewayAuthenticate(username, domain, password);
570 return false;
571 }
572
573 private static int OnVerifyCertificateEx(long inst, String host, long port, String commonName,
574 String subject, String issuer, String fingerprint,
575 long flags)
576 {
577 SessionState s = GlobalApp.getSession(inst);
578 if (s == null)
579 return 0;
580 UIEventListener uiEventListener = s.getUIEventListener();
581 if (uiEventListener != null)
582 return uiEventListener.OnVerifiyCertificateEx(host, port, commonName, subject, issuer,
583 fingerprint, flags);
584 return 0;
585 }
586
587 private static int OnVerifyChangedCertificateEx(long inst, String host, long port,
588 String commonName, String subject,
589 String issuer, String fingerprint,
590 String oldSubject, String oldIssuer,
591 String oldFingerprint, long flags)
592 {
593 SessionState s = GlobalApp.getSession(inst);
594 if (s == null)
595 return 0;
596 UIEventListener uiEventListener = s.getUIEventListener();
597 if (uiEventListener != null)
598 return uiEventListener.OnVerifyChangedCertificateEx(host, port, commonName, subject,
599 issuer, fingerprint, oldSubject,
600 oldIssuer, oldFingerprint, flags);
601 return 0;
602 }
603
604 private static void OnGraphicsUpdate(long inst, int x, int y, int width, int height)
605 {
606 SessionState s = GlobalApp.getSession(inst);
607 if (s == null)
608 return;
609 UIEventListener uiEventListener = s.getUIEventListener();
610 if (uiEventListener != null)
611 uiEventListener.OnGraphicsUpdate(x, y, width, height);
612 }
613
614 private static void OnGraphicsResize(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.OnGraphicsResize(width, height, bpp);
622 }
623
624 private static void OnRemoteClipboardChanged(long inst, String data)
625 {
626 SessionState s = GlobalApp.getSession(inst);
627 if (s == null)
628 return;
629 UIEventListener uiEventListener = s.getUIEventListener();
630 if (uiEventListener != null)
631 uiEventListener.OnRemoteClipboardChanged(data);
632 }
633
634 public static String getVersion()
635 {
636 return freerdp_get_version();
637 }
638
639 public interface EventListener
640 {
641 void OnPreConnect(long instance);
642
643 void OnConnectionSuccess(long instance);
644
645 void OnConnectionFailure(long instance);
646
647 void OnDisconnecting(long instance);
648
649 void OnDisconnected(long instance);
650 }
651
652 public interface UIEventListener
653 {
654 void OnSettingsChanged(int width, int height, int bpp);
655
656 boolean OnAuthenticate(StringBuilder username, StringBuilder domain,
657 StringBuilder password);
658
659 boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain,
660 StringBuilder password);
661
662 int OnVerifiyCertificateEx(String host, long port, String commonName, String subject, String issuer,
663 String fingerprint, long flags);
664
665 int OnVerifyChangedCertificateEx(String host, long port, String commonName, String subject, String issuer,
666 String fingerprint, String oldSubject, String oldIssuer,
667 String oldFingerprint, long flags);
668
669 void OnGraphicsUpdate(int x, int y, int width, int height);
670
671 void OnGraphicsResize(int width, int height, int bpp);
672
673 void OnRemoteClipboardChanged(String data);
674 }
675}