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

13 KiB

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():

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:

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 for root shell access:

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)
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

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

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:

# 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:

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:

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:
    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):

    find "$path" -maxdepth 1 -type f \( -iname '*.iso' -o -iname '*.img' \)
    
  2. ls command (fallback):

    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

private val KEY_ISO_DIRECTORY = stringPreferencesKey("iso_directory")

Stores:

  • Custom ISO directory path (default: /sdcard/isodrive/)

SharedPreferences

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:

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