From a3ac0b4ef8be941928051d3a709a0b549fc5871e Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Tue, 10 Mar 2026 15:40:52 +0500 Subject: [PATCH] some biggerish --- docs/TECHNICAL.md | 401 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 docs/TECHNICAL.md diff --git a/docs/TECHNICAL.md b/docs/TECHNICAL.md new file mode 100644 index 0000000..08276bf --- /dev/null +++ b/docs/TECHNICAL.md @@ -0,0 +1,401 @@ +# Technical Documentation + +This document provides an in-depth technical overview of ISO Droid's architecture, components, and implementation details. + +## Architecture Overview + +ISO Droid follows the MVVM (Model-View-ViewModel) architecture pattern with Jetpack Compose for the UI layer. + +``` +┌─────────────────────────────────────────────────────────┐ +│ UI Layer │ +│ (Jetpack Compose Screens & Components) │ +└─────────────────────────┬───────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ ViewModel │ +│ (MainViewModel - State Management) │ +└─────────────────────────┬───────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ Domain Layer │ +│ ┌─────────────────┐ ┌──────────────────┐ │ +│ │ IsoDriveManager │ │ RootManager │ │ +│ │ (Mount/Unmount) │ │ (Shell Access) │ │ +│ └─────────────────┘ └──────────────────┘ │ +└─────────────────────────┬───────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ Native Layer │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ isodrive binary (per-architecture) │ │ +│ │ Interfaces with Linux USB gadget subsystem │ │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +## Package Structure + +``` +sh.sar.isodroid/ +├── MainActivity.kt # Entry point, Compose host +├── ISODroidApp.kt # Application class +├── data/ +│ ├── IsoFile.kt # ISO/IMG file model +│ ├── MountOptions.kt # Mount configuration +│ └── MountStatus.kt # Current mount state +├── isodrive/ +│ ├── IsoDriveManager.kt # Core mounting logic +│ ├── MountEventBus.kt # Mount state events +│ └── CreateImgEventBus.kt # IMG creation events +├── notification/ +│ ├── NotificationHelper.kt # Notification management +│ ├── UnmountReceiver.kt # Unmount broadcast receiver +│ └── CreateImgReceiver.kt # Cancel IMG creation receiver +├── root/ +│ └── RootManager.kt # Root shell abstraction +├── ui/ +│ ├── components/ # Reusable UI components +│ ├── screens/ # Full-screen composables +│ └── theme/ # Material 3 theming +└── viewmodel/ + └── MainViewModel.kt # UI state management +``` + +## Binary Architecture Selection + +The app bundles the `isodrive` binary for multiple CPU architectures and selects the appropriate one at runtime. + +### Bundled Architectures + +| Architecture | Directory | Target Devices | +|---------------|------------------|-----------------------------------| +| arm64-v8a | `bin/arm64-v8a/` | Modern 64-bit ARM (most phones) | +| armeabi-v7a | `bin/armeabi-v7a/` | 32-bit ARM (older phones) | +| x86_64 | `bin/x86_64/` | 64-bit Intel (emulators) | +| x86 | `bin/x86/` | 32-bit Intel (some tablets) | + +### Selection Process + +The architecture selection happens in `IsoDriveManager.extractBinary()`: + +```kotlin +val abi = android.os.Build.SUPPORTED_ABIS.firstOrNull() +val assetPath = when { + abi.contains("arm64") -> "bin/arm64-v8a/$binaryName" + abi.contains("armeabi") -> "bin/armeabi-v7a/$binaryName" + abi.contains("x86_64") -> "bin/x86_64/$binaryName" + abi.contains("x86") -> "bin/x86/$binaryName" + else -> return@withContext false +} +``` + +**Key points:** +1. `Build.SUPPORTED_ABIS` returns a priority-ordered list of supported ABIs +2. The first (primary) ABI is used for best performance +3. String matching order matters: `x86_64` is checked before `x86` to avoid false matches +4. Binary is extracted to `context.filesDir/bin/isodrive` and made executable + +### Fallback Mechanism + +If the bundled binary fails to extract, the app searches for a system-installed `isodrive`: + +```kotlin +val commonPaths = listOf( + "/data/adb/modules/isodrive-magisk/system/bin/isodrive", + "/data/adb/modules/isodrive/system/bin/isodrive", + "/system/bin/isodrive", + "/system/xbin/isodrive", + "/data/local/tmp/isodrive", + "/vendor/bin/isodrive" +) +``` + +## Root Access + +### libsu Integration + +The app uses [libsu](https://github.com/topjohnwu/libsu) for root shell access: + +```kotlin +Shell.setDefaultBuilder( + Shell.Builder.create() + .setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_REDIRECT_STDERR) + .setTimeout(10) +) +``` + +**Flags:** +- `FLAG_MOUNT_MASTER`: Ensures the shell runs in the global mount namespace +- `FLAG_REDIRECT_STDERR`: Captures error output for debugging + +### Root Status Detection + +The app handles three root states: + +| State | Description | +|-----------|------------------------------------------------| +| `true` | Root granted (cached or confirmed) | +| `false` | Root denied by user | +| `null` | Unknown (first launch, checking) | + +```kotlin +fun isRootGrantedCached(): Boolean? { + return Shell.isAppGrantedRoot() // Returns cached status, no popup +} + +suspend fun hasRoot(): Boolean { + return Shell.getShell().isRoot // May trigger Magisk prompt +} +``` + +## USB Gadget Interface + +### Supported Backends + +ISO Droid supports two Linux USB gadget interfaces: + +#### 1. ConfigFS (Modern - Android 8.0+) + +The preferred method using the kernel's configfs: + +``` +/sys/kernel/config/usb_gadget/*/functions/mass_storage.0/lun.0/ +├── file # Path to mounted ISO/IMG +├── cdrom # 1 = CD-ROM mode, 0 = mass storage +├── ro # 1 = read-only, 0 = read-write +└── removable # 1 = removable media +``` + +#### 2. Sysfs/UsbGadget (Legacy) + +Older Android USB gadget interface: + +``` +/sys/class/android_usb/android0/f_mass_storage/lun/ +├── file # Path to mounted ISO/IMG +└── ... +``` + +### Support Detection + +```kotlin +suspend fun isSupported(): SupportStatus { + // Try configfs first + val configfsCheck = RootManager.executeCommand( + "ls /sys/kernel/config/usb_gadget/ 2>/dev/null" + ) + if (configfsCheck.success && configfsCheck.output.isNotBlank()) { + return SupportStatus.CONFIGFS_SUPPORTED + } + + // Fall back to sysfs + val sysfsCheck = RootManager.executeCommand( + "test -d /sys/class/android_usb/android0 && echo supported" + ) + if (sysfsCheck.success) { + return SupportStatus.SYSFS_SUPPORTED + } + + return SupportStatus.NOT_SUPPORTED +} +``` + +## Mount Options + +### Available Options + +| Option | Flag | Description | +|-----------|-------------|---------------------------------------| +| Read-Only | (default) | File cannot be modified by host PC | +| Read-Write| `-rw` | File can be modified by host PC | +| CD-ROM | `-cdrom` | Emulates optical drive (always R/O) | +| ConfigFS | `-configfs` | Use configfs backend (default) | +| UsbGadget | `-usbgadget`| Use legacy sysfs backend | + +### Command Generation + +```kotlin +fun toCommandArgs(): List { + val args = mutableListOf() + if (!readOnly) args.add("-rw") + if (cdrom) args.add("-cdrom") + if (useConfigfs) args.add("-configfs") + else args.add("-usbgadget") + return args +} +``` + +**Example commands:** +```bash +# Mount as read-only mass storage +isodrive "/sdcard/isodrive/ubuntu.iso" -configfs + +# Mount as writable drive +isodrive "/sdcard/isodrive/drive.img" -rw -configfs + +# Mount as CD-ROM +isodrive "/sdcard/isodrive/windows.iso" -cdrom -configfs +``` + +## Event System + +The app uses Kotlin SharedFlow for decoupled event communication. + +### MountEventBus + +Notifies components of mount state changes: + +```kotlin +sealed class MountEvent { + data object Mounted : MountEvent() + data object Unmounted : MountEvent() +} +``` + +**Producers:** +- `UnmountReceiver` (notification action) +- `IsoDriveManager.mount()`/`unmount()` + +**Consumers:** +- `MainViewModel` (updates UI state) + +### CreateImgEventBus + +Tracks IMG file creation progress: + +```kotlin +sealed class CreateImgEvent { + data class Progress(val bytesWritten: Long, val totalBytes: Long) + data class Complete(val success: Boolean, val filePath: String?) + data object Cancelled +} +``` + +## IMG File Creation + +### Process + +1. User specifies filename and size +2. App creates sparse file using `dd`: + ```bash + dd if=/dev/zero of="$filePath" bs=1M count=0 seek=$totalBlocks + ``` +3. File is filled in 64MB chunks with progress reporting +4. Cancellation is supported via `CreateImgEventBus` + +### Progress Notification + +The notification shows real-time progress with a cancel button, implemented via `CreateImgReceiver` broadcast receiver. + +## File Operations + +### Listing Files + +The app uses multiple strategies to list ISO/IMG files: + +1. **find command** (most reliable): + ```bash + find "$path" -maxdepth 1 -type f \( -iname '*.iso' -o -iname '*.img' \) + ``` + +2. **ls command** (fallback): + ```bash + ls "$path" + ``` + +3. **Direct Java File API** (if storage permissions allow) + +### File Management + +- **Rename**: `mv "$oldPath" "$newPath"` +- **Delete**: `rm -f "$path"` +- **Size query**: `stat -c %s "$path"` + +## Notification System + +### Channels + +| Channel ID | Purpose | Importance | +|-------------------------|----------------------------|------------| +| `iso_drive_mount_status`| Persistent mount status | LOW | +| `iso_drive_progress` | IMG creation progress | LOW | + +### Mount Notification + +Ongoing notification with: +- File name +- Mount type (CD-ROM / Mass Storage) +- Access mode (Read-Only / Read-Write) +- Unmount action button + +## Data Persistence + +### DataStore Preferences + +```kotlin +private val KEY_ISO_DIRECTORY = stringPreferencesKey("iso_directory") +``` + +Stores: +- Custom ISO directory path (default: `/sdcard/isodrive/`) + +### SharedPreferences + +```kotlin +getSharedPreferences("iso_drive_prefs", Context.MODE_PRIVATE) +``` + +Stores: +- `root_granted`: Whether root was granted in setup wizard + +## Assets + +### Structure + +``` +assets/ +├── bin/ # isodrive binaries (per-arch) +│ ├── arm64-v8a/isodrive +│ ├── armeabi-v7a/isodrive +│ ├── x86/isodrive +│ └── x86_64/isodrive +├── osicons/ # SVG icons for OS distributions +│ ├── ubuntu.svg +│ ├── archlinux.svg +│ └── ... +└── os.json # Download links for OS ISOs +``` + +### OS Icon Matching + +Icons are matched by checking if the ISO filename contains the icon name (case-insensitive): +- `ubuntu-22.04.iso` matches `ubuntu.svg` +- `archlinux-2024.01.iso` matches `archlinux.svg` + +Symlinks handle variations (e.g., `mint.svg` -> `linuxmint.svg`). + +## Error Handling + +### Mount Failures + +Common failure scenarios: +- File not found +- USB gadget not supported +- Another file already mounted +- Permission denied + +All errors are captured and displayed via `MountResult`: + +```kotlin +data class MountResult( + val success: Boolean, + val message: String +) +``` + +### Root Failures + +- Cached status checked first (no popup) +- User can retry from settings +- Graceful degradation to "not supported" state