@capgo/camera-preview 7.4.0-beta.1 → 7.4.0-beta.2

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.
package/README.md CHANGED
@@ -233,7 +233,7 @@ Stops the camera preview.
233
233
  ### capture(...)
234
234
 
235
235
  ```typescript
236
- capture(options: CameraPreviewPictureOptions) => Promise<{ value: string; }>
236
+ capture(options: CameraPreviewPictureOptions) => Promise<{ value: string; exif: ExifData; }>
237
237
  ```
238
238
 
239
239
  Captures a picture from the camera.
@@ -242,7 +242,7 @@ Captures a picture from the camera.
242
242
  | ------------- | ----------------------------------------------------------------------------------- | ---------------------------------------- |
243
243
  | **`options`** | <code><a href="#camerapreviewpictureoptions">CameraPreviewPictureOptions</a></code> | - The options for capturing the picture. |
244
244
 
245
- **Returns:** <code>Promise&lt;{ value: string; }&gt;</code>
245
+ **Returns:** <code>Promise&lt;{ value: string; exif: <a href="#exifdata">ExifData</a>; }&gt;</code>
246
246
 
247
247
  **Since:** 0.0.1
248
248
 
@@ -546,16 +546,22 @@ Defines the configuration options for starting the camera preview.
546
546
  | **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | |
547
547
 
548
548
 
549
+ #### ExifData
550
+
551
+ Represents EXIF data extracted from an image.
552
+
553
+
549
554
  #### CameraPreviewPictureOptions
550
555
 
551
556
  Defines the options for capturing a picture.
552
557
 
553
- | Prop | Type | Description | Default |
554
- | ------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------- |
555
- | **`height`** | <code>number</code> | The desired height of the picture in pixels. If not provided, the device default is used. | |
556
- | **`width`** | <code>number</code> | The desired width of the picture in pixels. If not provided, the device default is used. | |
557
- | **`quality`** | <code>number</code> | The quality of the captured image, from 0 to 100. Does not apply to `png` format. | <code>85</code> |
558
- | **`format`** | <code><a href="#pictureformat">PictureFormat</a></code> | The format of the captured image. | <code>"jpeg"</code> |
558
+ | Prop | Type | Description | Default | Since |
559
+ | ------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------- | ----- |
560
+ | **`height`** | <code>number</code> | The desired height of the picture in pixels. If not provided, the device default is used. | | |
561
+ | **`width`** | <code>number</code> | The desired width of the picture in pixels. If not provided, the device default is used. | | |
562
+ | **`quality`** | <code>number</code> | The quality of the captured image, from 0 to 100. Does not apply to `png` format. | <code>85</code> | |
563
+ | **`format`** | <code><a href="#pictureformat">PictureFormat</a></code> | The format of the captured image. | <code>"jpeg"</code> | |
564
+ | **`saveToGallery`** | <code>boolean</code> | If true, the captured image will be saved to the user's gallery. | <code>false</code> | 7.5.0 |
559
565
 
560
566
 
561
567
  #### CameraSampleOptions
@@ -1,5 +1,10 @@
1
1
 
2
2
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
3
  >
4
+ <uses-permission android:name="android.permission.CAMERA" />
5
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
6
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
7
+ <uses-feature android:name="android.hardware.camera" />
8
+ <uses-feature android:name="android.hardware.camera.autofocus" />
4
9
  </manifest>
5
10
 
@@ -4,14 +4,10 @@ import static android.Manifest.permission.CAMERA;
4
4
  import static android.Manifest.permission.RECORD_AUDIO;
5
5
 
6
6
  import android.content.pm.ActivityInfo;
7
- import android.graphics.Point;
8
7
  import android.util.DisplayMetrics;
9
8
  import android.util.TypedValue;
10
- import android.view.Display;
11
- import androidx.annotation.NonNull;
12
9
  import com.getcapacitor.JSArray;
13
10
  import com.getcapacitor.JSObject;
14
- import com.getcapacitor.Logger;
15
11
  import com.getcapacitor.PermissionState;
16
12
  import com.getcapacitor.Plugin;
17
13
  import com.getcapacitor.PluginCall;
@@ -28,6 +24,8 @@ import android.util.Size;
28
24
  import android.util.Log;
29
25
  import com.ahm.capacitor.camera.preview.model.LensInfo;
30
26
 
