This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is an Android multi-camera recording application that simultaneously captures video from up to 4 cameras. The app uses the Camera2 API and is designed for devices with multiple camera sensors (e.g., surveillance or multi-angle recording scenarios).
Package: com.test.cam
Min SDK: API 28 (Android 9.0)
Target SDK: API 36 (Android 14+)
Build System: Gradle with Kotlin DSL
IMPORTANT: The user will handle builds manually. Do NOT attempt to run build commands automatically.
The project requires JDK 17 or higher. A helper script is provided for building with JDK 25:
# Build using the provided batch file (sets JAVA_HOME to JDK 25)
build-with-jdk25.bat
# Or build manually after setting JAVA_HOME
set JAVA_HOME=C:\Program Files\Java\jdk-25.0.2
set PATH=%JAVA_HOME%\bin;%PATH%
gradlew.bat assembleDebug# Build debug APK (Windows: use gradlew.bat, Linux/Mac: use ./gradlew)
gradlew.bat assembleDebug
# Build release APK
gradlew.bat assembleRelease
# Install debug build to connected device
gradlew.bat installDebug
# Clean build
gradlew.bat clean
# Run unit tests
gradlew.bat test
# Run instrumented tests (requires connected device/emulator)
gradlew.bat connectedAndroidTestOutput Location: app\build\outputs\apk\debug\app-debug.apk
The project is configured with AOSP public test signing for release builds.
# Build signed release APK
gradlew.bat assembleRelease
# Output: app\build\outputs\apk\release\app-release.apkFor detailed release instructions, see RELEASE_GUIDE.md.
Quick release to GitHub:
release.bat v1.0.0# List connected devices
adb devices
# View real-time logs (filtered for camera operations)
adb logcat -v time -s CameraService:V Camera3-Device:V Camera3-Stream:V Camera3-Output:V camera3:V MainActivity:D MultiCameraManager:D SingleCamera:D VideoRecorder:D
# View app-specific logs only
adb logcat -v time | findstr "com.test.cam"
# Clear logcat buffer
adb logcat -c
# Uninstall app
adb uninstall com.test.cam
# Grant permissions manually (useful for testing)
adb shell pm grant com.test.cam android.permission.CAMERA
adb shell pm grant com.test.cam android.permission.RECORD_AUDIO
adb shell pm grant com.test.cam android.permission.WRITE_EXTERNAL_STORAGE
# Check recorded videos on device
adb shell ls -la /sdcard/DCIM/EVCam_Video/
# Pull recorded videos from device
adb pull /sdcard/DCIM/EVCam_Video/ ./recordings/
# Check photos on device
adb shell ls -la /sdcard/DCIM/EVCam_Photo/
# Pull photos from device
adb pull /sdcard/DCIM/EVCam_Photo/ ./photos/The application follows a layered architecture with clear separation between UI, camera management, and recording:
MainActivity (MainActivity.java)
- Entry point and UI controller
- Manages 4 TextureView instances for camera previews
- Handles runtime permissions (camera, audio, storage)
- Implements adaptive camera initialization (waits for all TextureViews to be ready)
- Includes integrated logging system with logcat reader
MultiCameraManager (MultiCameraManager.java)
- Orchestrates multiple SingleCamera instances
- Manages camera lifecycle across all active cameras
- Coordinates synchronized recording start/stop
- Implements fallback logic: 4→2→1 camera configurations
- Uses LinkedHashMap to maintain camera order (front, back, left, right)
SingleCamera (SingleCamera.java)
- Wraps Camera2 API for individual camera control
- Manages camera device lifecycle (open, configure, close)
- Handles preview sessions with TextureView
- Uses HandlerThread for background camera operations
- Target resolution: 1280x800 (referenced as "guardapp" standard)
VideoRecorder (VideoRecorder.java)
- Encapsulates MediaRecorder for video recording
- Configuration: MP4 format, H.264 encoding, 1Mbps bitrate, 30fps
- Saves to:
/DCIM/EVCam_Video/directory - Filename format:
yyyyMMdd_HHmmss_{position}.mp4(e.g.,20260123_151030_front.mp4)
Photo Capture
- Uses ImageReader for capturing still images
- Saves to:
/DCIM/EVCam_Photo/directory - Filename format:
yyyyMMdd_HHmmss_{position}.jpg(e.g.,20260123_151030_back.jpg) - Resolution: Same as preview (1280x800)
- Permission Check: MainActivity requests camera, audio, and storage permissions (version-aware)
- TextureView Readiness: Waits for all 4 TextureViews to report
onSurfaceTextureAvailable - Camera Detection: Queries CameraManager for available camera IDs
- Adaptive Configuration:
- 4+ cameras: Uses all 4 positions independently
- 2-3 cameras: Reuses cameras across TextureViews
- 1 camera: Shows same camera in all 4 positions
- Sequential Opening: Opens cameras respecting system limits (maxOpenCameras)
- Preview Start: Establishes capture sessions with preview surfaces
- User taps "Start Recording" button
- MultiCameraManager creates VideoRecorder for each active camera
- VideoRecorder.prepare() configures MediaRecorder and returns recording Surface
- SingleCamera adds recording Surface to capture session (alongside preview Surface)
- All recorders start simultaneously
- On stop: recorders stop, surfaces cleared, sessions recreated for preview-only
- Main Thread: UI updates, button handlers, TextureView callbacks
- Background HandlerThread: Camera operations (one per SingleCamera instance)
- Logcat Reader Thread: Separate thread for reading system camera logs
The app targets 1280x800 resolution for both preview and recording. The SingleCamera class implements a fallback strategy:
- Exact match: 1280x800
- Closest match: Finds nearest resolution by aspect ratio and size
- Falls back to largest available if no good match
- Android limits concurrent camera access (typically 2-3 cameras)
- The app respects
maxOpenCamerassetting (default: 4) - Cameras are opened sequentially with proper error handling
- Resources are released in onDestroy() lifecycle method
Version-aware permission requests:
- Android 13+: CAMERA, RECORD_AUDIO only
- Android 12 and below: Adds WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE
MainActivity includes a collapsible logging panel that:
- Displays timestamped application events
- Integrates logcat output filtered for camera-related tags
- Auto-scrolls to latest entries
- Limits buffer to 20,000 characters to prevent memory issues
When fewer than 4 cameras are available, the app reuses camera IDs across TextureViews. This is intentional for testing and fallback scenarios. Each SingleCamera instance is independent even if sharing the same cameraId.
- Preview Surface: Created from TextureView's SurfaceTexture
- Recording Surface: Obtained from MediaRecorder during recording
- Both surfaces are added to the same CameraCaptureSession during recording
- Recording surface is removed when recording stops
The app includes comprehensive error handling:
- Camera errors trigger callbacks with specific error codes
- Failed camera opens don't crash the app
- Recording failures are logged and reported to UI
- TextureView lifecycle is tracked to prevent premature camera access
- Main activity:
app/src/main/java/com/test/cam/MainActivity.java - Camera package:
app/src/main/java/com/test/cam/camera/
- Main layout:
app/src/main/res/layout/activity_main.xml - Manifest:
app/src/main/AndroidManifest.xml
- Project-level:
build.gradle.kts - App-level:
app/build.gradle.kts - Version catalog:
gradle/libs.versions.toml
- Check that TextureView is available before opening camera
- Verify permissions are granted (check logcat for "Missing permission")
- Ensure device has available cameras (check CameraManager.getCameraIdList())
- Respect maxOpenCameras limit (some devices can't open 4 simultaneously)
- Verify DCIM/EVCam_Video directory is writable
- Check that camera is opened and preview is running before starting recording
- Ensure MediaRecorder configuration matches camera capabilities
- Confirm audio permission is granted if recording with audio
- Verify DCIM/EVCam_Photo directory is writable
- Check that ImageReader is properly initialized in camera session
- Ensure camera is opened and preview is running before taking photos
- Verify TextureView dimensions are non-zero
- Check that SurfaceTexture is available
- Ensure camera preview size is supported by the device
- Look for Camera2 API errors in logcat (filtered by CameraService tags)