-
-
Notifications
You must be signed in to change notification settings - Fork 22
Fix: Improve camera device enumeration on android with type detection and fixes device switching for android #283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughRecords bound camera device IDs; replaces per-camera enumeration with Camera2 ID resolution and lens-type/FOV inference (logical + physical entries); switching updates SessionConfig and rebinds; flipping clears device ID and rebinds; added logging and layout/rebind adjustments. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CameraXView
participant CameraProvider
participant Camera2Info as Camera2CameraInfo
participant Characteristics as CameraCharacteristics
User->>CameraXView: request device list
activate CameraXView
CameraXView->>CameraProvider: getAvailableCameraInfos()
CameraProvider-->>CameraXView: [CameraInfo...]
loop each CameraInfo
CameraXView->>Camera2Info: Camera2CameraInfo.from(cameraInfo)
Camera2Info-->>CameraXView: cameraId (logical)
CameraXView->>Characteristics: getCameraCharacteristics(cameraId)
Characteristics-->>CameraXView: focalLengths, sensor info
CameraXView->>CameraXView: infer lens type & compute FOV
CameraXView-->>CameraXView: add logical device entry
alt physical cameras present
CameraXView->>Characteristics: get physical camera characteristics
Characteristics-->>CameraXView: physical properties
CameraXView-->>CameraXView: add physical device entry
end
end
CameraXView-->>User: return device list
deactivate CameraXView
sequenceDiagram
participant User
participant CameraXView
participant SessionConfig
participant BindProcess as bindCameraUseCases
participant Camera2Info as Camera2CameraInfo
User->>CameraXView: switchToDevice(deviceId)
activate CameraXView
CameraXView->>SessionConfig: update deviceId & position
CameraXView->>BindProcess: call bindCameraUseCases()
activate BindProcess
BindProcess->>Camera2CameraInfo: resolve bound camera ID from cameraInfo
Camera2CameraInfo-->>BindProcess: cameraId
BindProcess-->>CameraXView: store currentDeviceId
BindProcess-->>CameraXView: complete binding
deactivate BindProcess
CameraXView-->>User: device switched
deactivate CameraXView
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java`:
- Around line 1794-1795: The code calls cameraInfo.getZoomState().getValue()
directly (used to set minZoom and maxZoom) which can be null; replace with a
null-safe check: retrieve ZoomState zoomState =
cameraInfo.getZoomState().getValue(); if zoomState is null, handle gracefully
(e.g., return/skip zoom setup or schedule an observer on
cameraInfo.getZoomState() to wait for a non-null value) and only then call
zoomState.getMinZoomRatio()/getMaxZoomRatio(); update references in CameraXView
where minZoom/maxZoom are set to avoid Objects.requireNonNull and prevent an
NPE.
🧹 Nitpick comments (1)
android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java (1)
1819-1824: Inconsistent focal length thresholds for fallback classification.The fallback thresholds differ between logical and physical camera classification:
- Logical (lines 1819-1823):
< 2.5f→ ultraWide,> 5.5f→ telephoto- Physical (lines 1894-1895):
< 3.0f→ ultraWide,> 5.0f→ telephotoThis inconsistency could classify the same camera differently depending on whether it's enumerated as logical or physical. Consider unifying the thresholds.
Also applies to: 1894-1896
android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java
Outdated
Show resolved
Hide resolved
…aXView.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java`:
- Around line 1910-1913: The code may dereference focalLengths without a null
check and uses physicalDeviceType even when focalLengths is missing; update all
accesses to focalLengths (including earlier branches that read focalLengths[0])
to first check for null and fall back to a safe default (e.g., 4.25f) before
indexing, and when creating the LensInfo instance in the physicalLenses list use
a guarded physicalDeviceType value (or a safe fallback like "unknown" or
"wideAngleFallback") when focalLengths is absent so LensInfo(...) is never
constructed with a null dereference or inconsistent device type; ensure the
checks are applied to any code paths that read focalLengths and to the
constructor call that builds new LensInfo(...).
🧹 Nitpick comments (3)
android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java (3)
786-795: LGTM! Device ID capture after binding is correctly implemented.This properly tracks the bound camera's device ID, which is essential for device switching functionality. The duplicated code between the video-enabled and non-video branches could be extracted to reduce repetition.
♻️ Optional: Extract common device ID capture logic
if (sessionConfig.isVideoModeEnabled() && videoCapture != null) { camera = cameraProvider.bindToLifecycle(this, currentCameraSelector, preview, imageCapture, videoCapture); - CameraInfo cameraInfo = camera.getCameraInfo(); - currentDeviceId = Camera2CameraInfo.from(cameraInfo).getCameraId(); - Log.d(TAG, "bindCameraUseCases: Camera successfully bound to device ID: " + currentDeviceId); } else { camera = cameraProvider.bindToLifecycle(this, currentCameraSelector, preview, imageCapture); - CameraInfo cameraInfo = camera.getCameraInfo(); - currentDeviceId = Camera2CameraInfo.from(cameraInfo).getCameraId(); - Log.d(TAG, "bindCameraUseCases: Camera successfully bound to device ID: " + currentDeviceId); } + + // Capture bound device ID + CameraInfo boundCameraInfo = camera.getCameraInfo(); + currentDeviceId = Camera2CameraInfo.from(boundCameraInfo).getCameraId(); + Log.d(TAG, "bindCameraUseCases: Camera successfully bound to device ID: " + currentDeviceId);
1818-1825: Inconsistent focal length fallback thresholds between logical and physical cameras.Logical cameras use
<2.5ffor ultraWide and>5.5ffor telephoto (lines 1820-1823), while physical cameras use<3.0fand>5.0f(lines 1895-1896). This inconsistency could cause the same physical lens to be classified differently depending on whether it's accessed as a logical or physical camera.♻️ Suggested fix: Unify thresholds
} else { // Fallback: classify by focal length alone - if (focalLength < 2.5f) { + if (focalLength < 3.0f) { deviceType = "ultraWide"; - } else if (focalLength > 5.5f) { + } else if (focalLength > 5.0f) { deviceType = "telephoto"; } }Also applies to: 1895-1897
2619-2627: Inconsistent executor usage betweenflipCameraandswitchToDevice.
flipCamerausescameraExecutor(lines 2623-2626) whileswitchToDeviceusesmainExecutor(line 2536). The comment on line 2622 states "Camera operations must run on main thread" but then dispatches tocameraExecutor. SincebindCameraUseCases()internally posts tomainExecutor, this works but creates an unnecessary thread hop and inconsistency.♻️ Suggested fix: Use mainExecutor consistently
// Clear current device ID to force position-based selection currentDeviceId = null; - // Camera operations must run on main thread - cameraExecutor.execute(() -> { + mainExecutor.execute(() -> { currentCameraSelector = buildCameraSelector(); bindCameraUseCases(); });
android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java
Outdated
Show resolved
Hide resolved
match fallback lens classification to match for virtual and physical
Fix potential null check on focalLengths null or focalLengths empty array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java`:
- Around line 2555-2577: When reconstructing the CameraSessionConfiguration
after changing deviceId in CameraXView, the mutable targetZoom value is lost;
before creating the new CameraSessionConfiguration capture the existing
targetZoom from sessionConfig (e.g., float oldTargetZoom =
sessionConfig.getTargetZoom()) and after you reconstruct and assign
sessionConfig restore it with sessionConfig.setTargetZoom(oldTargetZoom); keep
the existing sessionConfig.setCentered(wasCentered) call and ensure you
reference CameraSessionConfiguration, sessionConfig, getTargetZoom, and
setTargetZoom in the fix.
| // Update sessionConfig with the new device ID | ||
| sessionConfig = new CameraSessionConfiguration( | ||
| deviceId, | ||
| position, | ||
| sessionConfig.getX(), | ||
| sessionConfig.getY(), | ||
| sessionConfig.getWidth(), | ||
| sessionConfig.getHeight(), | ||
| sessionConfig.getPaddingBottom(), | ||
| sessionConfig.getToBack(), | ||
| sessionConfig.getStoreToFile(), | ||
| sessionConfig.getEnableOpacity(), | ||
| sessionConfig.getDisableExifHeaderStripping(), | ||
| sessionConfig.getDisableAudio(), | ||
| sessionConfig.getZoomFactor(), | ||
| sessionConfig.getAspectRatio(), | ||
| sessionConfig.getGridMode(), | ||
| sessionConfig.getDisableFocusIndicator(), | ||
| sessionConfig.isVideoModeEnabled() | ||
| ); | ||
|
|
||
| sessionConfig.setCentered(wasCentered); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Locate and inspect CameraSessionConfiguration class
set -euo pipefail
# Find the file
echo "=== Locating CameraSessionConfiguration.java ==="
fd -a "CameraSessionConfiguration.java" || true
echo ""
echo "=== Searching for CameraSessionConfiguration definition ==="
# Search for the class definition
rg -l "class CameraSessionConfiguration" || trueRepository: Cap-go/capacitor-camera-preview
Length of output: 393
🏁 Script executed:
#!/bin/bash
# Read CameraSessionConfiguration.java to see all fields and constructor
set -euo pipefail
cat -n /home/jailuser/git/android/src/main/java/app/capgo/capacitor/camera/preview/model/CameraSessionConfiguration.javaRepository: Cap-go/capacitor-camera-preview
Length of output: 5360
🏁 Script executed:
#!/bin/bash
# Check the context around line 2555 in CameraXView.java to see how wasCentered is obtained
# and verify the full switchToDevice method
set -euo pipefail
cat -n android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java | sed -n '2530,2580p'Repository: Cap-go/capacitor-camera-preview
Length of output: 2940
Restore targetZoom when switching devices.
The reconstruction at lines 2556–2574 creates a new CameraSessionConfiguration with all constructor parameters, but does not preserve the mutable targetZoom field (which defaults to 1.0f). While isCentered is correctly restored on line 2576, targetZoom will silently reset on every device switch. Add sessionConfig.setTargetZoom(oldTargetZoom) after line 2576, or retrieve the value before reconstruction.
🤖 Prompt for AI Agents
In `@android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java`
around lines 2555 - 2577, When reconstructing the CameraSessionConfiguration
after changing deviceId in CameraXView, the mutable targetZoom value is lost;
before creating the new CameraSessionConfiguration capture the existing
targetZoom from sessionConfig (e.g., float oldTargetZoom =
sessionConfig.getTargetZoom()) and after you reconstruct and assign
sessionConfig restore it with sessionConfig.setTargetZoom(oldTargetZoom); keep
the existing sessionConfig.setCentered(wasCentered) call and ensure you
reference CameraSessionConfiguration, sessionConfig, getTargetZoom, and
setTargetZoom in the fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@YescoSoftware, I'll plan the code changes to preserve the targetZoom value when switching devices.
Task: Preserve targetZoom value when switching devices
The switchToDevice method needs to capture and restore the targetZoom value (similar to isCentered) when reconstructing the CameraSessionConfiguration.
🧠 Learnings used
Learnt from: Dalanir
Repo: Cap-go/capacitor-camera-preview PR: 217
File: ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift:1242-1246
Timestamp: 2025-08-29T15:37:50.891Z
Learning: In the iOS CameraController.swift handleTap method, the team wants to maintain the current behavior that forces exposureMode to .autoExpose and resets exposureTargetBias to 0.0 when tapping to focus, even though it overrides the user's current exposure mode setting.
💡 For code changes across multiple files, use @coderabbitai plan in an issue comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai ignore
this is intentional to reset back to initialZoom on device change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@YescoSoftware, thank you for the clarification! I've noted that resetting the zoom to initialZoom on device switch is the intended behavior.
🐰
✏️ Learnings added
Learnt from: YescoSoftware
Repo: Cap-go/capacitor-camera-preview PR: 283
File: android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java:2555-2577
Timestamp: 2026-01-27T22:52:14.003Z
Learning: In the Android CameraXView.java switchToDevice method, the targetZoom field is intentionally not preserved when reconstructing CameraSessionConfiguration. The team wants zoom to reset to initialZoom when switching between camera devices, rather than maintaining the previous zoom level.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: Dalanir
Repo: Cap-go/capacitor-camera-preview PR: 217
File: ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift:1242-1246
Timestamp: 2025-08-29T15:37:50.891Z
Learning: In the iOS CameraController.swift handleTap method, the team wants to maintain the current behavior that forces exposureMode to .autoExpose and resets exposureTargetBias to 0.0 when tapping to focus, even though it overrides the user's current exposure mode setting.
Summary
Improves camera device enumeration on Android by properly detecting camera types (ultraWide, wideAngle, telephoto) and fixes issues with device switching and lifecycle state preservation.
Changes
Device Type Detection
Device Switching Fixes
currentDeviceIdtracking by capturing device ID after camera bindingswitchToDevice()to properly set deviceId in sessionConfigisCenteredflag when creating new sessionConfig inswitchToDevice()Technical Details
CameraManager,CameraCharacteristics) for accurate device informationfov = 2 * atan(sensorWidth / (2 * focalLength))Testing
Tested on Samsung Galaxy device with 4 cameras:
Device switching and lifecycle preservation working correctly.
Breaking Changes
None - this is backward compatible and only improves existing functionality.
Related Issues
Fixes issues with camera device detection and switching on Android devices with multiple cameras.
Summary by CodeRabbit
Bug Fixes
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.