27
+ import org.json.JSONObject;
28
+
31
29
  @CapacitorPlugin(
32
30
  name = "CameraPreview",
33
31
  permissions = {
@@ -83,15 +81,18 @@ public class CameraPreview
83
81
  }
84
82
 
85
83
  @PluginMethod
86
- public void capture(PluginCall call) {
84
+ public void capture(final PluginCall call) {
87
85
  if (cameraXView == null || !cameraXView.isRunning()) {
88
86
  call.reject("Camera is not running");
89
87
  return;
90
88
  }
89
+
91
90
  bridge.saveCall(call);
92
91
  captureCallbackId = call.getCallbackId();
92
+
93
93
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
94
- cameraXView.capturePhoto(quality);
94
+ final boolean saveToGallery = Boolean.TRUE.equals(call.getBoolean("saveToGallery", false));
95
+ cameraXView.capturePhoto(quality, saveToGallery);
95
96
  }
96
97
 
97
98
  @PluginMethod
@@ -326,13 +327,13 @@ public class CameraPreview
326
327
  final int width = call.getInt("width", 0);
327
328
  final int height = call.getInt("height", 0);
328
329
  final int paddingBottom = call.getInt("paddingBottom", 0);
329
- final boolean toBack = call.getBoolean("toBack", true);
330
- final boolean storeToFile = call.getBoolean("storeToFile", false);
331
- final boolean enableOpacity = call.getBoolean("enableOpacity", false);
332
- final boolean enableZoom = call.getBoolean("enableZoom", false);
333
- final boolean disableExifHeaderStripping = call.getBoolean("disableExifHeaderStripping", false);
334
- final boolean lockOrientation = call.getBoolean("lockAndroidOrientation", false);
335
- final boolean disableAudio = call.getBoolean("disableAudio", true);
330
+ final boolean toBack = Boolean.TRUE.equals(call.getBoolean("toBack", true));
331
+ final boolean storeToFile = Boolean.TRUE.equals(call.getBoolean("storeToFile", false));
332
+ final boolean enableOpacity = Boolean.TRUE.equals(call.getBoolean("enableOpacity", false));
333
+ final boolean enableZoom = Boolean.TRUE.equals(call.getBoolean("enableZoom", false));
334
+ final boolean disableExifHeaderStripping = Boolean.TRUE.equals(call.getBoolean("disableExifHeaderStripping", false));
335
+ final boolean lockOrientation = Boolean.TRUE.equals(call.getBoolean("lockAndroidOrientation", false));
336
+ final boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
336
337
 
337
338
  float targetZoom = 1.0f;
338
339
  // Check if the selected device is a physical ultra-wide
@@ -367,8 +368,8 @@ public class CameraPreview
367
368
  }
368
369
  int computedX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, x, metrics);
369
370
  int computedY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, y, metrics);
370
- int computedWidth = width != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics) : (int) getBridge().getWebView().getWidth();
371
- int computedHeight = height != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics) : (int) getBridge().getWebView().getHeight();
371
+ int computedWidth = width != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics) : getBridge().getWebView().getWidth();
372
+ int computedHeight = height != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics) : getBridge().getWebView().getHeight();
372
373
  computedHeight -= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, metrics);
373
374
 
374
375
  CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f);
@@ -382,15 +383,28 @@ public class CameraPreview
382
383
  }
383
384
 
384
385
  @Override
385
- public void onPictureTaken(String result) {
386
- JSObject jsObject = new JSObject();
387
- jsObject.put("value", result);
388
- bridge.getSavedCall(captureCallbackId).resolve(jsObject);
386
+ public void onPictureTaken(String base64, JSONObject exif) {
387
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
388
+ if (pluginCall == null) {
389
+ Log.e("CameraPreview", "onPictureTaken: captureCallbackId is null");
390
+ return;
391
+ }
392
+ JSObject result = new JSObject();
393
+ result.put("value", base64);
394
+ result.put("exif", exif);
395
+ pluginCall.resolve(result);
396
+ bridge.releaseCall(pluginCall);
389
397
  }
390
398
 
391
399
  @Override
392
400
  public void onPictureTakenError(String message) {
393
- bridge.getSavedCall(captureCallbackId).reject(message);
401
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
402
+ if (pluginCall == null) {
403
+ Log.e("CameraPreview", "onPictureTakenError: captureCallbackId is null");
404
+ return;
405
+ }
406
+ pluginCall.reject(message);
407
+ bridge.releaseCall(pluginCall);
394
408
  }
395
409
 
396
410
  @Override
@@ -422,4 +436,5 @@ public class CameraPreview
422
436
  bridge.releaseCall(pluginCall);
423
437
  }
