@capgo/camera-preview 8.4.3 → 8.4.5

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.
@@ -6,7 +6,11 @@ import static android.Manifest.permission.RECORD_AUDIO;
6
6
  import android.Manifest;
7
7
  import android.annotation.SuppressLint;
8
8
  import android.app.Activity;
9
+ import android.app.KeyguardManager;
10
+ import android.content.BroadcastReceiver;
11
+ import android.content.Context;
9
12
  import android.content.Intent;
13
+ import android.content.IntentFilter;
10
14
  import android.content.pm.ActivityInfo;
11
15
  import android.content.pm.PackageManager;
12
16
  import android.content.res.Configuration;
@@ -69,12 +73,19 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
69
73
 
70
74
  private final String pluginVersion = "";
71
75
 
76
+ @Override
77
+ public void load() {
78
+ registerScreenLockReceiver();
79
+ }
80
+
72
81
  @Override
73
82
  protected void handleOnPause() {
74
83
  super.handleOnPause();
84
+ activityPaused = true;
75
85
  if (cameraXView != null && cameraXView.isRunning()) {
76
86
  // Store the current configuration before stopping
77
87
  lastSessionConfig = cameraXView.getSessionConfig();
88
+ requestBarcodeScannerRestartAfterCameraResume();
78
89
  cameraXView.stopSession();
79
90
  }
80
91
  }
@@ -82,6 +93,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
82
93
  @Override
83
94
  protected void handleOnResume() {
84
95
  super.handleOnResume();
96
+ registerScreenLockReceiver();
97
+ activityPaused = false;
85
98
  if (lastSessionConfig != null) {
86
99
  // Recreate camera with last known configuration
87
100
  if (cameraXView == null || !cameraXView.isRunning() || cameraXView.isStopping()) {
@@ -97,6 +110,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
97
110
  } else {
98
111
  toBackVisualStateActive = false;
99
112
  }
113
+ requestBarcodeScannerRestartAfterCameraResume();
114
+ cameraRestartAfterResumeInProgress = true;
100
115
  cameraXView.startSession(lastSessionConfig);
101
116
  }
102
117
  }
@@ -109,10 +124,12 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
109
124
  cameraXView = null;
110
125
  }
111
126
  lastSessionConfig = null;
127
+ clearActiveBarcodeScanner();
112
128
  toBackVisualStateActive = false;
113
129
  restoreOriginalWindowBackground(getBridge().getActivity());
114
130
  restoreWebViewVisualState();
115
131
  restoreSystemUiForToBackMode(getBridge().getActivity());
132
+ unregisterScreenLockReceiver();
116
133
  }
117
134
 
118
135
  private CameraSessionConfiguration lastSessionConfig;
@@ -155,6 +172,26 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
155
172
  private boolean pendingStartBarcodeScanner = false;
156
173
  private List<String> pendingStartBarcodeFormats = new ArrayList<>();
157
174
  private int pendingStartBarcodeDetectionInterval = 500;
175
+ private final Object activeBarcodeScannerLock = new Object();
176
+ private boolean activeBarcodeScanner = false;
177
+ private List<String> activeBarcodeFormats = new ArrayList<>();
178
+ private int activeBarcodeDetectionInterval = 500;
179
+ private boolean restartBarcodeScannerAfterCameraResume = false;
180
+ private boolean screenLocked = false;
181
+ private boolean activityPaused = false;
182
+ private boolean cameraRestartAfterResumeInProgress = false;
183
+ private BroadcastReceiver screenLockReceiver;
184
+
185
+ private static final class BarcodeScannerRequest {
186
+
187
+ private final List<String> formats;
188
+ private final int detectionInterval;
189
+
190
+ private BarcodeScannerRequest(List<String> formats, int detectionInterval) {
191
+ this.formats = formats;
192
+ this.detectionInterval = detectionInterval;
193
+ }
194
+ }
158
195
 
159
196
  @PluginMethod
