# CamSwapper — Project Status ## Build Status - **Both flavors compile**: `modernDebug` and `legacyDebug` assemble successfully with 0 errors, 0 warnings (minor `extractNativeLibs` manifest warning from AGP). - **On-device test**: Installed on Pixel 9a (Android 16). FGS crash resolved with `specialUse` type. App now has Application class, INTERNET + POST_NOTIFICATIONS permissions, and custom notification icon. - **Min SDK**: 29 | **Target SDK**: 36 | **Compile SDK**: 36 - **Kotlin**: JVM 11, toolchain 17 - **Gradle**: 8.13 ## Recent Changes 1. **Removed Xposed framework dependency** — all Xposed APIs (IXposedHookLoadPackage, ReflectionTools, process-based hooks) deleted. 2. **Cleaned up Xposed remnants** — removed `src/modern/resources/META-INF/xposed/`, `src/legacy/AndroidManifest.xml` (xposed metadata), and `src/legacy/assets/` (xposed_init files). 3. **Moved `ConfigRepository.kt`** from `src/modern/java/` to `src/main/java/` so both product flavors can compile (MediaType/MediaSource enums needed by main source set files). 4. **Rewrote `VirtualCameraService.kt`** (485 corrupted lines → 327 clean lines): - Uses reflection for `@SystemApi` VDM types (`VirtualDeviceManager`, `VirtualDeviceParams`, `VirtualCameraConfig`, `VirtualCameraCallback`, `DeviceFilter.Builder`) - Uses direct API for public companion types (`CompanionDeviceManager`, `AssociationRequest`, `AssociationInfo`) - Single clean `VirtualCameraCallback` proxy wired to `VideoPusher` pipeline - Proper CDM association flow with fallback to dummy ID - Removed 4x duplicated callback blocks, broken braces, dangling code 5. **Fixed `foregroundServiceType` for SDK 36** — On-device test on Pixel 9a (Android 16) revealed `mediaProjection` FGS type requires `FOREGROUND_SERVICE_MEDIA_PROJECTION` + `CAPTURE_VIDEO_OUTPUT` permissions on target SDK 36. Changed to `specialUse` with `PROPERTY_SPECIAL_USE_FGS_SUBTYPE` declaration. APK now installs without crash on boot. 6. **Added `CamSwapperApp` Application class** — `GlobalState.appContext` was `lateinit` and never initialized, causing potential crash when `VideoPusher.start()` or `SourceManager` accessed it. Now properly initialized in `Application.onCreate()`. 7. **Added missing permissions** — `INTERNET` (needed by ExoPlayer/Media3), `POST_NOTIFICATIONS` (for Android 13+ notification permission compliance). 8. **Replaced notification icon** — `android.R.drawable.ic_menu_camera` (system resource, may be unavailable) replaced with custom `R.drawable.ic_notification`. ## Architecture Overview ``` app/src/main/java/com/nothing/camera2magic/ ├── MainActivity.kt # Entry point, permission flow, service bind ├── VirtualCameraService.kt # Foreground service → VirtualDeviceManager via reflection ├── GlobalState.kt # Global app context holder ├── viewmodel/ │ ├── ConfigRepository.kt # SharedPreferences-backed config (MediaType, MediaSource enums) │ ├── SpotlightViewModel.kt # Media selection & preview state (MVI + StateFlow) │ ├── SettingsViewModel.kt # App settings + HAL mode toggle │ ├── ViewModelFactory.kt # ViewModel provider factory │ └── CompositionLocals.kt # Compose DI locals ├── view/ │ ├── SpotlightView.kt # Media source selector, preview grid, module switch │ └── SettingsView.kt # Toggle settings + HAL mode config ├── hook/ │ ├── SourceManager.kt # Media content resolver & fingerprint checking (224 lines) │ ├── VideoPusher.kt # ExoPlayer → Surface → VirtualCameraRenderer pipeline │ ├── VirtualCameraRenderer.kt # OpenGL ES renderer, SurfaceTexture → EGL → ImageWriter (387 lines) │ ├── NativeBridge.kt # JNI bridge for native C++ camera HAL hooks │ ├── HalConfigManager.kt # Root-based HAL config writer (/data/local/camera_magic/) │ ├── BlackHoleMapper.kt # Empty/dummy camera HAL mapper │ └── CameraState.kt # Camera state tracking ├── ui/theme/ │ ├── Color.kt, Theme.kt, Type.kt # Material3 theme └── utils/ └── Dog.kt # Logging utility ``` ## Product Flavors | Flavor | Purpose | |---------|---------| | `legacy` | Devices without VirtualDeviceManager support (Android 10-13) — currently identical to modern | | `modern` | Devices with Android 14+ VirtualDeviceManager API — has additional hardware buffer path available | ## Pipeline: Media → Virtual Camera ``` Local Media (ContentResolver) or Network (RTSP) │ ▼ VideoPusher (ExoPlayer) / SourceManager │ ▼ VirtualCameraRenderer (OpenGL ES) │ SurfaceTexture → EGL → ImageWriter ▼ VirtualCameraService (reflection) │ VirtualDeviceManager → VirtualCamera ▼ Other apps see a virtual camera device ``` ## Component Details ### VirtualCameraService (reflection-based) - **Status**: Compiles ✅, refactored ✅ (327 lines, clean architecture) - **API level gate**: Build.VERSION_CODES.UPSIDE_DOWN_CAKE (34) - **Approach**: Reflection for `@SystemApi` types (`VirtualDeviceManager`, `VirtualDeviceParams`, `VirtualCameraConfig`, `VirtualCameraCallback`, `DeviceFilter.Builder`). Direct API for public companion types (`CompanionDeviceManager`, `AssociationRequest`, `AssociationInfo`, `DeviceFilter`) - **Callback**: `VirtualCameraCallback` proxy wired — `onStreamConfigured()` feeds the `Surface` to `VideoPusher.start()` - **CDM**: Full `CompanionDeviceManager.associate()` flow with `DeviceFilter.FEATURE_VIRTUAL_CAMERA` filter, plus fallback to dummy ID 1 when CDM unavailable or association fails - **VideoPusher integration**: Uses `VideoPusher` singleton object directly (`VideoPusher.start()`, `VideoPusher.stop()`) - **Previously**: 485 corrupted lines with 4x duplicated callback blocks, broken braces, dangling code, no callback wiring, `VideoPusher.getInstance()` dependency that didn't exist ### ConfigRepository - **Status**: Clean ✅ - Backs all settings via SharedPreferences - Enums: `MediaSource` (LOCAL/NETWORK), `MediaType` (VIDEO/IMAGE) - HAL mode settings also stored here ### SourceManager - **Status**: ✅ Compiles, legacy footprint from Xposed era - Global singleton, manages media source selection & fingerprinting - Directly accesses ContentResolver for local media - Still uses `moduleEnabled` concept from Xposed module days ### VideoPusher + VirtualCameraRenderer - **Status**: ✅ Compiles (OpenGL ES + ExoPlayer pipeline) - VideoPusher: Singleton object, takes a video ID via `start(surfaces, width, height, videoId)`, creates ExoPlayer, feeds to renderer surfaces - VirtualCameraRenderer: 387-line OpenGL ES 2.0 renderer, handles EGL setup, SurfaceTexture → ImageWriter pipeline - VideoPusher is wired via `VirtualCameraCallback.onStreamConfigured()` in VirtualCameraService ### HalConfigManager - **Status**: ✅ Compiles, requires root - Writes config to `/data/local/camera_magic/config.txt` via `su -c` - Called from SettingsViewModel HAL mode toggle ## Remaining Work / Known Issues ### Critical - (none — all previously identified critical issues resolved) ### Medium - [ ] **SourceManager duplicates ConfigRepository** — SourceManager has its own SharedPreferences keys and module state, duplicating ConfigRepository. Should be consolidated. - [ ] **Native bridge stub** — `NativeBridge.kt` exists but no native `.so` is loaded (CMake/NDK build disabled). The project uses prebuilt `libcamera3.so` from jniLibs. - [ ] **legacy flavor** — Both flavors currently have identical source sets. The `legacy` flavor should ideally strip out VirtualDeviceManager-dependent features. ### Low / Cleanup - [ ] **Test files** — `ExampleInstrumentedTest.kt` and `ExampleUnitTest.kt` are the default Android Studio stubs. - [ ] **GlobalState.kt** — Legacy singleton used by Xposed hooks; may be removable now. - [ ] **`Dog` utility** — Thin wrapper over `android.util.Log`. ## Module Projects (outside `app/`) | Directory | Purpose | |-----------|---------| | `root-module/` | KernelSU module packaging (HAL-level camera hook) | | `zygisk-module/` | Zygisk module packaging (injection-based) | | `native/` | C++ native code for camera HAL hooks | | `docs/` | Documentation assets | | `camswapper-kernelsu-v2.zip` | Prebuilt KernelSU module | | `camswapper-zygisk-v2.zip` | Prebuilt Zygisk module | ## Build Commands ```bash # Build modern flavor (VirtualDeviceManager + system APIs) ./gradlew :app:assembleModernDebug # Build legacy flavor (no system APIs) ./gradlew :app:assembleLegacyDebug # Build with native C++ compilation ./gradlew :app:buildNative # Build release ./gradlew :app:assembleModernRelease ```