424
438
  }
439
+
425
440
  }
@@ -4,7 +4,6 @@ import android.content.Context;
4
4
  import android.hardware.camera2.CameraAccessException;
5
5
  import android.hardware.camera2.CameraManager;
6
6
  import android.os.Build;
7
- import android.os.HandlerThread;
8
7
  import android.util.Base64;
9
8
  import android.util.Log;
10
9
  import android.util.Size;
@@ -32,29 +31,37 @@ import com.ahm.capacitor.camera.preview.model.LensInfo;
32
31
  import com.ahm.capacitor.camera.preview.model.ZoomFactors;
33
32
  import com.google.common.util.concurrent.ListenableFuture;
34
33
  import java.nio.ByteBuffer;
35
- import java.nio.file.Files;
36
34
  import java.util.Arrays;
37
35
  import java.util.Collections;
38
36
  import java.util.List;
39
37
  import java.util.ArrayList;
40
38
  import java.util.Objects;
41
- import java.util.concurrent.ExecutionException;
42
39
  import java.util.concurrent.Executor;
43
40
  import java.util.concurrent.ExecutorService;
44
41
  import java.util.concurrent.Executors;
45
42
  import androidx.camera.camera2.interop.Camera2CameraInfo;
46
43
  import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
47
44
  import android.hardware.camera2.CameraCharacteristics;
48
- import androidx.camera.extensions.ExtensionMode;
49
45
  import java.util.Set;
50
46
  import androidx.camera.core.ZoomState;
51
47
  import androidx.camera.core.ResolutionInfo;
48
+ import android.content.Intent;
49
+ import android.net.Uri;
50
+ import android.os.Environment;
51
+ import java.io.File;
52
+ import java.io.FileOutputStream;
53
+ import java.io.IOException;
54
+ import java.text.SimpleDateFormat;
55
+ import java.util.Locale;
56
+ import androidx.exifinterface.media.ExifInterface;
57
+ import org.json.JSONObject;
58
+ import java.nio.file.Files;
52
59
 
