@shenai/capacitor-sdk 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1476 @@
1
+ package ai.mxlabs.shenai_sdk.plugins.capacitor;
2
+
3
+ import com.getcapacitor.JSObject;
4
+ import com.getcapacitor.Plugin;
5
+ import com.getcapacitor.PluginCall;
6
+ import com.getcapacitor.PluginMethod;
7
+ import com.getcapacitor.annotation.CapacitorPlugin;
8
+
9
+ import android.util.Base64;
10
+ import android.util.Log;
11
+ import android.Manifest;
12
+ import android.os.Handler;
13
+ import android.os.Looper;
14
+ import android.view.MotionEvent;
15
+ import android.view.View;
16
+ import android.view.ViewGroup;
17
+ import android.view.WindowManager;
18
+ import android.graphics.Color;
19
+ import android.util.DisplayMetrics;
20
+ import android.util.TypedValue;
21
+ import android.widget.FrameLayout;
22
+ import androidx.coordinatorlayout.widget.CoordinatorLayout;
23
+ import java.util.Optional;
24
+ import java.util.List;
25
+ import java.util.ArrayList;
26
+ import org.json.JSONArray;
27
+ import org.json.JSONException;
28
+
29
+ import ai.mxlabs.shenai_sdk.ShenAIAndroidSDK;
30
+ import ai.mxlabs.shenai_sdk.ShenAIView;
31
+
32
+
33
+ @CapacitorPlugin(name = "ShenaiSdkCapacitor")
34
+ public class ShenaiSdkCapacitorPlugin extends Plugin {
35
+ // ────────────────────────────────────────────────────────────── state
36
+ private static final String TAG = "shen-capacitor";
37
+ private static final int CONTAINER_VIEW_ID = 15835421;
38
+ private ShenAIAndroidSDK sdk;
39
+ private ShenAIView shenaiView;
40
+ private FrameLayout containerView;
41
+ private boolean isTouchForwardingActive;
42
+
43
+ // ────────────────────────────────────────────────────────────── JS API
44
+
45
+ // demo echo method
46
+ @PluginMethod
47
+ public void echo(PluginCall call) {
48
+ String value = call.getString("value", "");
49
+ Log.i(TAG, value);
50
+ JSObject ret = new JSObject();
51
+ ret.put("value", value);
52
+ call.resolve(ret);
53
+ }
54
+
55
+ /**
56
+ * initialize(options: { apiKey: string, userId?: string, settings?: InitializationSettings }):
57
+ * Promise<InitializationResult>
58
+ */
59
+ @PluginMethod
60
+ public void initialize(PluginCall call) {
61
+ String apiKey = call.getString("apiKey");
62
+ if (apiKey == null || apiKey.isEmpty()) {
63
+ call.reject("apiKey is required");
64
+ return;
65
+ }
66
+
67
+ String userId = call.getString("userId", "");
68
+
69
+ // 1 · init SDK
70
+ sdk = new ShenAIAndroidSDK();
71
+ ShenAIAndroidSDK.InitializationSettings settings = sdk.getDefaultInitializationSettings();
72
+
73
+ JSObject settingsObj = call.getObject("settings", new JSObject());
74
+ if (settingsObj != null) {
75
+ try {
76
+ applyInitializationSettings(settingsObj, settings);
77
+ } catch (JSONException e) {
78
+ call.reject("Failed to parse initialization settings: " + e.getMessage());
79
+ return;
80
+ }
81
+ }
82
+
83
+ settings.eventCallback = new ShenAIAndroidSDK.EventCallback() {
84
+ @Override
85
+ public void onEvent(ShenAIAndroidSDK.Event event) {
86
+ String name;
87
+ switch (event) {
88
+ case START_BUTTON_CLICKED:
89
+ name = "START_BUTTON_CLICKED";
90
+ break;
91
+ case STOP_BUTTON_CLICKED:
92
+ name = "STOP_BUTTON_CLICKED";
93
+ break;
94
+ case MEASUREMENT_FINISHED:
95
+ name = "MEASUREMENT_FINISHED";
96
+ break;
97
+ case USER_FLOW_FINISHED:
98
+ name = "USER_FLOW_FINISHED";
99
+ break;
100
+ case SCREEN_CHANGED:
101
+ name = "SCREEN_CHANGED";
102
+ break;
103
+ default:
104
+ name = "UNKNOWN";
105
+ break;
106
+ }
107
+ JSObject data = new JSObject();
108
+ data.put("EventName", name);
109
+ notifyListeners("ShenAIEvent", data);
110
+ }
111
+ };
112
+
113
+ getActivity().runOnUiThread(() -> {
114
+ try {
115
+ ViewGroup root = (ViewGroup) getBridge().getWebView().getParent();
116
+ if (containerView != null && containerView.getParent() instanceof ViewGroup) {
117
+ ((ViewGroup) containerView.getParent()).removeView(containerView);
118
+ }
119
+
120
+ containerView = new FrameLayout(getContext());
121
+ containerView.setId(CONTAINER_VIEW_ID);
122
+
123
+ shenaiView = new ShenAIView(getContext());
124
+ containerView.addView(
125
+ shenaiView,
126
+ new FrameLayout.LayoutParams(
127
+ ViewGroup.LayoutParams.MATCH_PARENT,
128
+ ViewGroup.LayoutParams.MATCH_PARENT));
129
+
130
+ root.addView(
131
+ containerView,
132
+ new ViewGroup.LayoutParams(
133
+ ViewGroup.LayoutParams.MATCH_PARENT,
134
+ ViewGroup.LayoutParams.MATCH_PARENT));
135
+
136
+ getBridge().getWebView().setBackgroundColor(Color.TRANSPARENT);
137
+ getBridge().getWebView().setAlpha(1.0f);
138
+ root.bringChildToFront(getBridge().getWebView());
139
+ setupTouchForwarding();
140
+ shenaiView.onResume();
141
+ getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
142
+
143
+ resolveInitialization(call, apiKey, userId, settings);
144
+ } catch (Throwable t) {
145
+ call.reject(
146
+ "Failed to initialize ShenAI SDK: " + t.getMessage(),
147
+ t instanceof Exception ? (Exception) t : new RuntimeException(t)
148
+ );
149
+ }
150
+ });
151
+ }
152
+
153
+ private void setupTouchForwarding() {
154
+ final View webView = getBridge().getWebView();
155
+ webView.setClickable(true);
156
+ webView.setOnTouchListener((v, event) -> {
157
+ if (containerView == null) {
158
+ return false;
159
+ }
160
+
161
+ int[] webLoc = new int[2];
162
+ int[] containerLoc = new int[2];
163
+ webView.getLocationOnScreen(webLoc);
164
+ containerView.getLocationOnScreen(containerLoc);
165
+
166
+ MotionEvent forwarded = MotionEvent.obtain(event);
167
+ forwarded.offsetLocation(webLoc[0] - containerLoc[0], webLoc[1] - containerLoc[1]);
168
+
169
+ int action = forwarded.getActionMasked();
170
+ if (action == MotionEvent.ACTION_DOWN) {
171
+ isTouchForwardingActive = isPointInsideView(forwarded.getX(), forwarded.getY(), containerView);
172
+ }
173
+
174
+ if (isTouchForwardingActive) {
175
+ containerView.dispatchTouchEvent(forwarded);
176
+ }
177
+
178
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
179
+ isTouchForwardingActive = false;
180
+ }
181
+
182
+ forwarded.recycle();
183
+ return false;
184
+ });
185
+ }
186
+
187
+ private boolean isPointInsideView(float x, float y, View view) {
188
+ return x >= 0 && y >= 0 && x < view.getWidth() && y < view.getHeight();
189
+ }
190
+
191
+ private void resolveInitialization(
192
+ PluginCall call,
193
+ String apiKey,
194
+ String userId,
195
+ ShenAIAndroidSDK.InitializationSettings settings
196
+ ) {
197
+ ShenAIAndroidSDK.InitializationResult result =
198
+ sdk.initialize(getActivity(), apiKey, userId, settings);
199
+ JSObject ret = new JSObject();
200
+ ret.put("value", result.ordinal());
201
+ call.resolve(ret);
202
+ }
203
+
204
+ /** isInitialized(): Promise<boolean> */
205
+ @PluginMethod
206
+ public void isInitialized(PluginCall call) {
207
+ JSObject ret = new JSObject();
208
+ ret.put("value", sdk != null && sdk.isInitialized());
209
+ call.resolve(ret);
210
+ }
211
+
212
+ /** deinitialize(): Promise<void> */
213
+ @PluginMethod
214
+ public void deinitialize(PluginCall call) {
215
+ final ShenAIView capturedView = shenaiView;
216
+ final FrameLayout capturedContainer = containerView;
217
+ shenaiView = null;
218
+ containerView = null;
219
+ isTouchForwardingActive = false;
220
+
221
+ getActivity().runOnUiThread(() -> {
222
+ try {
223
+ getBridge().getWebView().setOnTouchListener(null);
224
+ getBridge().getWebView().setAlpha(1.0f);
225
+
226
+ if (capturedView != null) {
227
+ try { capturedView.activityPaused(); } catch (Throwable ignored) {}
228
+ }
229
+ if (capturedContainer != null) {
230
+ ViewGroup root = (ViewGroup) getBridge().getWebView().getParent();
231
+ root.removeView(capturedContainer);
232
+
233
+ getBridge().getWebView().setBackgroundColor(Color.WHITE);
234
+ }
235
+ if (shenaiView == null) {
236
+ try { getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Throwable ignored) {}
237
+ }
238
+ } catch (Throwable ignored) {}
239
+ });
240
+ try { if (sdk != null) sdk.deinitialize(); } catch (Throwable ignored) {}
241
+
242
+ sdk = null;
243
+
244
+ call.resolve();
245
+ }
246
+
247
+ /**
248
+ * setViewRect(options: { x?: number, y?: number, width?: number, height?: number }): Promise<void>
249
+ */
250
+ @PluginMethod
251
+ public void setViewRect(PluginCall call) {
252
+ final double x = call.getDouble("x", 0.0);
253
+ final double y = call.getDouble("y", 0.0);
254
+ final double width = call.getDouble("width", 0.0);
255
+ final double height = call.getDouble("height", 0.0);
256
+
257
+ getActivity().runOnUiThread(() -> {
258
+ if (containerView == null) {
259
+ call.reject("View not initialized");
260
+ return;
261
+ }
262
+
263
+ DisplayMetrics metrics = getActivity().getResources().getDisplayMetrics();
264
+
265
+ int computedX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) x, metrics);
266
+ int computedY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) y, metrics);
267
+ int computedWidth = width == 0.0 ? ViewGroup.LayoutParams.MATCH_PARENT :
268
+ (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) width, metrics);
269
+ int computedHeight = height == 0.0 ? ViewGroup.LayoutParams.MATCH_PARENT :
270
+ (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) height, metrics);
271
+
272
+ ViewGroup parent = (ViewGroup) containerView.getParent();
273
+ ViewGroup.LayoutParams layoutParams;
274
+
275
+ if (parent instanceof CoordinatorLayout) {
276
+ layoutParams = new CoordinatorLayout.LayoutParams(computedWidth, computedHeight);
277
+ ((CoordinatorLayout.LayoutParams) layoutParams).setMargins(computedX, computedY, 0, 0);
278
+ } else {
279
+ layoutParams = new FrameLayout.LayoutParams(computedWidth, computedHeight);
280
+ ((FrameLayout.LayoutParams) layoutParams).setMargins(computedX, computedY, 0, 0);
281
+ }
282
+
283
+ containerView.setLayoutParams(layoutParams);
284
+
285
+ call.resolve();
286
+ });
287
+ }
288
+
289
+ /**
290
+ * setOverlaysWebview(options: { overlay: boolean }): Promise<void>
291
+ */
292
+ @PluginMethod
293
+ public void setOverlaysWebview(PluginCall call) {
294
+ final Boolean overlay = call.getBoolean("overlay", null);
295
+ if (overlay == null) {
296
+ call.reject("overlays is required");
297
+ return;
298
+ }
299
+
300
+ getActivity().runOnUiThread(() -> {
301
+ if (containerView == null) {
302
+ call.reject("View not initialized");
303
+ return;
304
+ }
305
+
306
+ ViewGroup root = (ViewGroup) getBridge().getWebView().getParent();
307
+ if (overlay) {
308
+ root.bringChildToFront(containerView);
309
+ } else {
310
+ root.bringChildToFront(getBridge().getWebView());
311
+ }
312
+
313
+ call.resolve();
314
+ });
315
+ }
316
+
317
+ /** setOperatingMode(options: { mode: number }): Promise<void> */
318
+ @PluginMethod
319
+ public void setOperatingMode(PluginCall call) {
320
+ int mode = call.getInt("operatingMode", 0);
321
+ if (sdk != null) {
322
+ sdk.setOperatingMode(ShenAIAndroidSDK.OperatingMode.values()[mode]);
323
+ }
324
+ call.resolve();
325
+ }
326
+
327
+ @PluginMethod
328
+ public void startMeasurement(PluginCall call) {
329
+ if (sdk != null) {
330
+ sdk.startMeasurement();
331
+ }
332
+ call.resolve();
333
+ }
334
+
335
+ @PluginMethod
336
+ public void stopMeasurement(PluginCall call) {
337
+ if (sdk != null) {
338
+ sdk.stopMeasurement();
339
+ }
340
+ call.resolve();
341
+ }
342
+
343
+ @PluginMethod
344
+ public void resetMeasurementSession(PluginCall call) {
345
+ if (sdk != null) {
346
+ sdk.resetMeasurementSession();
347
+ }
348
+ call.resolve();
349
+ }
350
+
351
+ /** getOperatingMode(): Promise<number> */
352
+ @PluginMethod
353
+ public void getOperatingMode(PluginCall call) {
354
+ JSObject ret = new JSObject();
355
+ if (sdk != null) {
356
+ ret.put("value", sdk.getOperatingMode().ordinal());
357
+ } else {
358
+ ret.put("value", 0);
359
+ }
360
+ call.resolve(ret);
361
+ }
362
+
363
+ /** getCalibrationState(): Promise<number> */
364
+ @PluginMethod
365
+ public void getCalibrationState(PluginCall call) {
366
+ JSObject ret = new JSObject();
367
+ if (sdk != null) {
368
+ ret.put("value", sdk.getCalibrationState().ordinal());
369
+ } else {
370
+ ret.put("value", 0);
371
+ }
372
+ call.resolve(ret);
373
+ }
374
+
375
+ /** setPrecisionMode(options: { mode: number }): Promise<void> */
376
+ @PluginMethod
377
+ public void setPrecisionMode(PluginCall call) {
378
+ int mode = call.getInt("precisionMode", 0);
379
+ if (sdk != null) {
380
+ sdk.setPrecisionMode(ShenAIAndroidSDK.PrecisionMode.values()[mode]);
381
+ }
382
+ call.resolve();
383
+ }
384
+
385
+ /** getPrecisionMode(): Promise<number> */
386
+ @PluginMethod
387
+ public void getPrecisionMode(PluginCall call) {
388
+ JSObject ret = new JSObject();
389
+ if (sdk != null) {
390
+ ret.put("value", sdk.getPrecisionMode().ordinal());
391
+ } else {
392
+ ret.put("value", 0);
393
+ }
394
+ call.resolve(ret);
395
+ }
396
+
397
+ /** setMeasurementPreset(options: { preset:number }):Promise<void> */
398
+ @PluginMethod
399
+ public void setMeasurementPreset(PluginCall call) {
400
+ int preset = call.getInt("measurementPreset", 0);
401
+ if (sdk != null) {
402
+ sdk.setMeasurementPreset(ShenAIAndroidSDK.MeasurementPreset.values()[preset]);
403
+ }
404
+ call.resolve();
405
+ }
406
+
407
+ /** getMeasurementPreset(): Promise<number> */
408
+ @PluginMethod
409
+ public void getMeasurementPreset(PluginCall call) {
410
+ JSObject ret = new JSObject();
411
+ if (sdk != null) {
412
+ ret.put("value", sdk.getMeasurementPreset().ordinal());
413
+ } else {
414
+ ret.put("value", 0);
415
+ }
416
+ call.resolve(ret);
417
+ }
418
+
419
+ /** setCameraMode(options: { mode:number }):Promise<void> */
420
+ @PluginMethod
421
+ public void setCameraMode(PluginCall call) {
422
+ int mode = call.getInt("cameraMode", 0);
423
+ if (sdk != null) {
424
+ sdk.setCameraMode(ShenAIAndroidSDK.CameraMode.values()[mode]);
425
+ }
426
+ call.resolve();
427
+ }
428
+
429
+ /** getCameraMode(): Promise<number> */
430
+ @PluginMethod
431
+ public void getCameraMode(PluginCall call) {
432
+ JSObject ret = new JSObject();
433
+ if (sdk != null) {
434
+ ret.put("value", sdk.getCameraMode().ordinal());
435
+ } else {
436
+ ret.put("value", 0);
437
+ }
438
+ call.resolve(ret);
439
+ }
440
+
441
+ /** getLastCameraError(): Promise<number | null> */
442
+ @PluginMethod
443
+ public void getLastCameraError(PluginCall call) {
444
+ JSObject ret = new JSObject();
445
+ if (sdk != null) {
446
+ final ShenAIAndroidSDK.CameraError cameraError = sdk.getLastCameraError();
447
+ if (cameraError != null) {
448
+ ret.put("value", cameraError.ordinal());
449
+ } else {
450
+ ret.put("value", JSObject.NULL);
451
+ }
452
+ } else {
453
+ ret.put("value", JSObject.NULL);
454
+ }
455
+ call.resolve(ret);
456
+ }
457
+
458
+ /** setScreen(options: { screen:number }):Promise<void> */
459
+ @PluginMethod
460
+ public void setScreen(PluginCall call) {
461
+ int screen = call.getInt("screen", 0);
462
+ if (sdk != null) {
463
+ sdk.setScreen(ShenAIAndroidSDK.Screen.values()[screen]);
464
+ }
465
+ call.resolve();
466
+ }
467
+
468
+ /** getScreen(): Promise<number> */
469
+ @PluginMethod
470
+ public void getScreen(PluginCall call) {
471
+ JSObject ret = new JSObject();
472
+ if (sdk != null) {
473
+ ret.put("value", sdk.getScreen().ordinal());
474
+ } else {
475
+ ret.put("value", 0);
476
+ }
477
+ call.resolve(ret);
478
+ }
479
+
480
+ /** setShowUserInterface(options: { value:boolean }) */
481
+ @PluginMethod
482
+ public void setShowUserInterface(PluginCall call) {
483
+ boolean value = call.getBoolean("value", true);
484
+ if (sdk != null) sdk.setShowUserInterface(value);
485
+ call.resolve();
486
+ }
487
+
488
+ /** getShowUserInterface():Promise<boolean> */
489
+ @PluginMethod
490
+ public void getShowUserInterface(PluginCall call) {
491
+ JSObject ret = new JSObject();
492
+ ret.put("value", sdk != null && sdk.getShowUserInterface());
493
+ call.resolve(ret);
494
+ }
495
+
496
+ /** setShowFacePositioningOverlay(options: { value:boolean }) */
497
+ @PluginMethod
498
+ public void setShowFacePositioningOverlay(PluginCall call) {
499
+ boolean value = call.getBoolean("value", true);
500
+ if (sdk != null) sdk.setShowFacePositioningOverlay(value);
501
+ call.resolve();
502
+ }
503
+
504
+ /** getShowFacePositioningOverlay():Promise<boolean> */
505
+ @PluginMethod
506
+ public void getShowFacePositioningOverlay(PluginCall call) {
507
+ JSObject ret = new JSObject();
508
+ ret.put("value", sdk != null && sdk.getShowFacePositioningOverlay());
509
+ call.resolve(ret);
510
+ }
511
+
512
+ /** setShowVisualWarnings(options: { value:boolean }) */
513
+ @PluginMethod
514
+ public void setShowVisualWarnings(PluginCall call) {
515
+ boolean value = call.getBoolean("value", true);
516
+ if (sdk != null) sdk.setShowVisualWarnings(value);
517
+ call.resolve();
518
+ }
519
+
520
+ /** getShowVisualWarnings():Promise<boolean> */
521
+ @PluginMethod
522
+ public void getShowVisualWarnings(PluginCall call) {
523
+ JSObject ret = new JSObject();
524
+ ret.put("value", sdk != null && sdk.getShowVisualWarnings());
525
+ call.resolve(ret);
526
+ }
527
+
528
+ /** setEnableCameraSwap(options: { value:boolean} ) */
529
+ @PluginMethod
530
+ public void setEnableCameraSwap(PluginCall call) {
531
+ boolean value = call.getBoolean("value", true);
532
+ if (sdk != null) sdk.setEnableCameraSwap(value);
533
+ call.resolve();
534
+ }
535
+
536
+ /** getEnableCameraSwap():Promise<boolean> */
537
+ @PluginMethod
538
+ public void getEnableCameraSwap(PluginCall call) {
539
+ JSObject ret = new JSObject();
540
+ ret.put("value", sdk != null && sdk.getEnableCameraSwap());
541
+ call.resolve(ret);
542
+ }
543
+
544
+ /** setShowFaceMask(options: { value:boolean }) */
545
+ @PluginMethod
546
+ public void setShowFaceMask(PluginCall call) {
547
+ boolean value = call.getBoolean("value", true);
548
+ if (sdk != null) sdk.setShowFaceMask(value);
549
+ call.resolve();
550
+ }
551
+
552
+ /** getShowFaceMask():Promise<boolean> */
553
+ @PluginMethod
554
+ public void getShowFaceMask(PluginCall call) {
555
+ JSObject ret = new JSObject();
556
+ ret.put("value", sdk != null && sdk.getShowFaceMask());
557
+ call.resolve(ret);
558
+ }
559
+
560
+ /** setShowBloodFlow(options: { value:boolean }) */
561
+ @PluginMethod
562
+ public void setShowBloodFlow(PluginCall call) {
563
+ boolean value = call.getBoolean("value", true);
564
+ if (sdk != null) sdk.setShowBloodFlow(value);
565
+ call.resolve();
566
+ }
567
+
568
+ /** getShowBloodFlow():Promise<boolean> */
569
+ @PluginMethod
570
+ public void getShowBloodFlow(PluginCall call) {
571
+ JSObject ret = new JSObject();
572
+ ret.put("value", sdk != null && sdk.getShowBloodFlow());
573
+ call.resolve(ret);
574
+ }
575
+
576
+ /** setIncludeTimestampInPdf(options: { value:boolean }) */
577
+ @PluginMethod
578
+ public void setIncludeTimestampInPdf(PluginCall call) {
579
+ boolean value = call.getBoolean("value", true);
580
+ if (sdk != null) sdk.setIncludeTimestampInPdf(value);
581
+ call.resolve();
582
+ }
583
+
584
+ /** getIncludeTimestampInPdf():Promise<boolean> */
585
+ @PluginMethod
586
+ public void getIncludeTimestampInPdf(PluginCall call) {
587
+ JSObject ret = new JSObject();
588
+ ret.put("value", sdk != null && sdk.getIncludeTimestampInPdf());
589
+ call.resolve(ret);
590
+ }
591
+
592
+ /** setPdfEmailSubject(options: { subject:string }) */
593
+ @PluginMethod
594
+ public void setPdfEmailSubject(PluginCall call) {
595
+ String subject = call.getString("subject", "");
596
+ if (sdk != null) sdk.setPdfEmailSubject(subject);
597
+ call.resolve();
598
+ }
599
+
600
+ /** setPdfEmailBody(options: { body:string }) */
601
+ @PluginMethod
602
+ public void setPdfEmailBody(PluginCall call) {
603
+ String body = call.getString("body", "");
604
+ if (sdk != null) sdk.setPdfEmailBody(body);
605
+ call.resolve();
606
+ }
607
+
608
+ /** setShowStartStopButton(options: { value:boolean }) */
609
+ @PluginMethod
610
+ public void setShowStartStopButton(PluginCall call) {
611
+ boolean value = call.getBoolean("value", true);
612
+ if (sdk != null) sdk.setShowStartStopButton(value);
613
+ call.resolve();
614
+ }
615
+
616
+ /** getShowStartStopButton():Promise<boolean> */
617
+ @PluginMethod
618
+ public void getShowStartStopButton(PluginCall call) {
619
+ JSObject ret = new JSObject();
620
+ ret.put("value", sdk != null && sdk.getShowStartStopButton());
621
+ call.resolve(ret);
622
+ }
623
+
624
+ /** setEnableMeasurementsDashboard(options: { value:boolean }) */
625
+ @PluginMethod
626
+ public void setEnableMeasurementsDashboard(PluginCall call) {
627
+ boolean value = call.getBoolean("value", true);
628
+ if (sdk != null) sdk.setEnableMeasurementsDashboard(value);
629
+ call.resolve();
630
+ }
631
+
632
+ /** getEnableMeasurementsDashboard():Promise<boolean> */
633
+ @PluginMethod
634
+ public void getEnableMeasurementsDashboard(PluginCall call) {
635
+ JSObject ret = new JSObject();
636
+ ret.put("value", sdk != null && sdk.getEnableMeasurementsDashboard());
637
+ call.resolve(ret);
638
+ }
639
+
640
+ /** setShowInfoButton(options: { value:boolean }) */
641
+ @PluginMethod
642
+ public void setShowInfoButton(PluginCall call) {
643
+ boolean value = call.getBoolean("value", true);
644
+ if (sdk != null) sdk.setShowInfoButton(value);
645
+ call.resolve();
646
+ }
647
+
648
+ /** getShowInfoButton():Promise<boolean> */
649
+ @PluginMethod
650
+ public void getShowInfoButton(PluginCall call) {
651
+ JSObject ret = new JSObject();
652
+ ret.put("value", sdk != null && sdk.getShowInfoButton());
653
+ call.resolve(ret);
654
+ }
655
+
656
+ /** getShowDisclaimer():Promise<boolean> */
657
+ @PluginMethod
658
+ public void getShowDisclaimer(PluginCall call) {
659
+ JSObject ret = new JSObject();
660
+ ret.put("value", sdk != null && sdk.getShowDisclaimer());
661
+ call.resolve(ret);
662
+ }
663
+
664
+ /** setEnableStartAfterSuccess(options: { value:boolean }) */
665
+ @PluginMethod
666
+ public void setEnableStartAfterSuccess(PluginCall call) {
667
+ boolean value = call.getBoolean("value", true);
668
+ if (sdk != null) sdk.setEnableStartAfterSuccess(value);
669
+ call.resolve();
670
+ }
671
+
672
+ /** getEnableStartAfterSuccess():Promise<boolean> */
673
+ @PluginMethod
674
+ public void getEnableStartAfterSuccess(PluginCall call) {
675
+ JSObject ret = new JSObject();
676
+ ret.put("value", sdk != null && sdk.getEnableStartAfterSuccess());
677
+ call.resolve(ret);
678
+ }
679
+
680
+ /** getFaceState():Promise<number> */
681
+ @PluginMethod
682
+ public void getFaceState(PluginCall call) {
683
+ JSObject ret = new JSObject();
684
+ if (sdk != null) {
685
+ ret.put("value", sdk.getFaceState().ordinal());
686
+ } else {
687
+ ret.put("value", 0);
688
+ }
689
+ call.resolve(ret);
690
+ }
691
+
692
+ /** getNormalizedFaceBbox():Promise<object|null> */
693
+ @PluginMethod
694
+ public void getNormalizedFaceBbox(PluginCall call) {
695
+ JSObject ret = new JSObject();
696
+ if (sdk != null) {
697
+ ShenAIAndroidSDK.NormalizedFaceBbox b = sdk.getNormalizedFaceBbox();
698
+ if (b != null) {
699
+ JSObject box = new JSObject();
700
+ box.put("x", b.x);
701
+ box.put("y", b.y);
702
+ box.put("width", b.width);
703
+ box.put("height", b.height);
704
+ ret.put("value", box);
705
+ call.resolve(ret);
706
+ return;
707
+ }
708
+ }
709
+ call.resolve();
710
+ }
711
+
712
+ /** getMeasurementState():Promise<number> */
713
+ @PluginMethod
714
+ public void getMeasurementState(PluginCall call) {
715
+ JSObject ret = new JSObject();
716
+ if (sdk != null) ret.put("value", sdk.getMeasurementState().ordinal());
717
+ else ret.put("value", 0);
718
+ call.resolve(ret);
719
+ }
720
+
721
+ @PluginMethod
722
+ public void getCurrentViolatedMeasurementEnvironmentCondition(PluginCall call) {
723
+ JSObject ret = new JSObject();
724
+ if (sdk != null) {
725
+ ShenAIAndroidSDK.MeasurementEnvironmentCondition value =
726
+ sdk.getCurrentViolatedMeasurementEnvironmentCondition();
727
+ ret.put("value", value == null ? JSObject.NULL : value.ordinal());
728
+ } else {
729
+ ret.put("value", JSObject.NULL);
730
+ }
731
+ call.resolve(ret);
732
+ }
733
+
734
+ /** isReadyToStartMeasurement():Promise<boolean> */
735
+ @PluginMethod
736
+ public void isReadyToStartMeasurement(PluginCall call) {
737
+ JSObject ret = new JSObject();
738
+ ret.put("value", sdk != null && sdk.isReadyToStartMeasurement());
739
+ call.resolve(ret);
740
+ }
741
+
742
+ /** areRequiredModelsDownloaded():Promise<boolean> */
743
+ @PluginMethod
744
+ public void areRequiredModelsDownloaded(PluginCall call) {
745
+ JSObject ret = new JSObject();
746
+ ret.put("value", sdk != null && sdk.areRequiredModelsDownloaded());
747
+ call.resolve(ret);
748
+ }
749
+
750
+ /** getMeasurementProgressPercentage():Promise<number> */
751
+ @PluginMethod
752
+ public void getMeasurementProgressPercentage(PluginCall call) {
753
+ JSObject ret = new JSObject();
754
+ if (sdk != null) ret.put("value", sdk.getMeasurementProgressPercentage());
755
+ else ret.put("value", 0);
756
+ call.resolve(ret);
757
+ }
758
+
759
+ /** getHeartRate10s():Promise<number|null> */
760
+ @PluginMethod
761
+ public void getHeartRate10s(PluginCall call) {
762
+ JSObject ret = new JSObject();
763
+ if (sdk == null) {
764
+ ret.put("value", JSObject.NULL);
765
+ } else {
766
+ int v = sdk.getHeartRate10s();
767
+ if (v < 0) ret.put("value", JSObject.NULL); else ret.put("value", v);
768
+ }
769
+ call.resolve(ret);
770
+ }
771
+
772
+ /** getHeartRate4s():Promise<number|null> */
773
+ @PluginMethod
774
+ public void getHeartRate4s(PluginCall call) {
775
+ JSObject ret = new JSObject();
776
+ if (sdk == null) {
777
+ ret.put("value", JSObject.NULL);
778
+ } else {
779
+ int v = sdk.getHeartRate4s();
780
+ if (v < 0) ret.put("value", JSObject.NULL); else ret.put("value", v);
781
+ }
782
+ call.resolve(ret);
783
+ }
784
+
785
+ /** getMeasurementResults():Promise<object|null> */
786
+ @PluginMethod
787
+ public void getMeasurementResults(PluginCall call) {
788
+ if (sdk == null) {
789
+ JSObject wrapper = new JSObject();
790
+ wrapper.put("value", JSObject.NULL);
791
+ call.resolve(wrapper);
792
+ return;
793
+ }
794
+ ShenAIAndroidSDK.MeasurementResults r = sdk.getMeasurementResults();
795
+ if (r == null) {
796
+ JSObject wrapper = new JSObject();
797
+ wrapper.put("value", JSObject.NULL);
798
+ call.resolve(wrapper);
799
+ return;
800
+ }
801
+ JSObject ret = measurementResultsToJSObject(r);
802
+ JSObject wrapper = new JSObject();
803
+ wrapper.put("value", ret);
804
+ call.resolve(wrapper);
805
+ }
806
+
807
+ /** getMeasurementResultsHistory():Promise<object|null> */
808
+ @PluginMethod
809
+ public void getMeasurementResultsHistory(PluginCall call) {
810
+ if (sdk == null) { call.resolve(); return; }
811
+ ShenAIAndroidSDK.MeasurementResultsHistory hist = sdk.getMeasurementResultsHistory();
812
+ if (hist == null) { call.resolve(); return; }
813
+ JSONArray arr = new JSONArray();
814
+ if (hist.history != null) {
815
+ for (ShenAIAndroidSDK.MeasurementResultsWithMetadata md : hist.history) {
816
+ JSObject obj = measurementResultsWithMetadataToJSObject(md);
817
+ arr.put(obj);
818
+ }
819
+ }
820
+ JSObject ret = new JSObject();
821
+ ret.put("value", arr);
822
+ call.resolve(ret);
823
+ }
824
+
825
+ /** getRealtimeMetrics(periodSec:number):Promise<object|null> */
826
+ @PluginMethod
827
+ public void getRealtimeMetrics(PluginCall call) {
828
+ if (sdk == null) { call.resolve(); return; }
829
+ Double periodOpt = call.getDouble("periodSec", 1.0);
830
+ float period = periodOpt != null ? periodOpt.floatValue() : 1.0f;
831
+ ShenAIAndroidSDK.MeasurementResults r = sdk.getRealtimeMetrics(period);
832
+ if (r == null) { call.resolve(); return; }
833
+ JSObject ret = new JSObject();
834
+ ret.put("value", measurementResultsToJSObject(r));
835
+ call.resolve(ret);
836
+ }
837
+
838
+ /** getRealtimeHeartbeats(periodSec?:number):Promise<object[]> */
839
+ @PluginMethod
840
+ public void getRealtimeHeartbeats(PluginCall call) {
841
+ if (sdk == null) { call.resolve(); return; }
842
+ Double p = call.getDouble("periodSec");
843
+ ShenAIAndroidSDK.Heartbeat[] hb = sdk.getRealtimeHeartbeats(p == null ? null : p.floatValue());
844
+ JSONArray arr = heartbeatsToJSONArray(hb);
845
+ JSObject ret = new JSObject();
846
+ ret.put("value", arr);
847
+ call.resolve(ret);
848
+ }
849
+
850
+ /** getFullPpgSignal(): Promise<{ value: number[] }> */
851
+ @PluginMethod
852
+ public void getFullPpgSignal(PluginCall call) {
853
+ JSObject ret = new JSObject();
854
+ if (sdk != null) {
855
+ double[] signal = sdk.getFullPpgSignal();
856
+ JSONArray arr = new JSONArray();
857
+ for (double v : signal) {
858
+ arr.put(Double.valueOf(v));
859
+ }
860
+
861
+ ret.put("value", arr);
862
+ }
863
+ call.resolve(ret);
864
+ }
865
+ /** setRecordingEnabled(options: { enabled:boolean }): Promise<void> */
866
+ @PluginMethod
867
+ public void setRecordingEnabled(PluginCall call) {
868
+ boolean enabled = call.getBoolean("value", false);
869
+ if (sdk != null) sdk.setRecordingEnabled(enabled);
870
+ call.resolve();
871
+ }
872
+
873
+ /** getRecordingEnabled(): Promise<{ value: boolean }> */
874
+ @PluginMethod
875
+ public void getRecordingEnabled(PluginCall call) {
876
+ JSObject ret = new JSObject();
877
+ ret.put("value", sdk != null && sdk.getRecordingEnabled());
878
+ call.resolve(ret);
879
+ }
880
+
881
+ /** getTotalBadSignalSeconds(): Promise<{ value: number }> */
882
+ @PluginMethod
883
+ public void getTotalBadSignalSeconds(PluginCall call) {
884
+ JSObject ret = new JSObject();
885
+ if (sdk != null) ret.put("value", sdk.getTotalBadSignalSeconds());
886
+ else ret.put("value", 0);
887
+ call.resolve(ret);
888
+ }
889
+
890
+ /** getCurrentSignalQualityMetric(): Promise<{ value: number }> */
891
+ @PluginMethod
892
+ public void getCurrentSignalQualityMetric(PluginCall call) {
893
+ JSObject ret = new JSObject();
894
+ if (sdk != null) ret.put("value", sdk.getCurrentSignalQualityMetric());
895
+ else ret.put("value", 0);
896
+ call.resolve(ret);
897
+ }
898
+
899
+ /** setCustomMeasurementConfig(options: { config:object }): Promise<void> */
900
+ @PluginMethod
901
+ public void setCustomMeasurementConfig(PluginCall call) {
902
+ JSObject cfg = call.getObject("config", new JSObject());
903
+ if (sdk != null && cfg != null) {
904
+ ShenAIAndroidSDK.CustomMeasurementConfig c = sdk.new CustomMeasurementConfig();
905
+ if (cfg.has("durationSeconds"))
906
+ c.durationSeconds = Optional.of((float) cfg.optDouble("durationSeconds"));
907
+ if (cfg.has("infiniteMeasurement"))
908
+ c.infiniteMeasurement = Optional.of(cfg.optBoolean("infiniteMeasurement"));
909
+ if (cfg.has("instantMetrics")) {
910
+ JSONArray arr = cfg.optJSONArray("instantMetrics");
911
+ if (arr != null) {
912
+ List<ShenAIAndroidSDK.Metric> list = new ArrayList<>();
913
+ for (int i = 0; i < arr.length(); ++i) list.add(ShenAIAndroidSDK.Metric.values()[arr.optInt(i)]);
914
+ c.instantMetrics = Optional.of(list);
915
+ }
916
+ }
917
+ if (cfg.has("summaryMetrics")) {
918
+ JSONArray arr = cfg.optJSONArray("summaryMetrics");
919
+ if (arr != null) {
920
+ List<ShenAIAndroidSDK.Metric> list = new ArrayList<>();
921
+ for (int i = 0; i < arr.length(); ++i) list.add(ShenAIAndroidSDK.Metric.values()[arr.optInt(i)]);
922
+ c.summaryMetrics = Optional.of(list);
923
+ }
924
+ }
925
+ if (cfg.has("healthIndices")) {
926
+ JSONArray arr = cfg.optJSONArray("healthIndices");
927
+ if (arr != null) {
928
+ List<ShenAIAndroidSDK.HealthIndex> list = new ArrayList<>();
929
+ for (int i = 0; i < arr.length(); ++i) list.add(ShenAIAndroidSDK.HealthIndex.values()[arr.optInt(i)]);
930
+ c.healthIndices = Optional.of(list);
931
+ }
932
+ }
933
+ if (cfg.has("realtimeHrPeriodSeconds"))
934
+ c.realtimeHrPeriodSeconds = Optional.of((float) cfg.optDouble("realtimeHrPeriodSeconds"));
935
+ if (cfg.has("realtimeHrvPeriodSeconds"))
936
+ c.realtimeHrvPeriodSeconds = Optional.of((float) cfg.optDouble("realtimeHrvPeriodSeconds"));
937
+ if (cfg.has("realtimeCardiacStressPeriodSeconds"))
938
+ c.realtimeCardiacStressPeriodSeconds = Optional.of((float) cfg.optDouble("realtimeCardiacStressPeriodSeconds"));
939
+ sdk.setCustomMeasurementConfig(c);
940
+ }
941
+ call.resolve();
942
+ }
943
+
944
+ /** setCustomColorTheme(options: { theme:object }): Promise<void> */
945
+ @PluginMethod
946
+ public void setCustomColorTheme(PluginCall call) {
947
+ JSObject obj = call.getObject("theme", new JSObject());
948
+ if (sdk != null && obj != null) {
949
+ ShenAIAndroidSDK.CustomColorTheme t = sdk.new CustomColorTheme();
950
+ if (obj.has("themeColor")) t.themeColor = obj.getString("themeColor");
951
+ if (obj.has("textColor")) t.textColor = obj.getString("textColor");
952
+ if (obj.has("backgroundColor")) t.backgroundColor = obj.getString("backgroundColor");
953
+ if (obj.has("tileColor")) t.tileColor = obj.getString("tileColor");
954
+ if (obj.has("buttonMainColor")) t.buttonMainColor = obj.getString("buttonMainColor");
955
+ if (obj.has("buttonSecondaryColor")) t.buttonSecondaryColor = obj.getString("buttonSecondaryColor");
956
+ sdk.setCustomColorTheme(t);
957
+ }
958
+ call.resolve();
959
+ }
960
+
961
+ /** setLanguage(options: { lang:string }) */
962
+ @PluginMethod
963
+ public void setLanguage(PluginCall call) {
964
+ String lang = call.getString("language");
965
+ if (sdk != null && lang != null) {
966
+ sdk.setLanguage(lang);
967
+ }
968
+ call.resolve();
969
+ }
970
+
971
+ /** getHealthRisks():Promise<object> */
972
+ @PluginMethod
973
+ public void getHealthRisks(PluginCall call) {
974
+ if (sdk == null) { call.reject("not initialized"); return; }
975
+ ShenAIAndroidSDK.HealthRisks risks = sdk.getHealthRisks();
976
+ JSObject ret = new JSObject();
977
+ ret.put("value", healthRisksToJSObject(risks));
978
+ call.resolve(ret);
979
+ }
980
+
981
+ /** getHealthRisksFactors():Promise<object> */
982
+ @PluginMethod
983
+ public void getHealthRisksFactors(PluginCall call) {
984
+ if (sdk == null) { call.reject("not initialized"); return; }
985
+ ShenAIAndroidSDK.RisksFactors factors = sdk.getHealthRisksFactors();
986
+ JSObject ret = new JSObject();
987
+ ret.put("value", risksFactorsToJSObject(factors));
988
+ call.resolve(ret);
989
+ }
990
+
991
+ /** getMaximalRisks(options: { risksFactors: object }):Promise<object> */
992
+ @PluginMethod
993
+ public void getMaximalRisks(PluginCall call) {
994
+ if (sdk == null) { call.reject("not initialized"); return; }
995
+ JSObject obj = call.getObject("risksFactors", new JSObject());
996
+ try {
997
+ ShenAIAndroidSDK.RisksFactors factors = jsObjectToRisksFactors(obj);
998
+ ShenAIAndroidSDK.HealthRisks risks = sdk.getMaximalHealthRisks(factors);
999
+ JSObject ret = new JSObject();
1000
+ ret.put("value", healthRisksToJSObject(risks));
1001
+ call.resolve(ret);
1002
+ } catch (JSONException e) {
1003
+ call.reject("Failed to parse risks factors: " + e.getMessage());
1004
+ }
1005
+ }
1006
+
1007
+ /** getMinimalRisks(options: { risksFactors: object }):Promise<object> */
1008
+ @PluginMethod
1009
+ public void getMinimalRisks(PluginCall call) {
1010
+ if (sdk == null) { call.reject("not initialized"); return; }
1011
+ JSObject obj = call.getObject("risksFactors", new JSObject());
1012
+ try {
1013
+ ShenAIAndroidSDK.RisksFactors factors = jsObjectToRisksFactors(obj);
1014
+ ShenAIAndroidSDK.HealthRisks risks = sdk.getMinimalHealthRisks(factors);
1015
+ JSObject ret = new JSObject();
1016
+ ret.put("value", healthRisksToJSObject(risks));
1017
+ call.resolve(ret);
1018
+ } catch (JSONException e) {
1019
+ call.reject("Failed to parse risks factors: " + e.getMessage());
1020
+ }
1021
+ }
1022
+
1023
+ /** getReferenceRisks(options: { risksFactors: object }):Promise<object> */
1024
+ @PluginMethod
1025
+ public void getReferenceRisks(PluginCall call) {
1026
+ if (sdk == null) { call.reject("not initialized"); return; }
1027
+ JSObject obj = call.getObject("risksFactors", new JSObject());
1028
+ try {
1029
+ ShenAIAndroidSDK.RisksFactors factors = jsObjectToRisksFactors(obj);
1030
+ ShenAIAndroidSDK.HealthRisks risks = sdk.getReferenceHealthRisks(factors);
1031
+ JSObject ret = new JSObject();
1032
+ ret.put("value", healthRisksToJSObject(risks));
1033
+ call.resolve(ret);
1034
+ } catch (JSONException e) {
1035
+ call.reject("Failed to parse risks factors: " + e.getMessage());
1036
+ }
1037
+ }
1038
+
1039
+ /** computeHealthRisks(options: { risksFactors: object }):Promise<object> */
1040
+ @PluginMethod
1041
+ public void computeHealthRisks(PluginCall call) {
1042
+ if (sdk == null) { call.reject("not initialized"); return; }
1043
+ JSObject obj = call.getObject("risksFactors", new JSObject());
1044
+ try {
1045
+ ShenAIAndroidSDK.RisksFactors factors = jsObjectToRisksFactors(obj);
1046
+ ShenAIAndroidSDK.HealthRisks risks = sdk.computeHealthRisks(factors);
1047
+ JSObject ret = new JSObject();
1048
+ ret.put("value", healthRisksToJSObject(risks));
1049
+ call.resolve(ret);
1050
+ } catch (JSONException e) {
1051
+ call.reject("Failed to parse risks factors: " + e.getMessage());
1052
+ }
1053
+ }
1054
+
1055
+ /** getSignalQualityMapPng(): Promise<{ value: number[] }> */
1056
+ @PluginMethod
1057
+ public void getSignalQualityMapPng(PluginCall call) {
1058
+ JSObject ret = new JSObject();
1059
+ if (sdk != null) {
1060
+ byte[] data = sdk.getSignalQualityMapPng();
1061
+ JSONArray arr = new JSONArray();
1062
+ for (byte b : data) arr.put(b & 0xFF);
1063
+ ret.put("value", arr);
1064
+ }
1065
+ call.resolve(ret);
1066
+ }
1067
+
1068
+ /** getFaceTexturePng(): Promise<{ value: number[] }> */
1069
+ @PluginMethod
1070
+ public void getFaceTexturePng(PluginCall call) {
1071
+ JSObject ret = new JSObject();
1072
+ if (sdk != null) {
1073
+ byte[] data = sdk.getFaceTexturePng();
1074
+ JSONArray arr = new JSONArray();
1075
+ for (byte b : data) arr.put(b & 0xFF);
1076
+ ret.put("value", arr);
1077
+ }
1078
+ call.resolve(ret);
1079
+ }
1080
+
1081
+ /** openMeasurementResultsPdfInBrowser(): Promise<void> */
1082
+ @PluginMethod
1083
+ public void openMeasurementResultsPdfInBrowser(PluginCall call) {
1084
+ if (sdk != null) sdk.openMeasurementResultsPdfInBrowser();
1085
+ call.resolve();
1086
+ }
1087
+
1088
+ /** sendMeasurementResultsPdfToEmail(options: { email:string }): Promise<void> */
1089
+ @PluginMethod
1090
+ public void sendMeasurementResultsPdfToEmail(PluginCall call) {
1091
+ String email = call.getString("email");
1092
+ if (sdk != null && email != null) sdk.sendMeasurementResultsPdfToEmail(email);
1093
+ call.resolve();
1094
+ }
1095
+
1096
+ /** requestMeasurementResultsPdfUrl(): Promise<void> */
1097
+ @PluginMethod
1098
+ public void requestMeasurementResultsPdfUrl(PluginCall call) {
1099
+ if (sdk != null) sdk.requestMeasurementResultsPdfUrl();
1100
+ call.resolve();
1101
+ }
1102
+
1103
+ /** getMeasurementResultsPdfUrl(): Promise<{ value: string | null }> */
1104
+ @PluginMethod
1105
+ public void getMeasurementResultsPdfUrl(PluginCall call) {
1106
+ JSObject ret = new JSObject();
1107
+ if (sdk != null) {
1108
+ String url = sdk.getMeasurementResultsPdfUrl();
1109
+ if (url == null || url.isEmpty()) ret.put("value", JSObject.NULL); else ret.put("value", url);
1110
+ } else {
1111
+ ret.put("value", JSObject.NULL);
1112
+ }
1113
+ call.resolve(ret);
1114
+ }
1115
+
1116
+ /** requestMeasurementResultsPdfBytes(): Promise<void> */
1117
+ @PluginMethod
1118
+ public void requestMeasurementResultsPdfBytes(PluginCall call) {
1119
+ if (sdk != null) sdk.requestMeasurementResultsPdfBytes();
1120
+ call.resolve();
1121
+ }
1122
+
1123
+ /** getMeasurementResultsPdfBytes(): Promise<{ value: number[] | null }> */
1124
+ @PluginMethod
1125
+ public void getMeasurementResultsPdfBytes(PluginCall call) {
1126
+ JSObject ret = new JSObject();
1127
+ if (sdk != null) {
1128
+ byte[] bytes = sdk.getMeasurementResultsPdfBytes();
1129
+ if (bytes == null) {
1130
+ ret.put("value", JSObject.NULL);
1131
+ } else {
1132
+ ret.put("valueBase64", Base64.encodeToString(bytes, Base64.NO_WRAP));
1133
+ }
1134
+ }
1135
+ call.resolve(ret);
1136
+ }
1137
+
1138
+ /** getResultAsFhirObservation(): Promise<{ value: string | null }> */
1139
+ @PluginMethod
1140
+ public void getResultAsFhirObservation(PluginCall call) {
1141
+ JSObject ret = new JSObject();
1142
+ if (sdk != null) {
1143
+ String observation = sdk.getResultAsFhirObservation();
1144
+ ret.put("value", (observation == null || observation.isEmpty()) ? JSObject.NULL : observation);
1145
+ } else {
1146
+ ret.put("value", JSObject.NULL);
1147
+ }
1148
+ call.resolve(ret);
1149
+ }
1150
+
1151
+ /** sendResultFhirObservation({ url }): Promise<{ value: string | null }> */
1152
+ @PluginMethod
1153
+ public void sendResultFhirObservation(PluginCall call) {
1154
+ String url = call.getString("url");
1155
+ if (sdk == null) {
1156
+ call.reject("SDK is not initialized");
1157
+ return;
1158
+ }
1159
+ if (url == null || url.isEmpty()) {
1160
+ call.reject("url is required");
1161
+ return;
1162
+ }
1163
+
1164
+ Handler mainHandler = new Handler(Looper.getMainLooper());
1165
+ sdk.sendResultFhirObservation(url, response -> {
1166
+ JSObject ret = new JSObject();
1167
+ String normalized = (response == null || response.isEmpty()) ? null : response;
1168
+ ret.put("value", normalized == null ? JSObject.NULL : normalized);
1169
+ mainHandler.post(() -> call.resolve(ret));
1170
+ });
1171
+ }
1172
+
1173
+ // ──────────────────────────────────────────────────────────── helpers
1174
+ private void applyInitializationSettings(JSObject obj,
1175
+ ShenAIAndroidSDK.InitializationSettings settings) throws JSONException {
1176
+ if (obj == null) return;
1177
+
1178
+ if (obj.has("precisionMode"))
1179
+ settings.precisionMode = ShenAIAndroidSDK.PrecisionMode.values()[obj.getInt("precisionMode")];
1180
+ if (obj.has("operatingMode"))
1181
+ settings.operatingMode = ShenAIAndroidSDK.OperatingMode.values()[obj.getInt("operatingMode")];
1182
+ if (obj.has("measurementPreset"))
1183
+ settings.measurementPreset = ShenAIAndroidSDK.MeasurementPreset.values()[obj.getInt("measurementPreset")];
1184
+ if (obj.has("cameraMode"))
1185
+ settings.cameraMode = ShenAIAndroidSDK.CameraMode.values()[obj.getInt("cameraMode")];
1186
+ if (obj.has("onboardingMode"))
1187
+ settings.onboardingMode = ShenAIAndroidSDK.OnboardingMode.values()[obj.getInt("onboardingMode")];
1188
+ if (obj.has("initializationMode"))
1189
+ settings.initializationMode = ShenAIAndroidSDK.InitializationMode.values()[obj.getInt("initializationMode")];
1190
+
1191
+ if (obj.has("showUserInterface"))
1192
+ settings.showUserInterface = obj.getBoolean("showUserInterface");
1193
+ if (obj.has("showFacePositioningOverlay"))
1194
+ settings.showFacePositioningOverlay = obj.getBoolean("showFacePositioningOverlay");
1195
+ if (obj.has("showVisualWarnings"))
1196
+ settings.showVisualWarnings = obj.getBoolean("showVisualWarnings");
1197
+ if (obj.has("enableCameraSwap"))
1198
+ settings.enableCameraSwap = obj.getBoolean("enableCameraSwap");
1199
+ if (obj.has("showFaceMask"))
1200
+ settings.showFaceMask = obj.getBoolean("showFaceMask");
1201
+ if (obj.has("showBloodFlow"))
1202
+ settings.showBloodFlow = obj.getBoolean("showBloodFlow");
1203
+ if (obj.has("hideShenaiLogo"))
1204
+ settings.hideShenaiLogo = obj.getBoolean("hideShenaiLogo");
1205
+ if (obj.has("includeTimestampInPdf"))
1206
+ settings.includeTimestampInPdf = obj.getBoolean("includeTimestampInPdf");
1207
+ if (obj.has("pdfEmailSubject"))
1208
+ settings.pdfEmailSubject = obj.getString("pdfEmailSubject");
1209
+ if (obj.has("pdfEmailBody"))
1210
+ settings.pdfEmailBody = obj.getString("pdfEmailBody");
1211
+ if (obj.has("enableStartAfterSuccess"))
1212
+ settings.enableStartAfterSuccess = obj.getBoolean("enableStartAfterSuccess");
1213
+ if (obj.has("enableSummaryScreen"))
1214
+ settings.enableSummaryScreen = obj.getBoolean("enableSummaryScreen");
1215
+ if (obj.has("showResultsFinishButton"))
1216
+ settings.showResultsFinishButton = obj.getBoolean("showResultsFinishButton");
1217
+ if (obj.has("enableHealthRisks"))
1218
+ settings.enableHealthRisks = obj.getBoolean("enableHealthRisks");
1219
+ if (obj.has("showHealthIndicesFinishButton"))
1220
+ settings.showHealthIndicesFinishButton = obj.getBoolean("showHealthIndicesFinishButton");
1221
+ if (obj.has("saveHealthRisksFactors"))
1222
+ settings.saveHealthRisksFactors = obj.getBoolean("saveHealthRisksFactors");
1223
+ if (obj.has("showOutOfRangeResultIndicators"))
1224
+ settings.showOutOfRangeResultIndicators = obj.getBoolean("showOutOfRangeResultIndicators");
1225
+ if (obj.has("showTrialMetricLabels"))
1226
+ settings.showTrialMetricLabels = obj.getBoolean("showTrialMetricLabels");
1227
+ if (obj.has("showSignalQualityIndicator"))
1228
+ settings.showSignalQualityIndicator = obj.getBoolean("showSignalQualityIndicator");
1229
+ if (obj.has("showSignalTile"))
1230
+ settings.showSignalTile = obj.getBoolean("showSignalTile");
1231
+ if (obj.has("showStartStopButton"))
1232
+ settings.showStartStopButton = obj.getBoolean("showStartStopButton");
1233
+ if (obj.has("showInfoButton"))
1234
+ settings.showInfoButton = obj.getBoolean("showInfoButton");
1235
+ if (obj.has("showDisclaimer"))
1236
+ settings.showDisclaimer = obj.getBoolean("showDisclaimer");
1237
+ if (obj.has("enableMeasurementsDashboard"))
1238
+ settings.enableMeasurementsDashboard = obj.getBoolean("enableMeasurementsDashboard");
1239
+ if (obj.has("uiVersion"))
1240
+ settings.uiVersion = ShenAIAndroidSDK.UiVersion.values()[obj.getInt("uiVersion")];
1241
+ if (obj.has("frameWidth"))
1242
+ settings.frameWidth = obj.getInt("frameWidth");
1243
+ if (obj.has("frameHeight"))
1244
+ settings.frameHeight = obj.getInt("frameHeight");
1245
+ if (obj.has("rotation"))
1246
+ settings.rotation = obj.getInt("rotation");
1247
+ if (obj.has("offlineProcessing"))
1248
+ settings.offlineProcessing = obj.getBoolean("offlineProcessing");
1249
+ if (obj.has("uiFlowScreens")) {
1250
+ JSONArray array = obj.getJSONArray("uiFlowScreens");
1251
+ settings.uiFlowScreens.clear();
1252
+ for (int i = 0; i < array.length(); ++i) {
1253
+ settings.uiFlowScreens.add(ShenAIAndroidSDK.Screen.values()[array.getInt(i)]);
1254
+ }
1255
+ }
1256
+
1257
+ if (obj.has("risksFactors")) {
1258
+ JSObject factorsObj = obj.getJSObject("risksFactors");
1259
+ settings.risksFactors = jsObjectToRisksFactors(factorsObj);
1260
+ }
1261
+ }
1262
+
1263
+ private ShenAIAndroidSDK.RisksFactors jsObjectToRisksFactors(JSObject obj) throws JSONException {
1264
+ ShenAIAndroidSDK.RisksFactors factors = sdk.new RisksFactors();
1265
+ if (obj == null) return factors;
1266
+
1267
+ if (obj.has("age")) factors.age = Optional.of(obj.getInt("age"));
1268
+ if (obj.has("cholesterol")) factors.cholesterol = Optional.of((float)obj.getDouble("cholesterol"));
1269
+ if (obj.has("cholesterolHdl")) factors.cholesterolHdl = Optional.of((float)obj.getDouble("cholesterolHdl"));
1270
+ if (obj.has("sbp")) factors.sbp = Optional.of((float)obj.getDouble("sbp"));
1271
+ if (obj.has("dbp")) factors.dbp = Optional.of((float)obj.getDouble("dbp"));
1272
+ if (obj.has("isSmoker")) factors.isSmoker = Optional.of(obj.getBoolean("isSmoker"));
1273
+ if (obj.has("hypertensionTreatment")) {
1274
+ int index = obj.getInt("hypertensionTreatment");
1275
+ factors.hypertensionTreatment = Optional.of(ShenAIAndroidSDK.HypertensionTreatment.values()[index]);
1276
+ }
1277
+ if (obj.has("hasDiabetes")) factors.hasDiabetes = Optional.of(obj.getBoolean("hasDiabetes"));
1278
+ if (obj.has("bodyHeight")) factors.bodyHeight = Optional.of((float)obj.getDouble("bodyHeight"));
1279
+ if (obj.has("bodyWeight")) factors.bodyWeight = Optional.of((float)obj.getDouble("bodyWeight"));
1280
+ if (obj.has("waistCircumference")) factors.waistCircumference = Optional.of((float)obj.getDouble("waistCircumference"));
1281
+ if (obj.has("neckCircumference")) factors.neckCircumference = Optional.of((float)obj.getDouble("neckCircumference"));
1282
+ if (obj.has("hipCircumference")) factors.hipCircumference = Optional.of((float)obj.getDouble("hipCircumference"));
1283
+ if (obj.has("gender")) {
1284
+ int index = obj.getInt("gender");
1285
+ factors.gender = Optional.of(ShenAIAndroidSDK.Gender.values()[index]);
1286
+ }
1287
+ if (obj.has("physicalActivity")) {
1288
+ int index = obj.getInt("physicalActivity");
1289
+ factors.physicalActivity = Optional.of(ShenAIAndroidSDK.PhysicalActivity.values()[index]);
1290
+ }
1291
+ if (obj.has("country")) factors.country = obj.getString("country");
1292
+ if (obj.has("race")) {
1293
+ int index = obj.getInt("race");
1294
+ factors.race = Optional.of(ShenAIAndroidSDK.Race.values()[index]);
1295
+ }
1296
+ if (obj.has("vegetableFruitDiet")) factors.vegetableFruitDiet = Optional.of(obj.getBoolean("vegetableFruitDiet"));
1297
+ if (obj.has("historyOfHypertension")) factors.historyOfHypertension = Optional.of(obj.getBoolean("historyOfHypertension"));
1298
+ if (obj.has("historyOfHighGlucose")) factors.historyOfHighGlucose = Optional.of(obj.getBoolean("historyOfHighGlucose"));
1299
+ if (obj.has("fastingGlucose")) factors.fastingGlucose = Optional.of((float)obj.getDouble("fastingGlucose"));
1300
+ if (obj.has("triglyceride")) factors.triglyceride = Optional.of((float)obj.getDouble("triglyceride"));
1301
+ if (obj.has("parentalHypertension")) {
1302
+ int index = obj.getInt("parentalHypertension");
1303
+ factors.parentalHypertension = Optional.of(ShenAIAndroidSDK.ParentalHistory.values()[index]);
1304
+ }
1305
+ if (obj.has("familyDiabetes")) {
1306
+ int index = obj.getInt("familyDiabetes");
1307
+ factors.familyDiabetes = Optional.of(ShenAIAndroidSDK.FamilyHistory.values()[index]);
1308
+ }
1309
+
1310
+ return factors;
1311
+ }
1312
+
1313
+ private JSObject risksFactorsToJSObject(ShenAIAndroidSDK.RisksFactors factors) {
1314
+ JSObject obj = new JSObject();
1315
+ factors.age.ifPresent(v -> obj.put("age", v));
1316
+ factors.cholesterol.ifPresent(v -> obj.put("cholesterol", v));
1317
+ factors.cholesterolHdl.ifPresent(v -> obj.put("cholesterolHdl", v));
1318
+ factors.sbp.ifPresent(v -> obj.put("sbp", v));
1319
+ factors.dbp.ifPresent(v -> obj.put("dbp", v));
1320
+ factors.isSmoker.ifPresent(v -> obj.put("isSmoker", v));
1321
+ factors.hypertensionTreatment.ifPresent(v -> obj.put("hypertensionTreatment", v.ordinal()));
1322
+ factors.hasDiabetes.ifPresent(v -> obj.put("hasDiabetes", v));
1323
+ factors.bodyHeight.ifPresent(v -> obj.put("bodyHeight", v));
1324
+ factors.bodyWeight.ifPresent(v -> obj.put("bodyWeight", v));
1325
+ factors.waistCircumference.ifPresent(v -> obj.put("waistCircumference", v));
1326
+ factors.neckCircumference.ifPresent(v -> obj.put("neckCircumference", v));
1327
+ factors.hipCircumference.ifPresent(v -> obj.put("hipCircumference", v));
1328
+ factors.gender.ifPresent(v -> obj.put("gender", v.ordinal()));
1329
+ factors.physicalActivity.ifPresent(v -> obj.put("physicalActivity", v.ordinal()));
1330
+ if (factors.country != null) obj.put("country", factors.country);
1331
+ factors.race.ifPresent(v -> obj.put("race", v.ordinal()));
1332
+ factors.vegetableFruitDiet.ifPresent(v -> obj.put("vegetableFruitDiet", v));
1333
+ factors.historyOfHypertension.ifPresent(v -> obj.put("historyOfHypertension", v));
1334
+ factors.historyOfHighGlucose.ifPresent(v -> obj.put("historyOfHighGlucose", v));
1335
+ factors.fastingGlucose.ifPresent(v -> obj.put("fastingGlucose", v));
1336
+ factors.triglyceride.ifPresent(v -> obj.put("triglyceride", v));
1337
+ factors.familyDiabetes.ifPresent(v -> obj.put("familyDiabetes", v.ordinal()));
1338
+ factors.parentalHypertension.ifPresent(v -> obj.put("parentalHypertension", v.ordinal()));
1339
+ return obj;
1340
+ }
1341
+
1342
+ private JSObject healthRisksToJSObject(ShenAIAndroidSDK.HealthRisks risks) {
1343
+ JSObject ret = new JSObject();
1344
+ JSObject hard = new JSObject();
1345
+ hard.put("coronaryDeathEventRisk", risks.hardAndFatalEvents.coronaryDeathEventRisk.orElse(null));
1346
+ hard.put("fatalStrokeEventRisk", risks.hardAndFatalEvents.fatalStrokeEventRisk.orElse(null));
1347
+ hard.put("totalCvMortalityRisk", risks.hardAndFatalEvents.totalCVMortalityRisk.orElse(null));
1348
+ hard.put("hardCvEventRisk", risks.hardAndFatalEvents.hardCVEventRisk.orElse(null));
1349
+ ret.put("hardAndFatalEvents", hard);
1350
+
1351
+ JSObject cv = new JSObject();
1352
+ cv.put("overallRisk", risks.cvDiseases.overallRisk.orElse(null));
1353
+ cv.put("coronaryHeartDiseaseRisk", risks.cvDiseases.coronaryHeartDiseaseRisk.orElse(null));
1354
+ cv.put("strokeRisk", risks.cvDiseases.strokeRisk.orElse(null));
1355
+ cv.put("heartFailureRisk", risks.cvDiseases.heartFailureRisk.orElse(null));
1356
+ cv.put("peripheralVascularDiseaseRisk", risks.cvDiseases.peripheralVascularDiseaseRisk.orElse(null));
1357
+ ret.put("cvDiseases", cv);
1358
+
1359
+ JSObject scores = new JSObject();
1360
+ scores.put("ageScore", risks.scores.ageScore.orElse(null));
1361
+ scores.put("sbpScore", risks.scores.sbpScore.orElse(null));
1362
+ scores.put("smokingScore", risks.scores.smokingScore.orElse(null));
1363
+ scores.put("diabetesScore", risks.scores.diabetesScore.orElse(null));
1364
+ scores.put("bmiScore", risks.scores.bmiScore.orElse(null));
1365
+ scores.put("cholesterolScore", risks.scores.cholesterolScore.orElse(null));
1366
+ scores.put("cholesterolHdlScore", risks.scores.cholesterolHdlScore.orElse(null));
1367
+ scores.put("totalScore", risks.scores.totalScore.orElse(null));
1368
+ ret.put("scores", scores);
1369
+
1370
+ ret.put("wellnessScore", risks.wellnessScore.orElse(null));
1371
+ ret.put("vascularAge", risks.vascularAge.orElse(null));
1372
+ ret.put("waistToHeightRatio", risks.waistToHeightRatio.orElse(null));
1373
+ ret.put("bodyFatPercentage", risks.bodyFatPercentage.orElse(null));
1374
+ ret.put("basalMetabolicRate", risks.basalMetabolicRate.orElse(null));
1375
+ ret.put("bodyRoundnessIndex", risks.bodyRoundnessIndex.orElse(null));
1376
+ ret.put("conicityIndex", risks.conicityIndex.orElse(null));
1377
+ ret.put("aBodyShapeIndex", risks.aBodyShapeIndex.orElse(null));
1378
+ ret.put("totalDailyEnergyExpenditure", risks.totalDailyEnergyExpenditure.orElse(null));
1379
+ ret.put("hypertensionRisk", risks.hypertensionRisk.orElse(null));
1380
+ ret.put("diabetesRisk", risks.diabetesRisk.orElse(null));
1381
+ ret.put("nonAlcoholicFattyLiverDiseaseRisk", risks.nonAlcoholicFattyLiverDiseaseRisk.map(Enum::ordinal).orElse(null));
1382
+ return ret;
1383
+ }
1384
+
1385
+ private JSONArray heartbeatsToJSONArray(ShenAIAndroidSDK.Heartbeat[] hbs) {
1386
+ JSONArray arr = new JSONArray();
1387
+ if (hbs != null) {
1388
+ for (ShenAIAndroidSDK.Heartbeat hb : hbs) {
1389
+ JSObject hbObj = new JSObject();
1390
+ hbObj.put("startLocationSec", hb.startLocationSec);
1391
+ hbObj.put("endLocationSec", hb.endLocationSec);
1392
+ hbObj.put("durationMs", hb.durationMs);
1393
+ arr.put(hbObj);
1394
+ }
1395
+ }
1396
+ return arr;
1397
+ }
1398
+
1399
+ private JSObject measurementQualityMetricsToJSObject(ShenAIAndroidSDK.MeasurementQualityMetrics metrics) {
1400
+ if (metrics == null) return null;
1401
+ JSObject obj = new JSObject();
1402
+ obj.put("ppgQualityIndex", metrics.ppgQualityIndex.orElse(null));
1403
+ obj.put("bcgQualityIndex", metrics.bcgQualityIndex.orElse(null));
1404
+ obj.put("breathingQualityIndex", metrics.breathingQualityIndex.orElse(null));
1405
+ obj.put("bloodPressureQualityIndex", metrics.bloodPressureQualityIndex.orElse(null));
1406
+ obj.put("expectedSbpMedianAbsErrorMmhg", metrics.expectedSbpMedianAbsErrorMmhg.orElse(null));
1407
+ obj.put("expectedSbpP80AbsErrorMmhg", metrics.expectedSbpP80AbsErrorMmhg.orElse(null));
1408
+ obj.put("expectedSbpMeanAbsErrorMmhg", metrics.expectedSbpMeanAbsErrorMmhg.orElse(null));
1409
+ obj.put("expectedSbpBalancedMaeMmhg", metrics.expectedSbpBalancedMaeMmhg.orElse(null));
1410
+ obj.put("expectedDbpMedianAbsErrorMmhg", metrics.expectedDbpMedianAbsErrorMmhg.orElse(null));
1411
+ obj.put("expectedDbpP80AbsErrorMmhg", metrics.expectedDbpP80AbsErrorMmhg.orElse(null));
1412
+ obj.put("expectedDbpMeanAbsErrorMmhg", metrics.expectedDbpMeanAbsErrorMmhg.orElse(null));
1413
+ obj.put("expectedDbpBalancedMaeMmhg", metrics.expectedDbpBalancedMaeMmhg.orElse(null));
1414
+ return obj;
1415
+ }
1416
+
1417
+ private JSObject measurementResultsToJSObject(ShenAIAndroidSDK.MeasurementResults r) {
1418
+ JSObject obj = new JSObject();
1419
+ obj.put("heartRateBpm", r.hrBpm);
1420
+ obj.put("averageSignalQuality", r.averageSignalQuality);
1421
+ obj.put("heartbeats", heartbeatsToJSONArray(r.heartbeats));
1422
+ obj.put("breathingRateBpm", r.brBpm.orElse(null));
1423
+ obj.put("hrvLnrmssdMs", r.hrvLnrmssdMs.orElse(null));
1424
+ obj.put("hrvSdnnMs", r.hrvSdnnMs.orElse(null));
1425
+ obj.put("stressIndex", r.stressIndex.orElse(null));
1426
+ obj.put("parasympatheticActivity", r.parasympatheticActivity.orElse(null));
1427
+ obj.put("systolicBloodPressureMmhg", r.systolicBloodPressureMmhg.orElse(null));
1428
+ obj.put("diastolicBloodPressureMmhg", r.diastolicBloodPressureMmhg.orElse(null));
1429
+ obj.put("cardiacWorkloadMmhgPerSec", r.cardiacWorkloadMmhgPerSec.orElse(null));
1430
+ obj.put("ageYears", r.ageYears.orElse(null));
1431
+ obj.put("bmiKgPerM2", r.bmiKgPerM2.orElse(null));
1432
+ obj.put("bmiCategory", r.bmiCategory.map(Enum::ordinal).orElse(null));
1433
+ obj.put("weightKg", r.weightKg.orElse(null));
1434
+ obj.put("heightCm", r.heightCm.orElse(null));
1435
+ obj.put("qualityMetrics", measurementQualityMetricsToJSObject(r.qualityMetrics));
1436
+ return obj;
1437
+ }
1438
+
1439
+ private JSObject measurementResultsWithMetadataToJSObject(ShenAIAndroidSDK.MeasurementResultsWithMetadata md) {
1440
+ JSObject obj = new JSObject();
1441
+ obj.put("measurementResults", measurementResultsToJSObject(md.measurementResults));
1442
+ obj.put("epochTimestamp", md.epochTimestamp);
1443
+ obj.put("isCalibration", md.isCalibration);
1444
+ return obj;
1445
+ }
1446
+
1447
+ // ───────────────────────────────────────────────────── lifecycle glue
1448
+ @Override
1449
+ protected void handleOnResume() {
1450
+ if (shenaiView != null) shenaiView.onResume();
1451
+ }
1452
+ @Override
1453
+ protected void handleOnPause() {
1454
+ if (shenaiView != null) shenaiView.onPause();
1455
+ }
1456
+ @Override
1457
+ public void handleOnDestroy() {
1458
+ if (shenaiView != null || containerView != null) {
1459
+ getActivity().runOnUiThread(() -> {
1460
+ try {
1461
+ getBridge().getWebView().setOnTouchListener(null);
1462
+ getBridge().getWebView().setAlpha(1.0f);
1463
+ } catch (Throwable ignored) {}
1464
+
1465
+ if (containerView != null) {
1466
+ ViewGroup parent = (ViewGroup) containerView.getParent();
1467
+ if (parent != null) parent.removeView(containerView);
1468
+ }
1469
+ });
1470
+ shenaiView = null;
1471
+ containerView = null;
1472
+ }
1473
+ sdk = null;
1474
+ }
1475
+
1476
+ }