FreeRDP
Loading...
Searching...
No Matches
android_freerdp.c
1/*
2 Android JNI Client Layer
3
4 Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
5 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
6 Copyright 2013 Thincast Technologies GmbH, Author: Armin Novak
7 Copyright 2015 Bernhard Miklautz <bernhard.miklautz@thincast.com>
8 Copyright 2016 Thincast Technologies GmbH
9 Copyright 2016 Armin Novak <armin.novak@thincast.com>
10
11 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
12 If a copy of the MPL was not distributed with this file, You can obtain one at
13 http://mozilla.org/MPL/2.0/.
14*/
15
16#include <freerdp/config.h>
17
18#include <locale.h>
19
20#include <jni.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <errno.h>
24
25#include <winpr/assert.h>
26#include <winpr/image.h>
27
28#include <freerdp/graphics.h>
29#include <freerdp/codec/rfx.h>
30#include <freerdp/gdi/gdi.h>
31#include <freerdp/gdi/gfx.h>
32#include <freerdp/client/rdpei.h>
33#include <freerdp/client/rdpgfx.h>
34#include <freerdp/client/cliprdr.h>
35#include <freerdp/codec/h264.h>
36#include <freerdp/codec/video.h>
37#include <freerdp/channels/channels.h>
38#include <freerdp/client/channels.h>
39#include <freerdp/client/cmdline.h>
40#include <freerdp/constants.h>
41#include <freerdp/locale/keyboard.h>
42#include <freerdp/primitives.h>
43#include <freerdp/version.h>
44#include <freerdp/settings.h>
45#include <freerdp/utils/signal.h>
46
47#include <android/bitmap.h>
48
49#include "android_jni_callback.h"
50#include "android_jni_utils.h"
51#include "android_cliprdr.h"
52#include "android_disp.h"
53#include "android_rail.h"
54#include "android_freerdp_jni.h"
55
56#if defined(WITH_GPROF)
57#include "jni/prof.h"
58#endif
59
60#define TAG CLIENT_TAG("android")
61
62/* Defines the JNI version supported by this library. */
63#define FREERDP_JNI_VERSION FREERDP_VERSION_FULL
64
65static jclass gJavaActivityClass;
66static jmethodID gOnPointerSetMethod;
67static jmethodID gOnRailWindowUpdateMethod;
68
69static UINT android_UpdateWindowFromSurface(RdpgfxClientContext* context, gdiGfxSurface* surface);
70
71static void android_OnChannelConnectedEventHandler(void* context,
72 const ChannelConnectedEventArgs* e)
73{
74 rdpSettings* settings;
75 androidContext* afc;
76
77 if (!context || !e)
78 {
79 WLog_FATAL(TAG, "(context=%p, EventArgs=%p", context, (void*)e);
80 return;
81 }
82
83 afc = (androidContext*)context;
84 settings = afc->common.context.settings;
85
86 if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
87 {
88 android_cliprdr_init(afc, (CliprdrClientContext*)e->pInterface);
89 }
90 else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
91 {
92 android_disp_init(afc, (DispClientContext*)e->pInterface);
93 }
94 else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
95 {
96 android_rail_init(afc, (RailClientContext*)e->pInterface);
97 }
98 else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
99 {
100 freerdp_client_OnChannelConnectedEventHandler(context, e);
101 RdpgfxClientContext* gfx = (RdpgfxClientContext*)e->pInterface;
102 if (gfx)
103 gfx->UpdateWindowFromSurface = android_UpdateWindowFromSurface;
104 }
105 else
106 freerdp_client_OnChannelConnectedEventHandler(context, e);
107}
108
109static void android_OnChannelDisconnectedEventHandler(void* context,
110 const ChannelDisconnectedEventArgs* e)
111{
112 rdpSettings* settings;
113 androidContext* afc;
114
115 if (!context || !e)
116 {
117 WLog_FATAL(TAG, "(context=%p, EventArgs=%p", context, (void*)e);
118 return;
119 }
120
121 afc = (androidContext*)context;
122 settings = afc->common.context.settings;
123
124 if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
125 {
126 android_cliprdr_uninit(afc, (CliprdrClientContext*)e->pInterface);
127 }
128 else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
129 {
130 android_disp_uninit(afc, (DispClientContext*)e->pInterface);
131 }
132 else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
133 {
134 android_rail_uninit(afc, (RailClientContext*)e->pInterface);
135 }
136 else
137 freerdp_client_OnChannelDisconnectedEventHandler(context, e);
138}
139
140static BOOL android_begin_paint(rdpContext* context)
141{
142 return TRUE;
143}
144
145static BOOL android_end_paint(rdpContext* context)
146{
147 HGDI_WND hwnd;
148 int ninvalid;
149 rdpGdi* gdi;
150 HGDI_RGN cinvalid;
151 int x1, y1, x2, y2;
152 androidContext* ctx = (androidContext*)context;
153 rdpSettings* settings;
154
155 if (!ctx || !context->instance)
156 return FALSE;
157
158 settings = context->settings;
159
160 if (!settings)
161 return FALSE;
162
163 gdi = context->gdi;
164
165 if (!gdi || !gdi->primary || !gdi->primary->hdc)
166 return FALSE;
167
168 hwnd = ctx->common.context.gdi->primary->hdc->hwnd;
169
170 if (!hwnd)
171 return FALSE;
172
173 ninvalid = hwnd->ninvalid;
174
175 if (ninvalid < 1)
176 return TRUE;
177
178 cinvalid = hwnd->cinvalid;
179
180 if (!cinvalid)
181 return FALSE;
182
183 x1 = cinvalid[0].x;
184 y1 = cinvalid[0].y;
185 x2 = cinvalid[0].x + cinvalid[0].w;
186 y2 = cinvalid[0].y + cinvalid[0].h;
187
188 for (int i = 0; i < ninvalid; i++)
189 {
190 x1 = MIN(x1, cinvalid[i].x);
191 y1 = MIN(y1, cinvalid[i].y);
192 x2 = MAX(x2, cinvalid[i].x + cinvalid[i].w);
193 y2 = MAX(y2, cinvalid[i].y + cinvalid[i].h);
194 }
195
196 freerdp_callback("OnGraphicsUpdate", "(JIIII)V", (jlong)context->instance, x1, y1, x2 - x1,
197 y2 - y1);
198
199 hwnd->invalid->null = TRUE;
200 hwnd->ninvalid = 0;
201 return TRUE;
202}
203
204static BOOL android_desktop_resize(rdpContext* context)
205{
206 WINPR_ASSERT(context);
207 WINPR_ASSERT(context->settings);
208 WINPR_ASSERT(context->instance);
209
210 const UINT32 width = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth);
211 const UINT32 height = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopHeight);
212
213 if (context->gdi && !gdi_resize(context->gdi, width, height))
214 return FALSE;
215
216 freerdp_callback("OnGraphicsResize", "(JIII)V", (jlong)context->instance, width, height,
217 freerdp_settings_get_uint32(context->settings, FreeRDP_ColorDepth));
218 return TRUE;
219}
220
221static BOOL android_pre_connect(freerdp* instance)
222{
223 WINPR_ASSERT(instance);
224 WINPR_ASSERT(instance->context);
225
226 rdpSettings* settings = instance->context->settings;
227
228 if (!settings)
229 return FALSE;
230
231 int rc = PubSub_SubscribeChannelConnected(instance->context->pubSub,
232 android_OnChannelConnectedEventHandler);
233
234 if (rc != CHANNEL_RC_OK)
235 {
236 WLog_ERR(TAG, "Could not subscribe to connect event handler [%08X]", rc);
237 return FALSE;
238 }
239
240 rc = PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
241 android_OnChannelDisconnectedEventHandler);
242
243 if (rc != CHANNEL_RC_OK)
244 {
245 WLog_ERR(TAG, "Could not subscribe to disconnect event handler [%08X]", rc);
246 return FALSE;
247 }
248
249 freerdp_callback("OnPreConnect", "(J)V", (jlong)instance);
250 return TRUE;
251}
252
253typedef struct
254{
255 rdpPointer pointer;
256 size_t size;
257 void* data;
258} androidPointer;
259
260static BOOL android_Pointer_New(rdpContext* context, rdpPointer* pointer)
261{
262 WINPR_ASSERT(context);
263 WINPR_ASSERT(pointer);
264 WINPR_ASSERT(context->gdi);
265
266 androidPointer* ptr = (androidPointer*)pointer;
267 if (!ptr)
268 return FALSE;
269
270 ptr->size = 4ULL * pointer->width * pointer->height;
271 ptr->data = winpr_aligned_malloc(ptr->size, 16);
272 if (!ptr->data)
273 return FALSE;
274
275 if (!freerdp_image_copy_from_pointer_data(
276 ptr->data, PIXEL_FORMAT_BGRA32, 0, 0, 0, pointer->width, pointer->height,
277 pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData,
278 pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette))
279 {
280 winpr_aligned_free(ptr->data);
281 ptr->data = nullptr;
282 return FALSE;
283 }
284
285 return TRUE;
286}
287
288static void android_Pointer_Free(rdpContext* context, rdpPointer* pointer)
289{
290 WINPR_UNUSED(context);
291 androidPointer* ptr = (androidPointer*)pointer;
292
293 if (ptr)
294 {
295 winpr_aligned_free(ptr->data);
296 ptr->data = nullptr;
297 }
298}
299
300static BOOL android_Pointer_Set(rdpContext* context, rdpPointer* pointer)
301{
302 WINPR_ASSERT(context);
303 WINPR_ASSERT(pointer);
304
305 androidPointer* ptr = (androidPointer*)pointer;
306 if (!ptr->data)
307 return FALSE;
308
309 const jsize nPixels = (jsize)(pointer->width * pointer->height);
310
311 JNIEnv* env = nullptr;
312 jboolean attached = jni_attach_thread(&env);
313
314 if (!gJavaActivityClass || !gOnPointerSetMethod)
315 goto done;
316
317 jintArray pixels = (*env)->NewIntArray(env, nPixels);
318 if (!pixels)
319 goto done;
320
321 (*env)->SetIntArrayRegion(env, pixels, 0, nPixels, (const jint*)ptr->data);
322 (*env)->CallStaticVoidMethod(env, gJavaActivityClass, gOnPointerSetMethod,
323 (jlong)context->instance, pixels, (jint)pointer->width,
324 (jint)pointer->height, (jint)pointer->xPos, (jint)pointer->yPos);
325 (*env)->DeleteLocalRef(env, pixels);
326done:
327 if (attached)
328 jni_detach_thread();
329
330 return TRUE;
331}
332
333static BOOL android_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y)
334{
335 WINPR_ASSERT(context);
336
337 return TRUE;
338}
339
340static BOOL android_Pointer_SetNull(rdpContext* context)
341{
342 WINPR_ASSERT(context);
343
344 freerdp_callback("OnPointerSetNull", "(J)V", (jlong)context->instance);
345 return TRUE;
346}
347
348static BOOL android_Pointer_SetDefault(rdpContext* context)
349{
350 WINPR_ASSERT(context);
351
352 freerdp_callback("OnPointerSetDefault", "(J)V", (jlong)context->instance);
353 return TRUE;
354}
355
356static UINT android_UpdateWindowFromSurface(RdpgfxClientContext* context, gdiGfxSurface* surface)
357{
358 if (!context || !surface)
359 return CHANNEL_RC_OK;
360
361 rdpGdi* gdi = (rdpGdi*)context->custom;
362 if (!gdi || !gdi->context)
363 return CHANNEL_RC_OK;
364
365 UINT32 width = surface->mappedWidth ? surface->mappedWidth : surface->width;
366 if (width > surface->width)
367 width = surface->width;
368
369 UINT32 height = surface->mappedHeight ? surface->mappedHeight : surface->height;
370 if (height > surface->height)
371 height = surface->height;
372
373 if (width == 0 || height == 0)
374 return CHANNEL_RC_OK;
375
376 JNIEnv* env = nullptr;
377 jboolean attached = jni_attach_thread(&env);
378 if (!gJavaActivityClass || !gOnRailWindowUpdateMethod)
379 goto done;
380
381 const jsize nPixels = (jsize)(width * height);
382 jintArray pixels = (*env)->NewIntArray(env, nPixels);
383 if (!pixels)
384 goto done;
385
386 jint* dst = (*env)->GetIntArrayElements(env, pixels, nullptr);
387 if (dst)
388 {
389 const BOOL rc = freerdp_image_copy((BYTE*)dst, surface->format, width * 4ull, 0, 0, width,
390 height, surface->data, surface->format,
391 surface->scanline, 0, 0, nullptr, FREERDP_FLIP_NONE);
392
393 /* Force coloured pixels opaque (ARGB_8888 would otherwise blend the active window's
394 * frame away), but keep transparent black so menu corners/shadows stay see-through. */
395 const size_t total = (size_t)width * height;
396 for (size_t i = 0; i < total; i++)
397 {
398 const UINT32 px = (UINT32)dst[i];
399 if ((px & 0x00FFFFFFu) != 0)
400 dst[i] = (jint)(px | 0xFF000000u);
401 }
402 (*env)->ReleaseIntArrayElements(env, pixels, dst, 0);
403 if (!rc)
404 goto done;
405 }
406
407 freerdp* inst = gdi->context->instance;
408 (*env)->CallStaticVoidMethod(env, gJavaActivityClass, gOnRailWindowUpdateMethod, (jlong)inst,
409 (jlong)surface->windowId, (jint)width, (jint)height, pixels);
410 (*env)->DeleteLocalRef(env, pixels);
411done:
412 if (attached)
413 jni_detach_thread();
414 return CHANNEL_RC_OK;
415}
416
417static BOOL android_register_pointer(rdpGraphics* graphics)
418{
419 rdpPointer pointer = WINPR_C_ARRAY_INIT;
420
421 if (!graphics)
422 return FALSE;
423
424 pointer.size = sizeof(androidPointer);
425 pointer.New = android_Pointer_New;
426 pointer.Free = android_Pointer_Free;
427 pointer.Set = android_Pointer_Set;
428 pointer.SetNull = android_Pointer_SetNull;
429 pointer.SetDefault = android_Pointer_SetDefault;
430 pointer.SetPosition = android_Pointer_SetPosition;
431 graphics_register_pointer(graphics, &pointer);
432 return TRUE;
433}
434
435/* Keep in sync with LibFreeRDP.EXPERIMENTAL_*. */
436#define ANDROID_EXPERIMENTAL_REMOTEAPP 0
437#define ANDROID_EXPERIMENTAL_CAMERA 1
438
439static BOOL android_post_connect(freerdp* instance)
440{
441 WINPR_ASSERT(instance);
442 WINPR_ASSERT(instance->context);
443
444 rdpUpdate* update = instance->context->update;
445 WINPR_ASSERT(update);
446
447 rdpSettings* settings = instance->context->settings;
448 WINPR_ASSERT(settings);
449
450 if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode) &&
451 !freerdp_callback_bool_result("OnExperimentalFeature", "(JI)Z", (jlong)instance,
452 ANDROID_EXPERIMENTAL_REMOTEAPP))
453 return FALSE;
454
455 if (freerdp_dynamic_channel_collection_find(settings, "rdpecam") &&
456 !freerdp_callback_bool_result("OnExperimentalFeature", "(JI)Z", (jlong)instance,
457 ANDROID_EXPERIMENTAL_CAMERA))
458 return FALSE;
459
460 if (!gdi_init(instance, PIXEL_FORMAT_RGBX32))
461 return FALSE;
462
463 if (!android_register_pointer(instance->context->graphics))
464 return FALSE;
465
466 update->BeginPaint = android_begin_paint;
467 update->EndPaint = android_end_paint;
468 update->DesktopResize = android_desktop_resize;
469 freerdp_callback("OnSettingsChanged", "(JIII)V", (jlong)instance,
470 freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
471 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
472 freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth));
473 freerdp_callback("OnConnectionSuccess", "(J)V", (jlong)instance);
474 return TRUE;
475}
476
477static void android_post_disconnect(freerdp* instance)
478{
479 freerdp_callback("OnDisconnecting", "(J)V", (jlong)instance);
480 gdi_free(instance);
481}
482
483static void android_post_final_disconnect(freerdp* instance)
484{
485 WINPR_ASSERT(instance);
486 WINPR_ASSERT(instance->context);
487
488 PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
489 android_OnChannelConnectedEventHandler);
490 PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
491 android_OnChannelDisconnectedEventHandler);
492}
493
494static BOOL android_authenticate_int(freerdp* instance, char** username, char** password,
495 char** domain, const char* cb_name)
496{
497 JNIEnv* env;
498 jboolean attached = jni_attach_thread(&env);
499 jobject jstr1 = create_string_builder(env, *username);
500 jobject jstr2 = create_string_builder(env, *domain);
501 jobject jstr3 = create_string_builder(env, *password);
502 jboolean res;
503 res = freerdp_callback_bool_result(cb_name,
504 "(JLjava/lang/StringBuilder;"
505 "Ljava/lang/StringBuilder;"
506 "Ljava/lang/StringBuilder;)Z",
507 (jlong)instance, jstr1, jstr2, jstr3);
508
509 if (res == JNI_TRUE)
510 {
511 // read back string values
512 free(*username);
513 *username = get_string_from_string_builder(env, jstr1);
514 free(*domain);
515 *domain = get_string_from_string_builder(env, jstr2);
516 free(*password);
517 *password = get_string_from_string_builder(env, jstr3);
518 }
519
520 if (attached == JNI_TRUE)
521 jni_detach_thread();
522
523 return ((res == JNI_TRUE) ? TRUE : FALSE);
524}
525
526static BOOL android_authenticate_ex(freerdp* instance, char** username, char** password,
527 char** domain, rdp_auth_reason reason)
528{
529 switch (reason)
530 {
531 case AUTH_NLA:
532 case AUTH_TLS:
533 case AUTH_RDP:
534 return android_authenticate_int(instance, username, password, domain, "OnAuthenticate");
535 case GW_AUTH_HTTP:
536 case GW_AUTH_RDG:
537 case GW_AUTH_RPC:
538 return android_authenticate_int(instance, username, password, domain,
539 "OnGatewayAuthenticate");
540 default:
541 return FALSE;
542 }
543}
544
545static DWORD android_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
546 const char* common_name, const char* subject,
547 const char* issuer, const char* fingerprint, DWORD flags)
548{
549 WLog_DBG(TAG, "Certificate details [%s:%" PRIu16 ":", host, port);
550 WLog_DBG(TAG, "\tSubject: %s", subject);
551 WLog_DBG(TAG, "\tIssuer: %s", issuer);
552 WLog_DBG(TAG, "\tThumbprint: %s", fingerprint);
553 WLog_DBG(TAG,
554 "The above X.509 certificate could not be verified, possibly because you do not have "
555 "the CA certificate in your certificate store, or the certificate has expired."
556 "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
557 JNIEnv* env;
558 jboolean attached = jni_attach_thread(&env);
559 jstring jstr0 = (*env)->NewStringUTF(env, host);
560 jstring jstr1 = (*env)->NewStringUTF(env, common_name);
561 jstring jstr2 = (*env)->NewStringUTF(env, subject);
562 jstring jstr3 = (*env)->NewStringUTF(env, issuer);
563 jstring jstr4 = (*env)->NewStringUTF(env, fingerprint);
564 jint res = freerdp_callback_int_result("OnVerifyCertificateEx",
565 "(JLjava/lang/String;JLjava/lang/String;Ljava/lang/"
566 "String;Ljava/lang/String;Ljava/lang/String;J)I",
567 (jlong)instance, jstr0, (jlong)port, jstr1, jstr2, jstr3,
568 jstr4, (jlong)flags);
569
570 if (attached == JNI_TRUE)
571 jni_detach_thread();
572
573 return res;
574}
575
576static DWORD android_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
577 const char* common_name, const char* subject,
578 const char* issuer, const char* new_fingerprint,
579 const char* old_subject, const char* old_issuer,
580 const char* old_fingerprint, DWORD flags)
581{
582 JNIEnv* env;
583 jboolean attached = jni_attach_thread(&env);
584 jstring jhost = (*env)->NewStringUTF(env, host);
585 jstring jstr0 = (*env)->NewStringUTF(env, common_name);
586 jstring jstr1 = (*env)->NewStringUTF(env, subject);
587 jstring jstr2 = (*env)->NewStringUTF(env, issuer);
588 jstring jstr3 = (*env)->NewStringUTF(env, new_fingerprint);
589 jstring jstr4 = (*env)->NewStringUTF(env, old_subject);
590 jstring jstr5 = (*env)->NewStringUTF(env, old_issuer);
591 jstring jstr6 = (*env)->NewStringUTF(env, old_fingerprint);
592 jint res =
593 freerdp_callback_int_result("OnVerifyChangedCertificateEx",
594 "(JLjava/lang/String;JLjava/lang/String;Ljava/lang/"
595 "String;Ljava/lang/String;Ljava/lang/String;"
596 "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)I",
597 (jlong)instance, jhost, (jlong)port, jstr0, jstr1, jstr2, jstr3,
598 jstr4, jstr5, jstr6, (jlong)flags);
599
600 if (attached == JNI_TRUE)
601 jni_detach_thread();
602
603 return res;
604}
605
606static int android_freerdp_run(freerdp* instance)
607{
608 WINPR_ASSERT(instance);
609
610 DWORD status = WAIT_FAILED;
611 HANDLE handles[MAXIMUM_WAIT_OBJECTS];
612 HANDLE inputEvent = nullptr;
613 rdpContext* context = instance->context;
614 WINPR_ASSERT(context);
615
616 const rdpSettings* settings = context->settings;
617
618 inputEvent = android_get_handle(instance);
619
620 while (!freerdp_shall_disconnect_context(context))
621 {
622 DWORD count = 0;
623
624 handles[count++] = inputEvent;
625
626 DWORD tmp = freerdp_get_event_handles(context, &handles[count], 64 - count);
627
628 if (tmp == 0)
629 {
630 WLog_ERR(TAG, "freerdp_get_event_handles failed");
631 break;
632 }
633
634 count += tmp;
635 status = WaitForMultipleObjects(count, handles, FALSE, INFINITE);
636
637 if (status == WAIT_FAILED)
638 {
639 WLog_ERR(TAG, "WaitForMultipleObjects failed with %u [%08X]", status,
640 (unsigned)GetLastError());
641 break;
642 }
643
644 if (!freerdp_check_event_handles(context))
645 {
646 if (!client_auto_reconnect(instance))
647 {
648 WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
649 status = GetLastError();
650 break;
651 }
652 }
653
654 if (freerdp_shall_disconnect_context(instance->context))
655 break;
656
657 if (android_check_handle(instance) != TRUE)
658 {
659 WLog_ERR(TAG, "Failed to check android file descriptor");
660 status = GetLastError();
661 break;
662 }
663 }
664
665disconnect:
666 WLog_INFO(TAG, "Prepare shutdown...");
667
668 return status;
669}
670
671static DWORD WINAPI android_thread_func(LPVOID param)
672{
673 DWORD status = ERROR_BAD_ARGUMENTS;
674 freerdp* instance = param;
675 WLog_DBG(TAG, "Start...");
676
677 WINPR_ASSERT(instance);
678 WINPR_ASSERT(instance->context);
679
680 if (freerdp_client_start(instance->context) != CHANNEL_RC_OK)
681 goto fail;
682
683 WLog_DBG(TAG, "Connect...");
684
685 if (!freerdp_connect(instance))
686 status = GetLastError();
687 else
688 {
689 status = android_freerdp_run(instance);
690 WLog_DBG(TAG, "Disconnect...");
691
692 if (!freerdp_disconnect(instance))
693 status = GetLastError();
694 }
695
696 WLog_DBG(TAG, "Stop...");
697
698 if (freerdp_client_stop(instance->context) != CHANNEL_RC_OK)
699 goto fail;
700
701fail:
702 WLog_DBG(TAG, "Session ended with %08" PRIX32 "", status);
703
704 if (status == CHANNEL_RC_OK)
705 freerdp_callback("OnDisconnected", "(J)V", (jlong)instance);
706 else
707 freerdp_callback("OnConnectionFailure", "(J)V", (jlong)instance);
708
709 WLog_DBG(TAG, "Quit.");
710 ExitThread(status);
711 return status;
712}
713
714static BOOL android_client_new(freerdp* instance, rdpContext* context)
715{
716 WINPR_ASSERT(instance);
717 WINPR_ASSERT(context);
718
719 if (!android_event_queue_init(instance))
720 return FALSE;
721
722 instance->PreConnect = android_pre_connect;
723 instance->PostConnect = android_post_connect;
724 instance->PostDisconnect = android_post_disconnect;
725 instance->PostFinalDisconnect = android_post_final_disconnect;
726 instance->AuthenticateEx = android_authenticate_ex;
727 instance->VerifyCertificateEx = android_verify_certificate_ex;
728 instance->VerifyChangedCertificateEx = android_verify_changed_certificate_ex;
729 instance->LogonErrorInfo = nullptr;
730 return TRUE;
731}
732
733static void android_client_free(freerdp* instance, rdpContext* context)
734{
735 if (!context)
736 return;
737
738 android_event_queue_uninit(instance);
739}
740
741static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
742{
743 WINPR_ASSERT(pEntryPoints);
744
745 ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
746
747 pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
748 pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
749 pEntryPoints->GlobalInit = nullptr;
750 pEntryPoints->GlobalUninit = nullptr;
751 pEntryPoints->ContextSize = sizeof(androidContext);
752 pEntryPoints->ClientNew = android_client_new;
753 pEntryPoints->ClientFree = android_client_free;
754 pEntryPoints->ClientStart = nullptr;
755 pEntryPoints->ClientStop = nullptr;
756 return 0;
757}
758
759JNIEXPORT jlong JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1new(
760 JNIEnv* env, jclass cls, jobject context)
761{
762 jclass contextClass;
763 jclass fileClass;
764 jobject filesDirObj;
765 jmethodID getFilesDirID;
766 jmethodID getAbsolutePathID;
767 jstring path;
768 const char* raw;
769 char* envStr;
770 RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
771 rdpContext* ctx;
772#if defined(WITH_GPROF)
773 setenv("CPUPROFILE_FREQUENCY", "200", 1);
774 monstartup("libfreerdp-android.so");
775#endif
776 contextClass = (*env)->FindClass(env, JAVA_CONTEXT_CLASS);
777 fileClass = (*env)->FindClass(env, JAVA_FILE_CLASS);
778
779 if (!contextClass || !fileClass)
780 {
781 WLog_FATAL(TAG, "Failed to load class references %s=%p, %s=%p", JAVA_CONTEXT_CLASS,
782 (void*)contextClass, JAVA_FILE_CLASS, (void*)fileClass);
783 return (jlong) nullptr;
784 }
785
786 getFilesDirID =
787 (*env)->GetMethodID(env, contextClass, "getFilesDir", "()L" JAVA_FILE_CLASS ";");
788
789 if (!getFilesDirID)
790 {
791 WLog_FATAL(TAG, "Failed to find method ID getFilesDir ()L" JAVA_FILE_CLASS ";");
792 return (jlong) nullptr;
793 }
794
795 getAbsolutePathID =
796 (*env)->GetMethodID(env, fileClass, "getAbsolutePath", "()Ljava/lang/String;");
797
798 if (!getAbsolutePathID)
799 {
800 WLog_FATAL(TAG, "Failed to find method ID getAbsolutePath ()Ljava/lang/String;");
801 return (jlong) nullptr;
802 }
803
804 filesDirObj = (*env)->CallObjectMethod(env, context, getFilesDirID);
805
806 if (!filesDirObj)
807 {
808 WLog_FATAL(TAG, "Failed to call getFilesDir");
809 return (jlong) nullptr;
810 }
811
812 path = (*env)->CallObjectMethod(env, filesDirObj, getAbsolutePathID);
813
814 if (!path)
815 {
816 WLog_FATAL(TAG, "Failed to call getAbsolutePath");
817 return (jlong) nullptr;
818 }
819
820 raw = (*env)->GetStringUTFChars(env, path, 0);
821
822 if (!raw)
823 {
824 WLog_FATAL(TAG, "Failed to get C string from java string");
825 return (jlong) nullptr;
826 }
827
828 envStr = _strdup(raw);
829 (*env)->ReleaseStringUTFChars(env, path, raw);
830
831 if (!envStr)
832 {
833 WLog_FATAL(TAG, "_strdup(%s) failed", raw);
834 return (jlong) nullptr;
835 }
836
837 if (setenv("HOME", _strdup(envStr), 1) != 0)
838 {
839 char ebuffer[256] = WINPR_C_ARRAY_INIT;
840 WLog_FATAL(TAG, "Failed to set environment HOME=%s %s [%d]", envStr,
841 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
842 return (jlong) nullptr;
843 }
844
845 RdpClientEntry(&clientEntryPoints);
846 ctx = freerdp_client_context_new(&clientEntryPoints);
847
848 if (!ctx)
849 return (jlong) nullptr;
850
851 return (jlong)ctx->instance;
852}
853
854JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1free(
855 JNIEnv* env, jclass cls, jlong instance)
856{
857 freerdp* inst = (freerdp*)instance;
858
859 if (inst)
860 freerdp_client_context_free(inst->context);
861
862#if defined(WITH_GPROF)
863 moncleanup();
864#endif
865}
866
867JNIEXPORT jstring JNICALL
868Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1last_1error_1string(JNIEnv* env,
869 jclass cls,
870 jlong instance)
871{
872 freerdp* inst = (freerdp*)instance;
873
874 if (!inst || !inst->context)
875 return (*env)->NewStringUTF(env, "");
876
877 return (*env)->NewStringUTF(
878 env, freerdp_get_last_error_string(freerdp_get_last_error(inst->context)));
879}
880
881JNIEXPORT jboolean JNICALL
882Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1parse_1arguments(JNIEnv* env, jclass cls,
883 jlong instance,
884 jobjectArray arguments)
885{
886 freerdp* inst = (freerdp*)instance;
887 int count;
888 char** argv;
889 DWORD status;
890
891 if (!inst || !inst->context)
892 return JNI_FALSE;
893
894 count = (*env)->GetArrayLength(env, arguments);
895 argv = calloc(count, sizeof(char*));
896
897 if (!argv)
898 return JNI_TRUE;
899
900 for (int i = 0; i < count; i++)
901 {
902 jstring str = (jstring)(*env)->GetObjectArrayElement(env, arguments, i);
903 const char* raw = (*env)->GetStringUTFChars(env, str, 0);
904 argv[i] = _strdup(raw);
905 (*env)->ReleaseStringUTFChars(env, str, raw);
906 }
907
908 status =
909 freerdp_client_settings_parse_command_line(inst->context->settings, count, argv, FALSE);
910
911 for (int i = 0; i < count; i++)
912 free(argv[i]);
913
914 free(argv);
915 return (status == 0) ? JNI_TRUE : JNI_FALSE;
916}
917
918JNIEXPORT jboolean JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1connect(
919 JNIEnv* env, jclass cls, jlong instance)
920{
921 freerdp* inst = (freerdp*)instance;
922
923 if (!inst || !inst->context)
924 {
925 WLog_FATAL(TAG, "(env=%p, cls=%p, instance=%" PRId64, (void*)env, (void*)cls,
926 (int64_t)instance);
927 return JNI_FALSE;
928 }
929
930 androidContext* ctx = (androidContext*)inst->context;
931
932 if (!(ctx->thread = CreateThread(nullptr, 0, android_thread_func, inst, 0, nullptr)))
933 {
934 return JNI_FALSE;
935 }
936
937 return JNI_TRUE;
938}
939
940JNIEXPORT jboolean JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1disconnect(
941 JNIEnv* env, jclass cls, jlong instance)
942{
943 freerdp* inst = (freerdp*)instance;
944
945 if (!inst || !inst->context || !cls || !env)
946 {
947 WLog_FATAL(TAG, "(env=%p, cls=%p, instance=%" PRId64, (void*)env, (void*)cls,
948 (int64_t)instance);
949 return JNI_FALSE;
950 }
951
952 androidContext* ctx = (androidContext*)inst->context;
953 ANDROID_EVENT* event = (ANDROID_EVENT*)android_event_disconnect_new();
954
955 if (!event)
956 return JNI_FALSE;
957
958 if (!android_push_event(inst, event))
959 {
960 android_event_free((ANDROID_EVENT*)event);
961 return JNI_FALSE;
962 }
963
964 if (!freerdp_abort_connect_context(inst->context))
965 return JNI_FALSE;
966
967 return JNI_TRUE;
968}
969
970JNIEXPORT jboolean JNICALL
971Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1update_1graphics(JNIEnv* env, jclass cls,
972 jlong instance,
973 jobject bitmap, jint x,
974 jint y, jint width,
975 jint height)
976{
977 UINT32 DstFormat;
978 jboolean rc;
979 int ret;
980 void* pixels;
981 AndroidBitmapInfo info;
982 freerdp* inst = (freerdp*)instance;
983 rdpGdi* gdi;
984
985 if (!env || !cls || !inst)
986 {
987 WLog_FATAL(TAG, "(env=%p, cls=%p, instance=%" PRId64, (void*)env, (void*)cls,
988 (int64_t)instance);
989 return JNI_FALSE;
990 }
991
992 gdi = inst->context->gdi;
993
994 if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0)
995 {
996 WLog_FATAL(TAG, "AndroidBitmap_getInfo() failed ! error=%d", ret);
997 return JNI_FALSE;
998 }
999
1000 if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0)
1001 {
1002 WLog_FATAL(TAG, "AndroidBitmap_lockPixels() failed ! error=%d", ret);
1003 return JNI_FALSE;
1004 }
1005
1006 rc = JNI_TRUE;
1007
1008 switch (info.format)
1009 {
1010 case ANDROID_BITMAP_FORMAT_RGBA_8888:
1011 DstFormat = PIXEL_FORMAT_RGBX32;
1012 break;
1013
1014 case ANDROID_BITMAP_FORMAT_RGB_565:
1015 DstFormat = PIXEL_FORMAT_RGB16;
1016 break;
1017
1018 case ANDROID_BITMAP_FORMAT_RGBA_4444:
1019 case ANDROID_BITMAP_FORMAT_A_8:
1020 case ANDROID_BITMAP_FORMAT_NONE:
1021 default:
1022 rc = JNI_FALSE;
1023 break;
1024 }
1025
1026 if (rc)
1027 {
1028 rc = freerdp_image_copy(pixels, DstFormat, info.stride, x, y, width, height,
1029 gdi->primary_buffer, gdi->dstFormat, gdi->stride, x, y,
1030 &gdi->palette, FREERDP_FLIP_NONE);
1031 }
1032
1033 if ((ret = AndroidBitmap_unlockPixels(env, bitmap)) < 0)
1034 {
1035 WLog_FATAL(TAG, "AndroidBitmap_unlockPixels() failed ! error=%d", ret);
1036 return JNI_FALSE;
1037 }
1038
1039 return rc;
1040}
1041
1042JNIEXPORT jboolean JNICALL
1043Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1key_1event(JNIEnv* env, jclass cls,
1044 jlong instance,
1045 jint keycode,
1046 jboolean down)
1047{
1048 DWORD scancode;
1049 ANDROID_EVENT* event;
1050 freerdp* inst = (freerdp*)instance;
1051 scancode = GetVirtualScanCodeFromVirtualKeyCode(keycode, 4);
1052 int flags = (down == JNI_TRUE) ? KBD_FLAGS_DOWN : KBD_FLAGS_RELEASE;
1053 flags |= (scancode & KBDEXT) ? KBD_FLAGS_EXTENDED : 0;
1054 event = (ANDROID_EVENT*)android_event_key_new(flags, scancode & 0xFF);
1055
1056 if (!event)
1057 return JNI_FALSE;
1058
1059 if (!android_push_event(inst, event))
1060 {
1061 android_event_free(event);
1062 return JNI_FALSE;
1063 }
1064
1065 WLog_DBG(TAG, "send_key_event: %" PRIu32 ", %d", scancode, flags);
1066 return JNI_TRUE;
1067}
1068
1069JNIEXPORT jboolean JNICALL
1070Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1unicodekey_1event(
1071 JNIEnv* env, jclass cls, jlong instance, jint keycode, jboolean down)
1072{
1073 ANDROID_EVENT* event;
1074 freerdp* inst = (freerdp*)instance;
1075 UINT16 flags = (down == JNI_TRUE) ? 0 : KBD_FLAGS_RELEASE;
1076 event = (ANDROID_EVENT*)android_event_unicodekey_new(flags, keycode);
1077
1078 if (!event)
1079 return JNI_FALSE;
1080
1081 if (!android_push_event(inst, event))
1082 {
1083 android_event_free(event);
1084 return JNI_FALSE;
1085 }
1086
1087 WLog_DBG(TAG, "send_unicodekey_event: %d", keycode);
1088 return JNI_TRUE;
1089}
1090
1091JNIEXPORT jboolean JNICALL
1092Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1is_1unicode_1input_1supported(
1093 JNIEnv* env, jclass cls, jlong instance)
1094{
1095 freerdp* inst = (freerdp*)instance;
1096
1097 if (!inst || !inst->context || !inst->context->settings)
1098 return JNI_FALSE;
1099
1100 if (!freerdp_settings_get_bool(inst->context->settings, FreeRDP_UnicodeInput))
1101 return JNI_FALSE;
1102
1103 return JNI_TRUE;
1104}
1105
1106JNIEXPORT jboolean JNICALL
1107Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1cursor_1event(
1108 JNIEnv* env, jclass cls, jlong instance, jint x, jint y, jint flags)
1109{
1110 ANDROID_EVENT* event;
1111 freerdp* inst = (freerdp*)instance;
1112 event = (ANDROID_EVENT*)android_event_cursor_new(flags, x, y);
1113
1114 if (!event)
1115 return JNI_FALSE;
1116
1117 if (!android_push_event(inst, event))
1118 {
1119 android_event_free(event);
1120 return JNI_FALSE;
1121 }
1122
1123 WLog_DBG(TAG, "send_cursor_event: (%d, %d), %d", x, y, flags);
1124 return JNI_TRUE;
1125}
1126
1127static jboolean android_push_clipboard_event(freerdp* inst, const void* data, size_t data_length,
1128 const char* mimeType)
1129{
1130 ANDROID_EVENT* event = (ANDROID_EVENT*)android_event_clipboard_new(data, data_length, mimeType);
1131 if (!event)
1132 return JNI_FALSE;
1133 if (!android_push_event(inst, event))
1134 {
1135 android_event_free(event);
1136 return JNI_FALSE;
1137 }
1138 return JNI_TRUE;
1139}
1140
1141JNIEXPORT jboolean JNICALL
1142Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1clipboard_1data(JNIEnv* env,
1143 jclass cls,
1144 jlong instance,
1145 jstring jdata)
1146{
1147 WINPR_UNUSED(cls);
1148 freerdp* inst = (freerdp*)instance;
1149 const char* data = jdata != nullptr ? (*env)->GetStringUTFChars(env, jdata, nullptr) : nullptr;
1150 const size_t data_length = data ? (*env)->GetStringUTFLength(env, jdata) : 0;
1151 jboolean ret = android_push_clipboard_event(inst, data, data_length, "text/plain");
1152 WLog_DBG(TAG, "send_clipboard_data: (%s)", data);
1153
1154 if (data)
1155 (*env)->ReleaseStringUTFChars(env, jdata, data);
1156
1157 return ret;
1158}
1159
1160static BOOL android_is_image_mime_supported(const char* mimeType)
1161{
1162 if (!mimeType)
1163 return FALSE;
1164 if (strcmp(mimeType, "image/png") == 0)
1165 return winpr_image_format_is_supported(WINPR_IMAGE_PNG);
1166 if (strcmp(mimeType, "image/jpeg") == 0 || strcmp(mimeType, "image/jpg") == 0)
1167 return winpr_image_format_is_supported(WINPR_IMAGE_JPEG);
1168 if (strcmp(mimeType, "image/webp") == 0)
1169 return winpr_image_format_is_supported(WINPR_IMAGE_WEBP);
1170 return FALSE;
1171}
1172
1173JNIEXPORT jboolean JNICALL
1174Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1clipboard_1image_1data(
1175 JNIEnv* env, jclass cls, jlong instance, jbyteArray jdata, jstring jmimeType)
1176{
1177 WINPR_UNUSED(cls);
1178 freerdp* inst = (freerdp*)instance;
1179 jsize data_length = (*env)->GetArrayLength(env, jdata);
1180 jbyte* data = (*env)->GetByteArrayElements(env, jdata, nullptr);
1181 const char* mimeType = jmimeType ? (*env)->GetStringUTFChars(env, jmimeType, nullptr) : nullptr;
1182 jboolean ret = JNI_FALSE;
1183 if (android_is_image_mime_supported(mimeType))
1184 ret = android_push_clipboard_event(inst, data, (size_t)data_length, mimeType);
1185
1186 if (mimeType)
1187 (*env)->ReleaseStringUTFChars(env, jmimeType, mimeType);
1188 (*env)->ReleaseByteArrayElements(env, jdata, data, JNI_ABORT);
1189 return ret;
1190}
1191
1192JNIEXPORT jboolean JNICALL
1193Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1monitor_1layout(
1194 JNIEnv* env, jclass cls, jlong instance, jint width, jint height)
1195{
1196 WINPR_UNUSED(env);
1197 WINPR_UNUSED(cls);
1198 freerdp* inst = (freerdp*)instance;
1199
1200 if (!inst || !inst->context)
1201 return JNI_FALSE;
1202
1203 androidContext* afc = (androidContext*)inst->context;
1204 return android_disp_send_monitor_layout(afc, (UINT32)width, (UINT32)height) ? JNI_TRUE
1205 : JNI_FALSE;
1206}
1207
1208JNIEXPORT jstring JNICALL
1209Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1jni_1version(JNIEnv* env, jclass cls)
1210{
1211 return (*env)->NewStringUTF(env, FREERDP_JNI_VERSION);
1212}
1213
1214JNIEXPORT jboolean JNICALL
1215Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1has_1h264(JNIEnv* env, jclass cls)
1216{
1217 H264_CONTEXT* ctx = h264_context_new(FALSE);
1218 if (!ctx)
1219 return JNI_FALSE;
1220 h264_context_free(ctx);
1221 return JNI_TRUE;
1222}
1223
1224JNIEXPORT jboolean JNICALL
1225Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1has_1camera_1redirection(JNIEnv* env,
1226 jclass cls)
1227{
1228 return freerdp_video_conversion_supported(FREERDP_VIDEO_FORMAT_NV12,
1229 FREERDP_VIDEO_FORMAT_YUV420P)
1230 ? JNI_TRUE
1231 : JNI_FALSE;
1232}
1233
1234JNIEXPORT jstring JNICALL
1235Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1version(JNIEnv* env, jclass cls)
1236{
1237 return (*env)->NewStringUTF(env, freerdp_get_version_string());
1238}
1239
1240JNIEXPORT jstring JNICALL
1241Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1build_1revision(JNIEnv* env,
1242 jclass cls)
1243{
1244 return (*env)->NewStringUTF(env, freerdp_get_build_revision());
1245}
1246
1247JNIEXPORT jstring JNICALL
1248Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1build_1config(JNIEnv* env,
1249 jclass cls)
1250{
1251 return (*env)->NewStringUTF(env, freerdp_get_build_config());
1252}
1253
1254jint JNI_OnLoad(JavaVM* vm, void* reserved)
1255{
1256 JNIEnv* env;
1257 setlocale(LC_ALL, "");
1258 WLog_DBG(TAG, "Setting up JNI environment...");
1259
1260 /*
1261 if (freerdp_handle_signals() != 0)
1262 {
1263 WLog_FATAL(TAG, "Failed to register signal handler");
1264 return -1;
1265 }
1266 */
1267 if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK)
1268 {
1269 WLog_FATAL(TAG, "Failed to get the environment");
1270 return -1;
1271 }
1272
1273 // Get SBCEngine activity class
1274 jclass activityClass = (*env)->FindClass(env, JAVA_LIBFREERDP_CLASS);
1275
1276 if (!activityClass)
1277 {
1278 WLog_FATAL(TAG, "failed to get %s class reference", JAVA_LIBFREERDP_CLASS);
1279 return -1;
1280 }
1281
1282 /* create global reference for class */
1283 gJavaActivityClass = (*env)->NewGlobalRef(env, activityClass);
1284 gOnPointerSetMethod =
1285 (*env)->GetStaticMethodID(env, gJavaActivityClass, "OnPointerSet", "(J[IIIII)V");
1286 gOnRailWindowUpdateMethod =
1287 (*env)->GetStaticMethodID(env, gJavaActivityClass, "OnRailWindowUpdate", "(JJII[I)V");
1288 if (!gOnRailWindowUpdateMethod)
1289 {
1290 (*env)->ExceptionClear(env);
1291 WLog_WARN(TAG, "OnRailWindowUpdate method not found, RAIL window display disabled");
1292 }
1293 g_JavaVm = vm;
1294 return init_callback_environment(vm, env);
1295}
1296
1297void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved)
1298{
1299 JNIEnv* env;
1300 WLog_DBG(TAG, "Tearing down JNI environment...");
1301
1302 if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK)
1303 {
1304 WLog_FATAL(TAG, "Failed to get the environment");
1305 return;
1306 }
1307
1308 if (gJavaActivityClass)
1309 (*env)->DeleteGlobalRef(env, gJavaActivityClass);
1310}
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.