FreeRDP
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 
11 package com.freerdp.freerdpcore.services;
12 
13 import android.content.Context;
14 import android.graphics.Bitmap;
15 import android.net.Uri;
16 import android.util.Log;
17 
18 import androidx.collection.LongSparseArray;
19 
20 import com.freerdp.freerdpcore.application.GlobalApp;
21 import com.freerdp.freerdpcore.application.SessionState;
22 import com.freerdp.freerdpcore.domain.BookmarkBase;
23 import com.freerdp.freerdpcore.domain.ManualBookmark;
24 import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity;
25 
26 import java.util.ArrayList;
27 import java.util.Objects;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 
31 public 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.toString());
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.toString());
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:" + String.valueOf(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:" + String.valueOf(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  {
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:" + String.valueOf(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) ? "" : (":" + String.valueOf(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 static interface EventListener {
640  void OnPreConnect(long instance);
641 
642  void OnConnectionSuccess(long instance);
643 
644  void OnConnectionFailure(long instance);
645 
646  void OnDisconnecting(long instance);
647 
648  void OnDisconnected(long instance);
649  }
650 
651  public static interface UIEventListener {
652  void OnSettingsChanged(int width, int height, int bpp);
653 
654  boolean OnAuthenticate(StringBuilder username, StringBuilder domain,
655  StringBuilder password);
656 
657  boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain,
658  StringBuilder password);
659 
660  int OnVerifiyCertificateEx(String host, long port, String commonName, String subject, String issuer,
661  String fingerprint, long flags);
662 
663  int OnVerifyChangedCertificateEx(String host, long port, String commonName, String subject, String issuer,
664  String fingerprint, String oldSubject, String oldIssuer,
665  String oldFingerprint, long flags);
666 
667  void OnGraphicsUpdate(int x, int y, int width, int height);
668 
669  void OnGraphicsResize(int width, int height, int bpp);
670 
671  void OnRemoteClipboardChanged(String data);
672  }
673 }