some biggerish
This commit is contained in:
401
docs/TECHNICAL.md
Normal file
401
docs/TECHNICAL.md
Normal file
@@ -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<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
|
||||
Reference in New Issue
Block a user