160
197
  public void getExposureModes(PluginCall call) {
@@ -426,6 +463,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
426
463
  new CameraXView.BarcodeScannerStartCallback() {
427
464
  @Override
428
465
  public void onStarted() {
466
+ markActiveBarcodeScanner(formats, detectionInterval != null ? detectionInterval : 500);
429
467
  call.resolve();
430
468
  }
431
469
 
@@ -439,6 +477,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
439
477
 
440
478
  @PluginMethod
441
479
  public void stopBarcodeScanner(PluginCall call) {
480
+ clearActiveBarcodeScanner();
442
481
  if (cameraXView != null) {
443
482
  cameraXView.stopBarcodeScanner();
444
483
  }
@@ -500,6 +539,176 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
500
539
  pendingStartBarcodeDetectionInterval = 500;
501
540
  }
502
541
 
542
+ private void markActiveBarcodeScanner(List<String> formats, int detectionInterval) {
543
+ synchronized (activeBarcodeScannerLock) {
544
+ activeBarcodeScanner = true;
545
+ activeBarcodeFormats = new ArrayList<>(formats);
546
+ activeBarcodeDetectionInterval = detectionInterval;
547
+ }
548
+ }
549
+
550
+ private void clearActiveBarcodeScanner() {
551
+ synchronized (activeBarcodeScannerLock) {
552
+ activeBarcodeScanner = false;
553
+ activeBarcodeFormats = new ArrayList<>();
554
+ activeBarcodeDetectionInterval = 500;
555
+ restartBarcodeScannerAfterCameraResume = false;
556
+ }
557
+ }
558
+
559
+ private void requestBarcodeScannerRestartAfterCameraResume() {
560
+ synchronized (activeBarcodeScannerLock) {
561
+ if (activeBarcodeScanner) {
562
+ restartBarcodeScannerAfterCameraResume = true;
563
+ }
564
+ }
565
+ }
566
+
567
+ private BarcodeScannerRequest consumeBarcodeScannerRestartRequest() {
568
+ synchronized (activeBarcodeScannerLock) {
569
+ if (!activeBarcodeScanner || !restartBarcodeScannerAfterCameraResume) {
570
+ return null;
571
+ }
572
+ restartBarcodeScannerAfterCameraResume = false;
573
+ return new BarcodeScannerRequest(new ArrayList<>(activeBarcodeFormats), activeBarcodeDetectionInterval);
574
+ }
575
+ }
576
+
577
+ private void restoreBarcodeScannerRestartRequest(BarcodeScannerRequest request) {
578
+ if (request == null) {
579
+ return;
580
+ }
581
+ synchronized (activeBarcodeScannerLock) {
582
+ if (activeBarcodeScanner) {
583
+ restartBarcodeScannerAfterCameraResume = true;
584
+ }
585
+ }
586
+ }
587
+
588
+ private void restartBarcodeScannerAfterCameraResumeIfNeeded() {
589
+ if (screenLocked || isDeviceLocked()) {
590
+ return;
591
+ }
592
+
593
+ if (cameraXView == null || !cameraXView.isRunning()) {
594
+ return;
595
+ }
596
+
597
+ BarcodeScannerRequest request = consumeBarcodeScannerRestartRequest();
598
+ if (request == null) {
599
+ return;
600
+ }
601
+
602
+ cameraXView.startBarcodeScanner(
603
+ request.formats,
604
+ request.detectionInterval,
605
+ new CameraXView.BarcodeScannerStartCallback() {
606
+ @Override
607
+ public void onStarted() {
608
+ markActiveBarcodeScanner(request.formats, request.detectionInterval);
609
+ }
610
+
611
+ @Override
612
+ public void onError(String message) {
613
+ restoreBarcodeScannerRestartRequest(request);
614
+ onBarcodeScanError("Failed to restart barcode scanner after unlock: " + message);
615
+ }
616
+ }
617
+ );
618
+ }
619
+
620
+ private void registerScreenLockReceiver() {
621
+ if (screenLockReceiver != null) {
622
+ return;
623
+ }
624
+
625
+ screenLockReceiver = new BroadcastReceiver() {
626
+ @Override
627
+ public void onReceive(Context context, Intent intent) {
628
+ String action = intent != null ? intent.getAction() : null;
629
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
630
+ handleScreenOff();
631
+ } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
632
+ handleScreenOn();
633
+ } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
634
+ handleScreenUnlocked();
635
+ }
636
+ }
637
+ };
638
+
639
+ IntentFilter filter = new IntentFilter();
640
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
641
+ filter.addAction(Intent.ACTION_SCREEN_ON);
642
+ filter.addAction(Intent.ACTION_USER_PRESENT);
643
+
644
+ Context context = getContext();
645
+ if (context == null) {
646
+ screenLockReceiver = null;
647
+ return;
648
+ }
649
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
650
+ context.registerReceiver(screenLockReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
651
+ } else {
652
+ context.registerReceiver(screenLockReceiver, filter);
653
+ }
654
+ }
655
+
656
+ private void unregisterScreenLockReceiver() {
657
+ if (screenLockReceiver == null) {
658
+ return;
659
+ }
660
+ try {
661
+ Context context = getContext();
662
+ if (context != null) {
663
+ context.unregisterReceiver(screenLockReceiver);
664
+ }
665
+ } catch (IllegalArgumentException ignored) {
666
+ // Receiver may already be unregistered by the host activity.
667
+ } finally {
668
+ screenLockReceiver = null;
669
+ }
670
+ }
671
+
672
+ private boolean isDeviceLocked() {
673
+ Context context = getContext();
674
+ KeyguardManager keyguardManager = context != null ? (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE) : null;
675
+ return keyguardManager != null && keyguardManager.isKeyguardLocked();
676
+ }
677
+
678
+ private void handleScreenOff() {
679
+ screenLocked = true;
680
+ requestBarcodeScannerRestartAfterCameraResume();
681
+ if (cameraXView != null && cameraXView.isRunning() && !cameraXView.isStopping()) {
682
+ lastSessionConfig = cameraXView.getSessionConfig();
683
+ cameraXView.stopSession();
684
+ }
685
+ }
686
+
687
+ private void handleScreenOn() {
688
+ screenLocked = isDeviceLocked();
689
+ if (!screenLocked) {
690
+ restartBarcodeScannerAfterCameraResumeIfNeeded();
691
+ }
692
+ }
693
+
694
+ private void handleScreenUnlocked() {
695
+ screenLocked = false;
696
+ if (
697
+ !activityPaused &&
698
+ !cameraRestartAfterResumeInProgress &&
699
+ lastSessionConfig != null &&
700
+ (cameraXView == null || cameraXView.isStopping())
701
+ ) {
702
+ cameraXView = new CameraXView(getContext(), getBridge().getWebView());
703
+ cameraXView.setListener(this);
704
+ requestBarcodeScannerRestartAfterCameraResume();
705
+ cameraRestartAfterResumeInProgress = true;
706
+ cameraXView.startSession(lastSessionConfig);
707
+ return;
708
+ }
709
+ restartBarcodeScannerAfterCameraResumeIfNeeded();
710
+ }
711
+
503
712
  @PluginMethod
