Initial commit

This commit is contained in:
2026-05-08 11:45:15 +02:00
commit e5471b5fa0
120 changed files with 8938 additions and 0 deletions
+199
View File
@@ -0,0 +1,199 @@
# Buffer Sharing Design: Video → HAL
## Problem
We need to inject decoded video frames into the camera HAL's output buffer pipeline at the HAL process level (camera provider APEX), replacing real camera frames with virtual video frames.
## Architecture Context
```
[App Process] [Camera Provider APEX Process]
┌─────────────────┐ ┌──────────────────────────────────┐
│ ExoPlayer │ │ libgooglecamerahal.so │
│ ↓ decodes │ │ ↓ processCaptureRequest() │
│ MediaCodec │ │ ↓ fills output buffers │
│ ↓ YUV frames │ │ ↓ (AHardwareBuffer/gralloc) │
│ VirtualCamera │ │ cameraserver reads via binder │
│ Renderer │ └──────────────────────────────────┘
│ ↓ renders to │ ↑
│ Surface │ │ WE INJECT HERE
└─────────────────┘ ┌──────────────────────────────────┘
│ libcamera_hook.so (LD_PRELOAD) │
│ intercepts processCaptureRequest │
└──────────────────────────────────┘
```
## Evaluated Approaches
### 1. AHardwareBuffer Direct Write (CHOSEN)
**How it works**: HAL output buffers are `buffer_handle_t` backed by gralloc/AHardwareBuffer. Lock the buffer for CPU write, copy YUV frame data, unlock.
**Pros**:
- No extra memory allocation needed (reuse HAL's buffer)
- Direct write to the buffer cameraserver will read
- Works with YUV_420_888 (format 32) which is CPU-accessible
- Pixel 9a (API 36) fully supports AHardwareBuffer_lock()
**Cons**:
- CPU copy required (not zero-copy)
- IMPLEMENTATION_DEFINED (format 35) buffers may not be CPU-lockable
- Must handle buffer stride/alignment correctly
**Verdict**: **CHOSEN** for YUV_420_888 preview streams. This is the primary virtual camera use case.
### 2. Shared Memory (ashmem/memfd) Cross-Process
**How it works**: App process decodes video, writes YUV frames to shared memory. Provider process reads from shared memory and copies into HAL buffers.
**Pros**:
- Clean separation: decoder in app, injection in provider
- App has full MediaCodec/ExoPlayer access
- memfd_create() available on Android 11+ (API 30+)
**Cons**:
- Extra copy: decoder → shared memory → HAL buffer
- Cross-process synchronization complexity
- Need IPC mechanism (binder, socket, or signal)
**Verdict**: **CHOSEN** as the transport mechanism from app to provider process. Combined with Approach 1 for the final write.
### 3. ION/dmabuf
**How it works**: Allocate ION memory, pass dmabuf fd to both decoder and HAL.
**Pros**:
- Zero-copy potential
- GPU/ISP accessible
**Cons**:
- Vendor-specific ION heap configurations
- Requires /dev/ion access (SELinux restrictions in provider process)
- Complex buffer lifecycle management
- Overkill for our use case
**Verdict**: **REJECTED**. Too complex, vendor-dependent, and SELinux-hostile.
## Chosen Architecture
### Two-Stage Pipeline
```
Stage 1: App Process (Video Decoding)
┌─────────────────────────────────────┐
│ ExoPlayer → MediaCodec │
│ ↓ decoded YUV420 frames │
│ FrameRingBuffer (shared memory) │
│ ↓ memfd + mmap │
│ SharedMemoryWriter │
└─────────────────────────────────────┘
│ memfd (fd passed via binder/property)
Stage 2: Provider Process (HAL Injection)
┌─────────────────────────────────────┐
│ SharedMemoryReader │
│ ↓ reads latest YUV frame │
│ BufferConverter │
│ ↓ handles format/stride conversion │
│ AHardwareBufferWriter │
│ ↓ lock → memcpy → unlock │
│ HAL output buffer (to cameraserver) │
└─────────────────────────────────────┘
```
### Buffer Lifecycle
1. **Allocation**: HAL allocates output buffers during `configureStreams()`. We observe and record buffer dimensions, format, and stride.
2. **Frame Production**: App process decodes video via MediaCodec, writes YUV frames to a ring buffer in shared memory (memfd). Each frame has a sequence number and timestamp.
3. **Frame Consumption**: In `processCaptureRequest()`, our hook:
a. Reads the latest frame from shared memory
b. Locks the HAL output buffer via `AHardwareBuffer_lock()` (for YUV_420_888)
c. Copies frame data with stride conversion if needed
d. Unlocks the buffer
e. Returns to cameraserver (appears as real camera frame)
4. **Synchronization**:
- Shared memory ring buffer uses atomic sequence numbers
- Reader always grabs the latest complete frame (no blocking)
- If no frame available, forward to real HAL (passthrough)
### Format Handling
| Stream Format | Strategy |
|---|---|
| YUV_420_888 (32) | Direct AHardwareBuffer_lock + memcpy |
| IMPLEMENTATION_DEFINED (35) | Passthrough to real HAL (opaque GPU format) |
| JPEG/BLOB (37) | Passthrough to real HAL (encode handled by HAL) |
### Memory Layout
Shared memory ring buffer (memfd):
```
┌─────────────────────────────────────────────┐
│ Header (256 bytes) │
│ - magic: 0xCSWAPR00 │
│ - frame_count: uint32 │
│ - write_index: atomic<uint32> │
│ - read_index: atomic<uint32> │
│ - frame_width: uint32 │
│ - frame_height: uint32 │
│ - frame_size: uint32 │
├─────────────────────────────────────────────┤
│ Frame 0 (width × height × 3/2 bytes) │
│ - Y plane: width × height │
│ - U plane: width/2 × height/2 │
│ - V plane: width/2 × height/2 │
├─────────────────────────────────────────────┤
│ Frame 1 ... │
├─────────────────────────────────────────────┤
│ Frame N-1 ... │
└─────────────────────────────────────────────┘
```
Ring size: 4 frames (enough for 30fps video with slight timing jitter).
### AHardwareBuffer Write Procedure
```cpp
// In processCaptureRequest hook:
for (int i = 0; i < request->output_buffer_count; i++) {
auto& buf = request->output_buffers[i];
auto stream = find_stream(buf.stream_id);
if (stream->format == FORMAT_YUV_420_888 && g_hook_state.virtual_camera_enabled) {
// Lock buffer for CPU write
AHardwareBuffer* ahb = AHardwareBuffer_fromNativeHandle(buf.handle);
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(ahb, &desc);
void* cpu_addr = nullptr;
int result = AHardwareBuffer_lock(ahb,
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
-1, nullptr, &cpu_addr);
if (result == 0 && cpu_addr) {
// Copy YUV frame from shared memory
copy_yuv_frame_to_buffer(cpu_addr, desc.stride, desc.height,
g_frame_reader->latest_frame());
AHardwareBuffer_unlock(ahb, nullptr);
}
}
}
```
## Implementation Tasks
- **Task 7**: Video decoder (MediaCodec NDK) in app process
- **Task 9**: Buffer format conversion + shared memory ring buffer
- **Task 6**: processCaptureRequest integration (AHardwareBuffer write)
## Pixel 9a Specifics
- API level: 36 (Android 16)
- AHardwareBuffer: fully supported
- Gralloc: `/vendor/lib64/hw/gralloc.gs101.so` (Tensor G3)
- YUV_420_888: CPU-accessible, linear layout
- IMPLEMENTATION_DEFINED: opaque, GPU-only (passthrough)
+268
View File
@@ -0,0 +1,268 @@
# Pixel 9a Camera HAL Interface Map
> **Generated**: 2026-05-07
> **Device**: Pixel 9a (tegu), API 36 (Android 16)
> **Device ID**: 58241JEBF08428
---
## 1. Architecture Overview
### CRITICAL DISCOVERY: Camera HAL is a Separate Process
The Pixel 9a camera HAL is **NOT** a shared library loaded by `system_server`. It is a **separate process** running as the `system` user, communicating with `cameraserver` via **AIDL Binder IPC**.
```
┌─────────────────────────────────────────────────────────────┐
│ App Process │
│ (Google Camera, Fossify Camera, Instagram, etc.) │
│ Uses Camera2 API → Binder IPC → cameraserver │
└────────────────────────┬────────────────────────────────────┘
│ Camera2 API (Binder)
┌────────────────────────▼────────────────────────────────────┐
│ cameraserver (PID 1211) │
│ Process: /system/bin/cameraserver │
│ User: cameraserver │
│ Role: Manages camera devices, routes requests to HAL │
└────────────────────────┬────────────────────────────────────┘
│ AIDL Binder IPC (ICameraProvider)
┌────────────────────────▼────────────────────────────────────┐
│ Camera Provider HAL Process (PID 994) │
│ Binary: /apex/com.google.pixel.camera.hal/bin/hw/ │
│ android.hardware.camera.provider@2.7-service-google│
│ User: system │
│ APEX: com.google.pixel.camera.hal │
│ Interface: ICameraProvider/internal/0 (AIDL v3) │
│ Role: Implements camera HAL, talks to kernel drivers │
└────────────────────────┬────────────────────────────────────┘
│ Kernel drivers (/dev/lwis-*)
┌────────────────────────▼────────────────────────────────────┐
│ Camera Hardware │
│ Sensors, ISP, OIS, Actuator, EEPROM │
└─────────────────────────────────────────────────────────────┘
```
### Implications for HAL Hooking
The traditional "HAL wrapper library loaded by system_server" approach is **WRONG** for Pixel 9a. The correct interception strategies are:
1. **Hook the camera provider process** — Inject a library into the provider process via `wrap.` property + `LD_PRELOAD`
2. **Intercept Binder/AIDL IPC** — Hook the Binder communication between `cameraserver` and the provider
3. **Replace the APEX binary** — Swap the provider binary with a wrapper (requires APEX modification)
4. **Hook at cameraserver level** — Intercept in `cameraserver` before requests reach the provider
**Recommended approach**: Option 1 (hook the camera provider process via `wrap.` property). The provider process is the most direct interception point.
---
## 2. AIDL Interface Hierarchy
### Interface Chain
```
ICameraProvider (v3)
└── getCameraIdList() → ["device@1.1/internal/0", ...]
└── openCamera(deviceId, callback) → ICameraDevice
└── ICameraDevice (v4)
└── open(sessionCallback) → ICameraDeviceSession
└── ICameraDeviceSession (v4)
├── configureStreams(config) → StreamConfiguration
├── processCaptureRequest(request) → Status
├── flush() → Status
└── close()
```
### Key AIDL Interfaces
#### ICameraProvider (android.hardware.camera.provider-V4-ndk.so)
```aidl
interface ICameraProvider {
CameraStatus[] getCameraIdList(out String[] cameraIds);
CameraStatus isSetTorchModeSupported(String cameraId, out boolean support);
CameraStatus openCamera(String cameraId, ICameraDeviceCallback callback,
out ICameraDevice device);
CameraStatus setTorchMode(String cameraId, boolean enabled);
CameraStatus notifyDeviceStateChange(long physicalCameraId, long deviceState);
}
```
#### ICameraDevice (android.hardware.camera.device-V4-ndk.so)
```aidl
interface ICameraDevice {
CameraMetadata getCameraCharacteristics();
int getResourceCost();
CameraStatus open(ICameraDeviceCallback callback,
out ICameraDeviceSession session);
void close();
}
```
#### ICameraDeviceSession — PRIMARY INTERCEPTION TARGET
```aidl
interface ICameraDeviceSession {
// ★★★ KEY FUNCTION 1 ★★★
// Configure output/input streams for the camera
CameraStatus configureStreams(
in StreamConfiguration requestedConfiguration,
out HalStreamConfiguration halConfiguration);
// ★★★ KEY FUNCTION 2 ★★★
// Process a capture request — THIS IS WHERE WE REPLACE FRAMES
CameraStatus processCaptureRequest(
in CaptureRequest request,
out CaptureResultMetadata resultMetadata);
// ★★★ KEY FUNCTION 3 ★★★
CameraStatus flush();
void close();
CameraStatus getSignalStreamMap(out SignalStreamMap streamMap);
CameraStatus processPhysicalCaptureRequest(
in PhysicalCaptureRequestInfo physicalRequestInfo,
out CaptureResultMetadata resultMetadata);
CameraStatus setRepeatingRequests(in CaptureRequest[] requests, out int32_t sequenceId);
CameraStatus cancelRepeatingRequest(int32_t sequenceId);
}
```
---
## 3. Camera Device Configuration
### Device Mapping
| API Device | HAL ID | Facing | Description |
|---|---|---|---|
| Device 0 | HAL 2 (Rear) | Back | Main rear camera |
| Device 0 | HAL 3 (RearWide) | Back | Ultra-wide rear camera |
| Device 1 | HAL 1 (Front) | Front | Selfie camera |
### Stream Configuration Formats
From `android.scaler.availableStreamConfigurations`:
| Format Code | Format Name | Description | Max Resolution |
|---|---|---|---|
| 32 | `HAL_PIXEL_FORMAT_YCBCR_420_888` | Flexible YUV 4:2:0 | 4000×3000 |
| 35 | `HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED` | GPU/ISP opaque format | 4208×3120 |
| 36 | `HAL_PIXEL_FORMAT_RAW16` | Raw sensor data | Varies |
| 37 | `HAL_PIXEL_FORMAT_BLOB` (JPEG) | Compressed JPEG | 4000×3000 |
**Most important format for virtual camera**: Format 32 (YUV_420_888) — this is what preview streams use and what we need to feed video frames into.
### Buffer Flow
```
1. App calls CameraDevice.createCaptureSession() with Surface targets
2. cameraserver calls ICameraDeviceSession.configureStreams()
→ HAL allocates/configures output buffers
3. App calls CaptureRequest.Builder.addTarget(surface)
4. cameraserver calls ICameraDeviceSession.processCaptureRequest()
→ HAL fills buffers with camera sensor data
→ Buffers are returned to cameraserver → app surfaces
```
**Our interception point**: Step 4 — replace buffer contents with decoded video frames BEFORE returning to cameraserver.
---
## 4. Key Libraries in APEX
### Location: `/apex/com.google.pixel.camera.hal/lib64/`
| Library | Purpose | Relevance |
|---|---|---|
| `libgooglecamerahal.so` | Main Google Camera HAL implementation | **PRIMARY TARGET** |
| `libgooglecamerahalutils.so` | HAL utility functions | Supporting |
| `android.hardware.camera.provider-V4-ndk.so` | AIDL Provider interface stubs | Interface definition |
| `android.hardware.camera.device-V4-ndk.so` | AIDL Device interface stubs | Interface definition |
| `android.hardware.camera.common-V1-ndk.so` | Common camera types | Interface definition |
| `android.hardware.camera.metadata-V3-ndk.so` | Camera metadata handling | Needed for result metadata |
| `liblyric_hwl.so` | Google Lyric Hardware Layer | Google-specific ISP |
| `libg3a.so` | 3A algorithms (AE/AWB/AF) | Auto-exposure/whitebalance/focus |
| `libcamerasuezclient.so` | Suez framework client | Google analytics/telemetry |
| `libion.so` / `libion_google.so` | ION memory allocator | Buffer allocation |
| `libdmabufheap.so` | DMA-BUF heap allocator | Buffer sharing |
| `libyuv.so` | YUV format conversion | Format conversion |
---
## 5. Interception Strategy
### Approach: Hook the Camera Provider Process
Since the camera HAL runs as a separate process, we inject our hooking library into that process.
#### Option A: wrap. Property + LD_PRELOAD (Recommended)
```bash
# Set via Magisk service.sh at boot:
setprop wrap.android.hardware.camera.provider@2.7-service-google \
"LD_PRELOAD=/data/local/camera_magic/libcamera_hook.so"
```
The `wrap.` property tells Android's init to restart the process with LD_PRELOAD.
#### Option B: Replace APEX Binary
Replace the provider binary with a wrapper that loads the original + our hooks. More invasive but more reliable.
### Functions to Hook
| Function | Library | Purpose | Hook Strategy |
|---|---|---|---|
| `configureStreams` | `libgooglecamerahal.so` | Know stream sizes/formats | Observe and store config |
| `processCaptureRequest` | `libgooglecamerahal.so` | Replace frames | Fill buffers with video data |
| `flush` | `libgooglecamerahal.so` | Cleanup | Pass through + cleanup our buffers |
### Buffer Replacement Flow
```
1. configureStreams() called → store stream config (resolution, format, buffer count)
2. Allocate our own video frame buffers (matching HAL format)
3. processCaptureRequest() called:
a. Check if virtual camera is enabled (/data/local/camera_magic/config.txt)
b. If enabled: copy decoded video frame into request's output buffer
c. If disabled: pass through to original HAL
d. Return OK status
4. cameraserver receives buffer → sends to app → app shows virtual video
```
---
## 6. SELinux Considerations
The camera provider process runs as `system` user with its own SELinux context. Injecting libraries requires:
1. SELinux policy allowing `system` process to load libraries from `/data/local/`
2. Magisk `sepolicy.rule` to add:
```
allow hal_camera_server system_data_file:file { read open execute };
```
---
## 7. Verification Commands
```bash
# Verify camera provider process
adb shell ps -A | grep camera.provider
# Check AIDL service registration
adb shell service list | grep camera
# View camera service events
adb shell dumpsys media.camera | head -30
# Check stream configurations
adb shell dumpsys media.camera | grep availableStreamConfigurations
# Monitor camera usage
adb shell dumpsys media.camera | grep "CONNECT\|DISCONNECT"
# Check loaded libraries in provider process
adb shell su -c 'cat /proc/$(pidof android.hardware.camera.provider@2.7-service-google)/maps' | grep camera
```
+212
View File
@@ -0,0 +1,212 @@
# CamSwapper System-Wide HAL Hook Testing Tutorial
This guide walks you through testing the system-wide camera HAL hook feature on a rooted Pixel 9a. This mode injects virtual camera feeds into all camera apps simultaneously via LD_PRELOAD, with no per-app Xposed scoping required.
## Prerequisites
- Rooted Pixel 9a (Magisk or KernelSU installed)
- ADB (Android Debug Bridge) set up on your computer
- `camswapper-hal-hook-v1.zip` — the flashable module ZIP (pre-built, in the repo)
- A test video file (MP4, H.264/H.265/VP9) or RTSP stream URL
## Step 1: Prepare the Device
1. Connect your Pixel 9a via USB
2. Enable USB debugging in Developer Options
3. Authorize the ADB connection on your device
4. Verify ADB connection:
```bash
adb devices
```
You should see your device serial with "device" status.
5. Verify root access:
```bash
adb shell su -c id
```
Should return `uid=0(root) gid=0(root) groups=0(root)`.
## Step 2: Install the Module (ZIP Flash)
The module is packaged as a standard ZIP file that can be flashed directly in Magisk or KernelSU.
### Option A: Flash via Magisk App
1. Transfer `camswapper-hal-hook-v1.zip` to your device
2. Open Magisk app → Modules tab → "Install from storage"
3. Select `camswapper-hal-hook-v1.zip`
4. Wait for installation to complete
5. Tap "Reboot"
### Option B: Flash via KernelSU Manager
1. Transfer `camswapper-hal-hook-v1.zip` to your device
2. Open KernelSU app → Modules tab → "+" button
3. Select `camswapper-hal-hook-v1.zip`
4. Wait for installation to complete
5. Tap "Reboot"
### Option C: Flash via Custom Recovery (TWRP)
1. Push the ZIP to your device: `adb push camswapper-hal-hook-v1.zip /sdcard/`
2. Boot into recovery
3. Flash the ZIP
4. Reboot system
### Verify Installation
After reboot, check the module is recognized:
```bash
adb shell su -c "ls -la /data/adb/modules/camera-hook/"
```
You should see `module.prop`, `post-fs-data.sh`, `service.sh`, `libcamera_hook.so`, `system.prop`, `sepolicy.rule`, and `customize.sh`.
## Step 3: Verify Module Installation
After the device reboots, run the included integration test script or verify manually.
### Option A: Run Integration Test Script
```bash
./test_hal_wrapper.sh
```
This script checks prerequisites, module installation, wrap property, config file, and hook loading status.
### Option B: Manual Verification
1. Check the wrap property is set correctly:
```bash
adb shell getprop wrap.android.hardware.camera.provider@2.7-service-google
```
Expected output: `LD_PRELOAD=/data/adb/modules/camera-hook/libcamera_hook.so`
2. Check the camera provider process is running:
```bash
adb shell pidof android.hardware.camera.provider@2.7-service-google
```
Should return a PID number.
3. Verify `libcamera_hook.so` is loaded in the provider process:
```bash
adb shell su -c "cat /proc/\$(pidof android.hardware.camera.provider@2.7-service-google)/maps | grep libcamera_hook"
```
Should show the path to `libcamera_hook.so`.
4. Check hook initialization logs:
```bash
adb logcat -d -s CameraHook
```
Should show hook initialization messages.
5. Check for SELinux denials:
```bash
adb shell su -c "dmesg | grep \"avc: denied\" | grep camera"
```
Should return empty if no denials are present.
## Step 4: Configure Video Source
The HAL hook reads configuration from `/data/local/camera_magic/config.txt`. You can configure it via the CamSwapper app or manually.
### Option A: Use CamSwapper App
1. Install the CamSwapper app on your device
2. Open the app and navigate to HAL Mode settings
3. Toggle "Enable HAL Mode"
4. Select source mode: File or RTSP
5. For File mode: select your video file (place it in `/data/local/camera_magic/video.mp4` or update config manually)
6. For RTSP mode: enter your RTSP stream URL
### Option B: Manual Config File
Create or edit the config file directly via adb:
```bash
adb shell su -c "mkdir -p /data/local/camera_magic"
adb shell su -c "echo 'enabled=1' > /data/local/camera_magic/config.txt"
adb shell su -c "echo 'source_mode=file' >> /data/local/camera_magic/config.txt"
adb shell su -c "echo 'video_path=/data/local/camera_magic/test_video.mp4' >> /data/local/camera_magic/config.txt"
adb shell su -c "echo 'rtsp_url=' >> /data/local/camera_magic/config.txt"
adb shell su -c "chmod 644 /data/local/camera_magic/config.txt"
```
#### Config File Format
```
enabled=1 # 0=off, 1=on
source_mode=file # file or rtsp
video_path=/data/local/camera_magic/video.mp4
rtsp_url=rtsp://192.168.1.100:554/stream
```
## Step 5: Test the Virtual Camera
1. Push your test video file to the device:
```bash
adb push test_video.mp4 /data/local/camera_magic/video.mp4
adb shell su -c "chmod 644 /data/local/camera_magic/video.mp4"
```
2. Open any camera app (Google Camera, Instagram, Telegram, etc.)
3. The camera preview should display your virtual video instead of the real camera feed.
4. Check hook logs for frame injection:
```bash
adb logcat -s CameraHook
```
Should show FPS counts and frame injection messages.
## Step 6: Test RTSP Stream (Optional)
1. Update config to RTSP mode:
```bash
adb shell su -c "sed -i 's/source_mode=file/source_mode=rtsp/' /data/local/camera_magic/config.txt"
adb shell su -c "sed -i 's|video_path=.*|rtsp_url=rtsp://YOUR_RTSP_URL|' /data/local/camera_magic/config.txt"
```
2. Restart the camera provider process to reload config:
```bash
adb shell su -c "killall android.hardware.camera.provider@2.7-service-google"
```
The process will restart automatically and load the new config.
3. Open a camera app to view the RTSP stream.
## Troubleshooting
### Hook Not Loading
- Verify module is in `/data/adb/modules/camera-hook/`
- Check wrap property is set correctly
- Reboot the device
- Check `adb shell dmesg | grep CameraHook` for error messages
### No Virtual Feed Showing
- Verify `enabled=1` in config file
- Check video file path is correct and accessible
- Test RTSP URL with VLC first to ensure it's reachable
- View hook logs: `adb logcat -s CameraHook`
- Verify `libcamera_hook.so` is loaded in the provider process
### SELinux Denials
- Check `adb shell dmesg | grep "avc: denied"`
- Ensure `sepolicy.rule` is present in the module directory
- Temporary test: set SELinux to Permissive with `adb shell su -c setenforce 0`
### Camera App Crashes
- Check logcat for crashes: `adb logcat -d | grep -i crash`
- Verify video format is supported (H.264/H.265/VP9)
- Try a lower resolution/bitrate video file
## Uninstall/Disable HAL Hook
### Temporary Disable
Set `enabled=0` in config file:
```bash
adb shell su -c "sed -i 's/enabled=1/enabled=0/' /data/local/camera_magic/config.txt"
```
Restart camera provider: `adb shell su -c "killall android.hardware.camera.provider@2.7-service-google"`
### Permanent Uninstall
```bash
adb shell su -c "rm -rf /data/adb/modules/camera-hook"
adb shell su -c "setprop wrap.android.hardware.camera.provider@2.7-service-google ''"
adb shell su -c "rm -rf /data/local/camera_magic"
adb reboot
```
---