53
60
  public class CameraXView implements LifecycleOwner {
54
61
  private static final String TAG = "CameraPreview CameraXView";
55
62
 
56
63
  public interface CameraXViewListener {
57
- void onPictureTaken(String result);
64
+ void onPictureTaken(String base64, JSONObject exif);
58
65
  void onPictureTakenError(String message);
59
66
  void onSampleTaken(String result);
60
67
  void onSampleTakenError(String message);
@@ -102,6 +109,26 @@ public class CameraXView implements LifecycleOwner {
102
109
  return isRunning;
103
110
  }
104
111
 
112
+ private void saveImageToGallery(byte[] data) {
113
+ try {
114
+ File photo = new File(
115
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
116
+ "IMG_" + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new java.util.Date()) + ".jpg"
117
+ );
118
+ FileOutputStream fos = new FileOutputStream(photo);
119
+ fos.write(data);
120
+ fos.close();
121
+
122
+ // Notify the gallery of the new image
123
+ Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
124
+ Uri contentUri = Uri.fromFile(photo);
125
+ mediaScanIntent.setData(contentUri);
126
+ context.sendBroadcast(mediaScanIntent);
127
+ } catch (IOException e) {
128
+ Log.e(TAG, "Error saving image to gallery", e);
129
+ }
130
+ }
131
+
105
132
  public void startSession(CameraSessionConfiguration config) {
106
133
  this.sessionConfig = config;
107
134
  cameraExecutor = Executors.newSingleThreadExecutor();
@@ -167,6 +194,7 @@ public class CameraXView implements LifecycleOwner {
167
194
  webView.setBackgroundColor(android.graphics.Color.WHITE);
168
195
  }
169
196
 
197
+ @OptIn(markerClass = ExperimentalCamera2Interop.class)
170
198
  private void bindCameraUseCases() {
171
199
  if (cameraProvider == null) return;
172
200
  mainExecutor.execute(() -> {
@@ -188,7 +216,6 @@ public class CameraXView implements LifecycleOwner {
188
216
  Log.d(TAG, "Use cases bound. Inspecting active camera and use cases.");
189
217
  CameraInfo cameraInfo = camera.getCameraInfo();
190
218
  Log.d(TAG, "Bound Camera ID: " + Camera2CameraInfo.from(cameraInfo).getCameraId());
191
- Log.d(TAG, "Implementation Type: " + cameraInfo.getImplementationType());
192
219
 
193
220
  // Log zoom state
194
221
  ZoomState zoomState = cameraInfo.getZoomState().getValue();
@@ -256,22 +283,6 @@ public class CameraXView implements LifecycleOwner {
256
283
  return builder.build();
257
284
  }
258
285
 
259
- private static boolean isIsLogical(CameraManager cameraManager, String cameraId) throws CameraAccessException {
260
- CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
261
- int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
262
-
263
- boolean isLogical = false;
264
- if (capabilities != null) {
265
- for (int capability : capabilities) {
266
- if (capability == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) {
267
- isLogical = true;
268
- break;
269
- }
270
- }
271
- }
272
- return isLogical;
273
- }
274
-
275
286
  private static String getCameraId(androidx.camera.core.CameraInfo cameraInfo) {
276
287
  try {
277
288
  // Generate a stable ID based on camera characteristics
@@ -303,7 +314,7 @@ public class CameraXView implements LifecycleOwner {
303
314
  }
304
315
  }
305
316
 
306
- public void capturePhoto(int quality) {
317
+ public void capturePhoto(int quality, final boolean saveToGallery) {
307
318
  Log.d(TAG, "capturePhoto: Starting photo capture with quality: " + quality);
308
319
 
309
320
  if (imageCapture == null) {
@@ -313,9 +324,8 @@ public class CameraXView implements LifecycleOwner {
313
324
  return;
314
325
  }
315
326
 
316
- ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(
317
- new java.io.File(context.getCacheDir(), "temp_image.jpg")
318
- ).build();
327
+ File tempFile = new File(context.getCacheDir(), "temp_image.jpg");
328
+ ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(tempFile).build();
319
329
 
320
330
  imageCapture.takePicture(
321
331
  outputFileOptions,
@@ -331,31 +341,24 @@ public class CameraXView implements LifecycleOwner {
331
341
 
332
342
  @Override
333
343
  public void onImageSaved(@NonNull ImageCapture.OutputFileResults output) {
334
- // Convert to base64
335
344
  try {
336
- java.io.File tempFile = new java.io.File(context.getCacheDir(), "temp_image.jpg");
337
- byte[] bytes;
338
-
339
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
340
- bytes = Files.readAllBytes(tempFile.toPath());
341
- } else {
342
- // Fallback for older Android versions
343
- java.io.FileInputStream fis = new java.io.FileInputStream(tempFile);
344
- bytes = new byte[(int) tempFile.length()];
345
- fis.read(bytes);
346
- fis.close();
345
+ byte[] bytes = Files.readAllBytes(tempFile.toPath());
346
+ ExifInterface exifInterface = new ExifInterface(tempFile.getAbsolutePath());
347
+ JSONObject exifData = getExifData(exifInterface);
348
+
349
+ if (saveToGallery) {
350
+ saveImageToGallery(bytes);
347
351
  }
348
352
 
349
353
  String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
350
-
351
- // Clean up temp file
354
+
352
355
  tempFile.delete();
353
356
 
354
357
  if (listener != null) {
355
- listener.onPictureTaken(base64);
358
+ listener.onPictureTaken(base64, exifData);
356
359
  }
357
360
  } catch (Exception e) {
358
- Log.e(TAG, "capturePhoto: Error converting to base64", e);
361
+ Log.e(TAG, "capturePhoto: Error processing image", e);
359
362
  if (listener != null) {
360
363
  listener.onPictureTakenError("Error processing image: " + e.getMessage());
361
364
  }
@@ -365,6 +368,166 @@ public class CameraXView implements LifecycleOwner {
365
368
  );
366
369
  }
367
370
 
371
+ private JSONObject getExifData(ExifInterface exifInterface) {
372
+ JSONObject exifData = new JSONObject();
373
+ try {
374
+ // Add all available exif tags to a JSON object
375
+ for (String[] tag : EXIF_TAGS) {
376
+ String value = exifInterface.getAttribute(tag[0]);
377
+ if (value != null) {
378
+ exifData.put(tag[1], value);
379
+ }
380
+ }
381
+ } catch (Exception e) {
382
+ Log.e(TAG, "getExifData: Error reading exif data", e);
383
+ }
384
+ return exifData;
385
+ }
386
+
387
+ private static final String[][] EXIF_TAGS = new String[][]{
388
+ {ExifInterface.TAG_APERTURE_VALUE, "ApertureValue"},
389
+ {ExifInterface.TAG_ARTIST, "Artist"},
390
+ {ExifInterface.TAG_BITS_PER_SAMPLE, "BitsPerSample"},
391
+ {ExifInterface.TAG_BRIGHTNESS_VALUE, "BrightnessValue"},
392
+ {ExifInterface.TAG_CFA_PATTERN, "CFAPattern"},
393
+ {ExifInterface.TAG_COLOR_SPACE, "ColorSpace"},
394
+ {ExifInterface.TAG_COMPONENTS_CONFIGURATION, "ComponentsConfiguration"},
395
+ {ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, "CompressedBitsPerPixel"},
396
+ {ExifInterface.TAG_COMPRESSION, "Compression"},
397
+ {ExifInterface.TAG_CONTRAST, "Contrast"},
398
+ {ExifInterface.TAG_COPYRIGHT, "Copyright"},
399
+ {ExifInterface.TAG_CUSTOM_RENDERED, "CustomRendered"},
400
+ {ExifInterface.TAG_DATETIME, "DateTime"},
401
+ {ExifInterface.TAG_DATETIME_DIGITIZED, "DateTimeDigitized"},
402
+ {ExifInterface.TAG_DATETIME_ORIGINAL, "DateTimeOriginal"},
403
+ {ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, "DeviceSettingDescription"},
404
+ {ExifInterface.TAG_DIGITAL_ZOOM_RATIO, "DigitalZoomRatio"},
405
+ {ExifInterface.TAG_DNG_VERSION, "DNGVersion"},
406
+ {ExifInterface.TAG_EXIF_VERSION, "ExifVersion"},
407
+ {ExifInterface.TAG_EXPOSURE_BIAS_VALUE, "ExposureBiasValue"},
408
+ {ExifInterface.TAG_EXPOSURE_INDEX, "ExposureIndex"},
409
+ {ExifInterface.TAG_EXPOSURE_MODE, "ExposureMode"},
410
+ {ExifInterface.TAG_EXPOSURE_PROGRAM, "ExposureProgram"},
411
+ {ExifInterface.TAG_EXPOSURE_TIME, "ExposureTime"},
412
+ {ExifInterface.TAG_FILE_SOURCE, "FileSource"},
413
+ {ExifInterface.TAG_FLASH, "Flash"},
414
+ {ExifInterface.TAG_FLASHPIX_VERSION, "FlashpixVersion"},
415
+ {ExifInterface.TAG_FLASH_ENERGY, "FlashEnergy"},
416
+ {ExifInterface.TAG_FOCAL_LENGTH, "FocalLength"},
417
+ {ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, "FocalLengthIn35mmFilm"},
418
+ {ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, "FocalPlaneResolutionUnit"},
419
+ {ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, "FocalPlaneXResolution"},
420
+ {ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, "FocalPlaneYResolution"},
421
+ {ExifInterface.TAG_F_NUMBER, "FNumber"},
422
+ {ExifInterface.TAG_GAIN_CONTROL, "GainControl"},
423
+ {ExifInterface.TAG_GPS_ALTITUDE, "GPSAltitude"},
424
+ {ExifInterface.TAG_GPS_ALTITUDE_REF, "GPSAltitudeRef"},
425
+ {ExifInterface.TAG_GPS_AREA_INFORMATION, "GPSAreaInformation"},
426
+ {ExifInterface.TAG_GPS_DATESTAMP, "GPSDateStamp"},
427
+ {ExifInterface.TAG_GPS_DEST_BEARING, "GPSDestBearing"},
428
+ {ExifInterface.TAG_GPS_DEST_BEARING_REF, "GPSDestBearingRef"},
429
+ {ExifInterface.TAG_GPS_DEST_DISTANCE, "GPSDestDistance"},
430
+ {ExifInterface.TAG_GPS_DEST_DISTANCE_REF, "GPSDestDistanceRef"},
431
+ {ExifInterface.TAG_GPS_DEST_LATITUDE, "GPSDestLatitude"},
432
+ {ExifInterface.TAG_GPS_DEST_LATITUDE_REF, "GPSDestLatitudeRef"},
433
+ {ExifInterface.TAG_GPS_DEST_LONGITUDE, "GPSDestLongitude"},
434
+ {ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, "GPSDestLongitudeRef"},
435
+ {ExifInterface.TAG_GPS_DIFFERENTIAL, "GPSDifferential"},
436
+ {ExifInterface.TAG_GPS_DOP, "GPSDOP"},
437
+ {ExifInterface.TAG_GPS_IMG_DIRECTION, "GPSImgDirection"},
438
+ {ExifInterface.TAG_GPS_IMG_DIRECTION_REF, "GPSImgDirectionRef"},
439
+ {ExifInterface.TAG_GPS_LATITUDE, "GPSLatitude"},
440
+ {ExifInterface.TAG_GPS_LATITUDE_REF, "GPSLatitudeRef"},
441
+ {ExifInterface.TAG_GPS_LONGITUDE, "GPSLongitude"},
442
+ {ExifInterface.TAG_GPS_LONGITUDE_REF, "GPSLongitudeRef"},
443
+ {ExifInterface.TAG_GPS_MAP_DATUM, "GPSMapDatum"},
444
+ {ExifInterface.TAG_GPS_MEASURE_MODE, "GPSMeasureMode"},
445
+ {ExifInterface.TAG_GPS_PROCESSING_METHOD, "GPSProcessingMethod"},
446
+ {ExifInterface.TAG_GPS_SATELLITES, "GPSSatellites"},
447
+ {ExifInterface.TAG_GPS_SPEED, "GPSSpeed"},
448
+ {ExifInterface.TAG_GPS_SPEED_REF, "GPSSpeedRef"},
449
+ {ExifInterface.TAG_GPS_STATUS, "GPSStatus"},
450
+ {ExifInterface.TAG_GPS_TIMESTAMP, "GPSTimeStamp"},
451
+ {ExifInterface.TAG_GPS_TRACK, "GPSTrack"},
452
+ {ExifInterface.TAG_GPS_TRACK_REF, "GPSTrackRef"},
453
+ {ExifInterface.TAG_GPS_VERSION_ID, "GPSVersionID"},
454
+ {ExifInterface.TAG_IMAGE_DESCRIPTION, "ImageDescription"},
455
+ {ExifInterface.TAG_IMAGE_LENGTH, "ImageLength"},
456
+ {ExifInterface.TAG_IMAGE_UNIQUE_ID, "ImageUniqueID"},
457
+ {ExifInterface.TAG_IMAGE_WIDTH, "ImageWidth"},
458
+ {ExifInterface.TAG_INTEROPERABILITY_INDEX, "InteroperabilityIndex"},
459
+ {ExifInterface.TAG_ISO_SPEED, "ISOSpeed"},
460
+ {ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, "ISOSpeedLatitudeyyy"},
461
+ {ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, "ISOSpeedLatitudezzz"},
462
+ {ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, "JPEGInterchangeFormat"},
463
+ {ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, "JPEGInterchangeFormatLength"},
464
+ {ExifInterface.TAG_LIGHT_SOURCE, "LightSource"},
465
+ {ExifInterface.TAG_MAKE, "Make"},
466
+ {ExifInterface.TAG_MAKER_NOTE, "MakerNote"},
467
+ {ExifInterface.TAG_MAX_APERTURE_VALUE, "MaxApertureValue"},
468
+ {ExifInterface.TAG_METERING_MODE, "MeteringMode"},
469
+ {ExifInterface.TAG_MODEL, "Model"},
470
+ {ExifInterface.TAG_NEW_SUBFILE_TYPE, "NewSubfileType"},
471
+ {ExifInterface.TAG_OECF, "OECF"},
472
+ {ExifInterface.TAG_OFFSET_TIME, "OffsetTime"},
473
+ {ExifInterface.TAG_OFFSET_TIME_DIGITIZED, "OffsetTimeDigitized"},
474
+ {ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "OffsetTimeOriginal"},
475
+ {ExifInterface.TAG_ORF_ASPECT_FRAME, "ORFAspectFrame"},
476
+ {ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH, "ORFPreviewImageLength"},
477
+ {ExifInterface.TAG_ORF_PREVIEW_IMAGE_START, "ORFPreviewImageStart"},
478
+ {ExifInterface.TAG_ORF_THUMBNAIL_IMAGE, "ORFThumbnailImage"},
479
+ {ExifInterface.TAG_ORIENTATION, "Orientation"},
480
+ {ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation"},
481
+ {ExifInterface.TAG_PIXEL_X_DIMENSION, "PixelXDimension"},
482
+ {ExifInterface.TAG_PIXEL_Y_DIMENSION, "PixelYDimension"},
483
+ {ExifInterface.TAG_PLANAR_CONFIGURATION, "PlanarConfiguration"},
484
+ {ExifInterface.TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities"},
485
+ {ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, "RecommendedExposureIndex"},
486
+ {ExifInterface.TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite"},
487
+ {ExifInterface.TAG_RELATED_SOUND_FILE, "RelatedSoundFile"},
488
+ {ExifInterface.TAG_RESOLUTION_UNIT, "ResolutionUnit"},
489
+ {ExifInterface.TAG_ROWS_PER_STRIP, "RowsPerStrip"},
490
+ {ExifInterface.TAG_RW2_ISO, "RW2ISO"},
491
+ {ExifInterface.TAG_RW2_JPG_FROM_RAW, "RW2JpgFromRaw"},
492
+ {ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER, "RW2SensorBottomBorder"},
493
+ {ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER, "RW2SensorLeftBorder"},
494
+ {ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER, "RW2SensorRightBorder"},
495
+ {ExifInterface.TAG_RW2_SENSOR_TOP_BORDER, "RW2SensorTopBorder"},
496
+ {ExifInterface.TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel"},
497
+ {ExifInterface.TAG_SATURATION, "Saturation"},
498
+ {ExifInterface.TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType"},
499
+ {ExifInterface.TAG_SCENE_TYPE, "SceneType"},
500
+ {ExifInterface.TAG_SENSING_METHOD, "SensingMethod"},
501
+ {ExifInterface.TAG_SENSITIVITY_TYPE, "SensitivityType"},
502
+ {ExifInterface.TAG_SHARPNESS, "Sharpness"},
503
+ {ExifInterface.TAG_SHUTTER_SPEED_VALUE, "ShutterSpeedValue"},
504
+ {ExifInterface.TAG_SOFTWARE, "Software"},
505
+ {ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, "SpatialFrequencyResponse"},
506
+ {ExifInterface.TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity"},
507
+ {ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, "StandardOutputSensitivity"},
508
+ {ExifInterface.TAG_STRIP_BYTE_COUNTS, "StripByteCounts"},
509
+ {ExifInterface.TAG_STRIP_OFFSETS, "StripOffsets"},
510
+ {ExifInterface.TAG_SUBFILE_TYPE, "SubfileType"},
511
+ {ExifInterface.TAG_SUBJECT_AREA, "SubjectArea"},
512
+ {ExifInterface.TAG_SUBJECT_DISTANCE, "SubjectDistance"},
513
+ {ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, "SubjectDistanceRange"},
514
+ {ExifInterface.TAG_SUBJECT_LOCATION, "SubjectLocation"},
515
+ {ExifInterface.TAG_SUBSEC_TIME, "SubSecTime"},
516
+ {ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, "SubSecTimeDigitized"},
517
+ {ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, "SubSecTimeOriginal"},
518
+ {ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH, "ThumbnailImageLength"},
519
+ {ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH, "ThumbnailImageWidth"},
520
+ {ExifInterface.TAG_TRANSFER_FUNCTION, "TransferFunction"},
521
+ {ExifInterface.TAG_USER_COMMENT, "UserComment"},
522
+ {ExifInterface.TAG_WHITE_BALANCE, "WhiteBalance"},
523
+ {ExifInterface.TAG_WHITE_POINT, "WhitePoint"},
524
+ {ExifInterface.TAG_X_RESOLUTION, "XResolution"},
525
+ {ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients"},
526
+ {ExifInterface.TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning"},
527
+ {ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling"},
528
+ {ExifInterface.TAG_Y_RESOLUTION, "YResolution"}
529
+ };
530
+
368
531
  public void captureSample(int quality) {
369
532
  Log.d(TAG, "captureSample: Starting sample capture with quality: " + quality);
370
533
 
@@ -498,11 +661,10 @@ public class CameraXView implements LifecycleOwner {
498
661
  }
499
662
  }
500
663
 
501
- public static ZoomFactors getZoomFactorsStatic(Context context) {
664
+ public static ZoomFactors getZoomFactorsStatic() {
502
665
  try {
503
666
  // For static method, return default zoom factors
504
667
  // We can try to detect if ultra-wide is available by checking device list
505
- List<com.ahm.capacitor.camera.preview.model.CameraDevice> devices = getAvailableDevicesStatic(context);
506
668
 
507
669
  float minZoom = 1.0f;
508
670
  float maxZoom = 10.0f;
@@ -519,7 +681,7 @@ public class CameraXView implements LifecycleOwner {
519
681
 
520
682
  public ZoomFactors getZoomFactors() {
521
683
  if (camera == null) {
522
- return getZoomFactorsStatic(context);
684
+ return getZoomFactorsStatic();
523
685
  }
524
686
 
525
687
  try {
@@ -546,8 +708,6 @@ public class CameraXView implements LifecycleOwner {
546
708
 
547
709
  try {
548
710
  float currentZoom = Objects.requireNonNull(camera.getCameraInfo().getZoomState().getValue()).getZoomRatio();
549
- float minZoom = camera.getCameraInfo().getZoomState().getValue().getMinZoomRatio();
550
- float maxZoom = camera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
551
711
 
552
712
  // Determine device type based on zoom capabilities
553
713
  String deviceType = "wideAngle";
@@ -594,46 +754,6 @@ public class CameraXView implements LifecycleOwner {
594
754
  }
595
755
  }
596
756
 
597
- private List<androidx.camera.core.CameraInfo> getAvailableCamerasForCurrentPosition() {
598
- if (cameraProvider == null) {
599
- Log.w(TAG, "getAvailableCamerasForCurrentPosition: cameraProvider is null");
600
- return Collections.emptyList();
601
- }
602
-
603
- List<androidx.camera.core.CameraInfo> allCameras = cameraProvider.getAvailableCameraInfos();
604
- List<androidx.camera.core.CameraInfo> sameFacingCameras = new ArrayList<>();
605
-
606
- Log.d(TAG, "getAvailableCamerasForCurrentPosition: Total cameras available: " + allCameras.size());
607
-
608
- // Determine current facing direction from the session config to avoid restricted API call
609
- boolean isCurrentBack = "back".equals(sessionConfig.getPosition());
610
- Log.d(TAG, "getAvailableCamerasForCurrentPosition: Looking for " + (isCurrentBack ? "back" : "front") + " cameras");
611
-
612
- for (int i = 0; i < allCameras.size(); i++) {
613
- androidx.camera.core.CameraInfo cameraInfo = allCameras.get(i);
614
- boolean isCameraBack = isBackCamera(cameraInfo);
615
- String cameraId = getCameraId(cameraInfo);
616
-
617
- Log.d(TAG, "getAvailableCamerasForCurrentPosition: Camera " + i + " - ID: " + cameraId + ", isBack: " + isCameraBack);
618
-
619
- try {
620
- float minZoom = Objects.requireNonNull(cameraInfo.getZoomState().getValue()).getMinZoomRatio();
621
- float maxZoom = cameraInfo.getZoomState().getValue().getMaxZoomRatio();
622
- Log.d(TAG, "getAvailableCamerasForCurrentPosition: Camera " + i + " zoom range: " + minZoom + "-" + maxZoom);
623
- } catch (Exception e) {
624
- Log.w(TAG, "getAvailableCamerasForCurrentPosition: Cannot get zoom info for camera " + i + ": " + e.getMessage());
625
- }
626
-
627
- if (isCameraBack == isCurrentBack) {
628
- sameFacingCameras.add(cameraInfo);
629
- Log.d(TAG, "getAvailableCamerasForCurrentPosition: Added camera " + i + " (" + cameraId + ") to same-facing list");
630
- }
631
- }
632
-
633
- Log.d(TAG, "getAvailableCamerasForCurrentPosition: Found " + sameFacingCameras.size() + " cameras for " + (isCurrentBack ? "back" : "front"));
634
- return sameFacingCameras;
635
- }
636
-
637
757
  public static List<Size> getSupportedPictureSizes(String facing) {
638
758
  List<Size> sizes = new ArrayList<>();
639
759
  try {
@@ -786,13 +906,13 @@ public class CameraXView implements LifecycleOwner {
786
906
  Log.d(TAG, "switchToDevice: Found matching CameraInfo for deviceId: " + deviceId);
787
907
  final CameraInfo finalTarget = targetCameraInfo;
788
908
 
789
- CameraSelector newSelector = new CameraSelector.Builder()
909
+ // This filter will receive a list of all cameras and must return the one we want.
910
+
911
+ currentCameraSelector = new CameraSelector.Builder()
790
912
  .addCameraFilter(cameras -> {
791
913
  // This filter will receive a list of all cameras and must return the one we want.
792
914
  return Collections.singletonList(finalTarget);
793
915
  }).build();
794
-
795
- currentCameraSelector = newSelector;
796
916
  currentDeviceId = deviceId;
797
917
  bindCameraUseCases(); // Rebind with the new, highly specific selector
798
918
  } else {