Files
ISODroid/docs/BUILDING.md
2026-03-10 14:43:33 +05:00

8.5 KiB

Building ISO Droid

This document contains technical information for building and understanding ISO Droid.

Building from Source

Prerequisites

Before building the app, you need to obtain the isodrive binary:

  1. The isodrive binary is compiled from nitanmarcel/isodrive
  2. Place the compiled binary at app/src/main/assets/bin/isodrive before building
  3. The binary must be executable and compiled for the appropriate Android architecture (ARM64 recommended for modern devices)

Build Steps

# Clone the repository
git clone https://github.com/sargit/ISODroid.git
cd ISODroid

# Ensure isodrive binary is present
ls -la app/src/main/assets/bin/isodrive

# Build the debug APK
./gradlew assembleDebug

# Or build the release APK (requires signing configuration)
./gradlew assembleRelease

The compiled APK will be located at:

  • Debug: app/build/outputs/apk/debug/app-debug.apk
  • Release: app/build/outputs/apk/release/app-release.apk

Technical Architecture

Overview

ISO Droid is built using modern Android development practices:

  • Language: Kotlin
  • UI Framework: Jetpack Compose (Material 3)
  • Architecture: MVVM (Model-View-ViewModel)
  • Minimum SDK: 26 (Android 8.0)
  • Target SDK: 34 (Android 14)

Core Components

1. IsoDriveManager (isodrive/IsoDriveManager.kt)

The central component that interfaces with the isodrive binary:

  • Binary Extraction: Copies the bundled isodrive binary from assets to app-specific storage
  • Permission Management: Ensures the binary has executable permissions (chmod 755)
  • Command Execution: Wraps all isodrive commands (mount, unmount, status checks)
  • Device Support Detection: Checks for configfs or sysfs USB gadget support
// Example: Mounting an ISO
isoDriveManager.mount(
    filePath = "/sdcard/isodrive/ubuntu.iso",
    mountType = MountType.CDROM,
    readOnly = true
)

2. RootManager (root/RootManager.kt)

Handles all root access operations:

  • Root Detection: Checks for available root methods (Magisk, KernelSU, APatch, su binary)
  • Cached Status: Stores root grant status to avoid repeated prompts
  • Command Execution: Executes shell commands with root privileges
  • Error Handling: Captures stdout, stderr, and exit codes
val result = RootManager.executeCommand("isodrive status")
if (result.isSuccess) {
    // Parse output
}

3. USB Gadget Subsystem

ISO Droid supports both modern and legacy USB gadget interfaces:

ConfigFS (Modern - Preferred)

  • Path: /config/usb_gadget/
  • More flexible and feature-rich
  • Standard on Android 8.0+ devices

SysFS (Legacy)

  • Path: /sys/class/android_usb/
  • Older interface
  • Fallback for older devices

The isodrive binary automatically detects and uses the appropriate interface.

4. Mount Types

Mass Storage Mode

  • Exposes the file as a USB mass storage device
  • Supports read-write mode for IMG files
  • Host PC sees it as a USB flash drive
  • Can be formatted with any filesystem (FAT32, NTFS, exFAT, ext4)

CD-ROM Mode

  • Exposes the file as a virtual CD/DVD drive
  • Always read-only
  • Ideal for bootable ISOs
  • Host PC sees it as an optical drive

UI Architecture

Jetpack Compose

All UI is built using Jetpack Compose with Material 3:

  • Declarative: UI is a function of state
  • Reactive: Automatically updates when state changes
  • Type-safe: Compile-time checking for UI code

Key Screens

  1. MainScreen (ui/screens/MainScreen.kt)

    • File browser with OS icon detection
    • Mount/unmount operations
    • Status card showing current mount state
  2. DownloadsScreen (ui/screens/DownloadsScreen.kt)

    • Curated list of OS download links
    • Grouped by category (Linux, BSD, Windows, Recovery)
  3. SettingsScreen (ui/screens/SettingsScreen.kt)

    • Root permission status
    • Notification permission (Android 13+)
    • Default ISO directory configuration
    • App version and build information

State Management

Uses Kotlin StateFlow for reactive state:

data class MainUiState(
    val isLoading: Boolean = true,
    val hasRoot: Boolean? = null,
    val isSupported: Boolean? = null,
    val mountStatus: MountStatus = MountStatus.UNMOUNTED,
    val isoFiles: List<IsoFile> = emptyList(),
    // ...
)

