Files
ISODroid/docs/TECHNICAL.md
2026-03-10 15:40:52 +05:00

402 lines
13 KiB
Markdown

# 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<String> {
val args = mutableListOf<String>()
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