504
713
  public void stop(final PluginCall call) {
505
714
  boolean force = Boolean.TRUE.equals(call.getBoolean("force", false));
@@ -532,6 +741,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
532
741
  }
533
742
  // Manual stops should not trigger automatic resume with stale config
534
743
  lastSessionConfig = null;
744
+ clearActiveBarcodeScanner();
535
745
  toBackVisualStateActive = false;
536
746
  restoreOriginalWindowBackground(getBridge().getActivity());
537
747
  restoreWebViewVisualState();
@@ -1340,6 +1550,9 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1340
1550
  config.setEnablePhysicalDeviceSelection(enablePhysicalDeviceSelection);
1341
1551
  config.setBarcodeScannerEnabled(barcodeScannerOptions != null);
1342
1552
  setPendingStartBarcodeScanner(barcodeScannerOptions);
1553
+ if (barcodeScannerOptions == null) {
1554
+ clearActiveBarcodeScanner();
1555
+ }
1343
1556
 
1344
1557
  bridge.saveCall(call);
1345
1558
  cameraStartCallbackId = call.getCallbackId();
@@ -1627,6 +1840,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1627
1840
  Log.d(TAG, "onCameraStopped: ignoring callback from stale instance");
1628
1841
  return;
1629
1842
  }
1843
+ cameraRestartAfterResumeInProgress = false;
1630
1844
  // Ensure reference is cleared once the originating CameraXView has fully stopped
1631
1845
  if (source != null && cameraXView == source) {
1632
1846
  cameraXView = null;
@@ -1925,6 +2139,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1925
2139
 
1926
2140
  @Override
1927
2141
  public void onCameraStarted(int width, int height, int x, int y) {
2142
+ cameraRestartAfterResumeInProgress = false;
1928
2143
  // Always transition window and WebView backgrounds to transparent when the camera starts,
1929
2144
  // regardless of whether there is a pending JS call. This is critical for the
1930
2145
  // background/foreground resume cycle: on resume, handleOnResume() sets backgrounds to
@@ -2085,6 +2300,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2085
2300
  new CameraXView.BarcodeScannerStartCallback() {
2086
2301
  @Override
2087
2302
  public void onStarted() {
2303
+ markActiveBarcodeScanner(formats, detectionInterval);
2088
2304
  resolveCameraStartCall(call, result);
2089
2305
  }
2090
2306
 
@@ -2099,6 +2315,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2099
2315
 
2100
2316
  resolveCameraStartCall(call, result);
2101
2317
  }
2318
+ restartBarcodeScannerAfterCameraResumeIfNeeded();
2102
2319
  }
2103
2320
 
2104
2321
  private void resolveCameraStartCall(PluginCall call, JSObject result) {
@@ -2161,6 +2378,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2161
2378
  Log.d(TAG, "onCameraStartError: ignoring callback from stale instance");
2162
2379
  return;
2163
2380
  }
2381
+ cameraRestartAfterResumeInProgress = false;
2164
2382
  toBackVisualStateActive = false;
2165
2383
  if (cameraXView == source) {
2166
2384
  try {
@@ -35,7 +35,7 @@ extension UIWindow {
35
35
  */
36
36
  @objc(CameraPreview)
37
37
  public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
38
- private let pluginVersion: String = "8.4.3"
38
+ private let pluginVersion: String = "8.4.5"
39
39
  public let identifier = "CameraPreviewPlugin"
40
40
  public let jsName = "CameraPreview"
41
41
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "8.4.3",
3
+ "version": "8.4.5",
4
4
  "description": "Camera preview",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {
@@ -55,7 +55,10 @@
55
55
  "maestro:smoke": "bunx maestro test .maestro/camera-preview-smoke.yaml",
56
56
  "watch": "tsc --watch",
57
57
  "prepublishOnly": "bun run build",
58
- "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
58
+ "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
59
+ "example:install": "cd example-app && bun install --frozen-lockfile",
60
+ "example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build",
61
+ "example:capgo:deploy": "bun run example:build && bun scripts/deploy-example-capgo.mjs"
59
62
  },
60
63
  "devDependencies": {
61
64
  "@capacitor/android": "^8.0.0",