OS Icon Detection

Dynamic icon matching system:

  1. Icon Storage: SVG files in app/src/main/assets/osicons/

  2. Dynamic Loading: Icons are loaded at runtime via AssetManager

  3. Fuzzy Matching: Filename matching algorithm:

    • Converts filename to lowercase
    • Checks for icon name presence (e.g., "ubuntu" in "ubuntu-22.04-desktop.iso")
    • Prioritizes longer matches (e.g., "linuxmint" over "linux")
    • Prefers earlier position in filename
  4. Rendering: Uses Coil image library with SVG decoder

  5. Badge System: Small circular badge shows file type (ISO/IMG)

File Operations

Creating IMG Files

// User specifies size (e.g., 4 GB)
val sizeBytes = 4L * 1024 * 1024 * 1024

// Creates sparse file using dd with progress reporting
dd if=/dev/zero of=/sdcard/isodrive/custom.img bs=1M count=4096

// Progress events are emitted via CreateImgEventBus

Rename/Delete Protection

  • Files currently mounted cannot be renamed or deleted
  • UI shows warning message and disables actions
  • Prevents accidental data corruption

Notifications

Persistent notification while mounted:

  • Information: Shows mounted filename and mount type
  • Quick Actions: Unmount button in notification
  • Foreground Service: Ensures notification stays visible
  • Auto-dismiss: Notification removed after unmount

Data Persistence

Uses Android DataStore (Preferences):

// Saves user preferences
dataStore.edit { preferences ->
    preferences[KEY_ISO_DIRECTORY] = "/sdcard/custom/path"
}

Stores:

  • ISO directory path
  • Root grant status (from setup wizard)
  • App settings

Event Bus System

Decoupled communication using Kotlin Flow:

// Mount events
object MountEventBus {
    private val _events = MutableSharedFlow<MountEvent>()
    val events: SharedFlow<MountEvent> = _events.asSharedFlow()
}

// Emit event from notification
MountEventBus.emit(MountEvent.Unmounted)

// Observe in ViewModel
MountEventBus.events.collect { event ->
    when (event) {
        is MountEvent.Unmounted -> updateUiState()
    }
}

Dependencies

Core Libraries

  • Jetpack Compose: Modern declarative UI
  • Material 3: Design system and components
  • Lifecycle & ViewModel: Android Architecture Components
  • DataStore: Modern preference storage
  • Coroutines: Asynchronous programming

Image Loading

  • Coil: Image loading library
  • Coil SVG: SVG decoder for OS icons

JSON Parsing

  • org.json: Parsing OS download list (assets/os.json)

Testing

Manual Testing Checklist

  • Root access granted on first launch
  • USB gadget support detected correctly
  • ISO files mount successfully
  • IMG files can be created and mounted
  • OS icons display correctly
  • Unmount via notification works
  • File rename/delete protection when mounted
  • Settings persist across app restarts
  • Dark mode toggles correctly

Device Compatibility

Tested on:

  • Modern devices with ConfigFS (Android 8.0+)
  • Legacy devices with SysFS (older Android versions)
  • Various root methods (Magisk, KernelSU, APatch)

Debugging

Enable Verbose Logging

Check logcat for ISO Droid logs:

adb logcat | grep -i "isodroid\|isodrive"

Common Issues

"Device not supported"

  • Check for USB gadget support: ls /config/usb_gadget/ or ls /sys/class/android_usb/
  • Verify kernel has USB gadget drivers

"Root access required"

  • Ensure root manager (Magisk/KernelSU) is installed
  • Grant root access when prompted
  • Check RootManager.hasRoot() returns true

Mount fails

  • Verify file exists and is readable
  • Check file isn't corrupted
  • Ensure no other USB modes are active
  • Try rebooting device

isodrive Binary Version

Check the bundled binary version:

adb shell
su
/data/data/sh.sar.isodroid/files/isodrive --version

Contributing

Code Style

  • Follow Kotlin coding conventions
  • Use meaningful variable names
  • Comment complex logic
  • Keep functions focused and small

Pull Requests

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Test thoroughly on a real device
  5. Submit a pull request with description

License

GNU General Public License v3.0 - See LICENSE file for details