# 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