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:
- The
isodrivebinary is compiled from nitanmarcel/isodrive - Place the compiled binary at
app/src/main/assets/bin/isodrivebefore building - 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
isodrivebinary 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
-
MainScreen (
ui/screens/MainScreen.kt)- File browser with OS icon detection
- Mount/unmount operations
- Status card showing current mount state
-
DownloadsScreen (
ui/screens/DownloadsScreen.kt)- Curated list of OS download links
- Grouped by category (Linux, BSD, Windows, Recovery)
-
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:
-
Icon Storage: SVG files in
app/src/main/assets/osicons/ -
Dynamic Loading: Icons are loaded at runtime via
AssetManager -
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
-
Rendering: Uses Coil image library with SVG decoder
-
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/orls /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
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly on a real device
- Submit a pull request with description
License
GNU General Public License v3.0 - See LICENSE file for details