working poc
This commit is contained in:
273
app/src/main/java/sh/sar/isodroid/isodrive/IsoDriveManager.kt
Normal file
273
app/src/main/java/sh/sar/isodroid/isodrive/IsoDriveManager.kt
Normal file
@@ -0,0 +1,273 @@
|
||||
package sh.sar.isodroid.isodrive
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import sh.sar.isodroid.data.MountOptions
|
||||
import sh.sar.isodroid.data.MountStatus
|
||||
import sh.sar.isodroid.data.MountType
|
||||
import sh.sar.isodroid.root.RootManager
|
||||
import java.io.File
|
||||
|
||||
class IsoDriveManager(private val context: Context) {
|
||||
|
||||
private val binaryName = "isodrive"
|
||||
private var binaryPath: String? = null
|
||||
|
||||
suspend fun initialize(): Boolean = withContext(Dispatchers.IO) {
|
||||
extractBinary()
|
||||
}
|
||||
|
||||
private suspend fun extractBinary(): Boolean = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val abi = android.os.Build.SUPPORTED_ABIS.firstOrNull() ?: return@withContext false
|
||||
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
|
||||
}
|
||||
|
||||
val targetDir = File(context.filesDir, "bin")
|
||||
if (!targetDir.exists()) {
|
||||
targetDir.mkdirs()
|
||||
}
|
||||
|
||||
val targetFile = File(targetDir, binaryName)
|
||||
|
||||
// Check if binary already exists
|
||||
if (targetFile.exists()) {
|
||||
binaryPath = targetFile.absolutePath
|
||||
return@withContext true
|
||||
}
|
||||
|
||||
// Extract binary from assets
|
||||
try {
|
||||
context.assets.open(assetPath).use { input ->
|
||||
targetFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
targetFile.setExecutable(true, false)
|
||||
binaryPath = targetFile.absolutePath
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
// Binary not bundled yet, will use system isodrive if available
|
||||
checkSystemBinary()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkSystemBinary(): Boolean = withContext(Dispatchers.IO) {
|
||||
// Check common locations first (more reliable than 'which')
|
||||
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"
|
||||
)
|
||||
for (path in commonPaths) {
|
||||
val checkResult = RootManager.executeCommand("test -f $path && echo exists")
|
||||
if (checkResult.success && checkResult.output.contains("exists")) {
|
||||
binaryPath = path
|
||||
return@withContext true
|
||||
}
|
||||
}
|
||||
|
||||
// Try 'which' as fallback
|
||||
val result = RootManager.executeCommand("which isodrive 2>/dev/null")
|
||||
if (result.success && result.output.isNotBlank()) {
|
||||
binaryPath = result.output.trim()
|
||||
return@withContext true
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
suspend fun isSupported(): SupportStatus = withContext(Dispatchers.IO) {
|
||||
// First check if binary is available
|
||||
if (binaryPath == null && !extractBinary()) {
|
||||
return@withContext SupportStatus.NO_BINARY
|
||||
}
|
||||
|
||||
// Try to mount configfs if not already mounted
|
||||
RootManager.executeCommand("mount -t configfs none /sys/kernel/config 2>/dev/null")
|
||||
|
||||
// Check configfs support - look for usb_gadget directory
|
||||
val configfsCheck = RootManager.executeCommand(
|
||||
"ls /sys/kernel/config/usb_gadget/ 2>/dev/null"
|
||||
)
|
||||
if (configfsCheck.success && configfsCheck.output.isNotBlank()) {
|
||||
return@withContext SupportStatus.CONFIGFS_SUPPORTED
|
||||
}
|
||||
|
||||
// Alternative configfs check - check if configfs is mounted at all
|
||||
val configfsMountCheck = RootManager.executeCommand(
|
||||
"mount | grep configfs"
|
||||
)
|
||||
if (configfsMountCheck.success && configfsMountCheck.output.contains("configfs")) {
|
||||
// configfs is mounted, check for usb_gadget support
|
||||
val gadgetCheck = RootManager.executeCommand(
|
||||
"find /sys/kernel/config -maxdepth 2 -name 'usb_gadget' -type d 2>/dev/null"
|
||||
)
|
||||
if (gadgetCheck.success && gadgetCheck.output.isNotBlank()) {
|
||||
return@withContext SupportStatus.CONFIGFS_SUPPORTED
|
||||
}
|
||||
}
|
||||
|
||||
// Check sysfs/usbgadget support (legacy Android USB gadget)
|
||||
val sysfsCheck = RootManager.executeCommand(
|
||||
"test -d /sys/class/android_usb/android0 && echo supported"
|
||||
)
|
||||
if (sysfsCheck.success && sysfsCheck.output.contains("supported")) {
|
||||
return@withContext SupportStatus.SYSFS_SUPPORTED
|
||||
}
|
||||
|
||||
// If we have the binary, assume the user knows their device supports it
|
||||
// Let isodrive itself determine support at mount time
|
||||
if (binaryPath != null) {
|
||||
return@withContext SupportStatus.CONFIGFS_SUPPORTED
|
||||
}
|
||||
|
||||
SupportStatus.NOT_SUPPORTED
|
||||
}
|
||||
|
||||
suspend fun mount(isoPath: String, options: MountOptions): MountResult = withContext(Dispatchers.IO) {
|
||||
if (binaryPath == null) {
|
||||
return@withContext MountResult(
|
||||
success = false,
|
||||
message = "isodrive binary not found"
|
||||
)
|
||||
}
|
||||
|
||||
// Validate file exists
|
||||
val fileCheck = RootManager.executeCommand("test -f \"$isoPath\" && echo exists")
|
||||
if (!fileCheck.success || !fileCheck.output.contains("exists")) {
|
||||
return@withContext MountResult(
|
||||
success = false,
|
||||
message = "File not found: $isoPath"
|
||||
)
|
||||
}
|
||||
|
||||
// Validate options
|
||||
if (options.cdrom && !options.readOnly) {
|
||||
return@withContext MountResult(
|
||||
success = false,
|
||||
message = "CD-ROM mode requires read-only"
|
||||
)
|
||||
}
|
||||
|
||||
// Build command
|
||||
val args = options.toCommandArgs().joinToString(" ")
|
||||
val command = "$binaryPath \"$isoPath\" $args"
|
||||
|
||||
val result = RootManager.executeCommand(command)
|
||||
|
||||
if (result.success) {
|
||||
MountResult(
|
||||
success = true,
|
||||
message = "Mounted successfully"
|
||||
)
|
||||
} else {
|
||||
MountResult(
|
||||
success = false,
|
||||
message = result.error.ifBlank { result.output.ifBlank { "Mount failed with exit code ${result.exitCode}" } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unmount(): MountResult = withContext(Dispatchers.IO) {
|
||||
if (binaryPath == null) {
|
||||
return@withContext MountResult(
|
||||
success = false,
|
||||
message = "isodrive binary not found"
|
||||
)
|
||||
}
|
||||
|
||||
// Running isodrive without arguments unmounts
|
||||
val result = RootManager.executeCommand(binaryPath!!)
|
||||
|
||||
MountResult(
|
||||
success = result.success || result.output.contains("Usage"),
|
||||
message = if (result.success || result.output.contains("Usage")) "Unmounted successfully" else result.error.ifBlank { "Unmount failed" }
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getStatus(): MountStatus = withContext(Dispatchers.IO) {
|
||||
// Check configfs lun file
|
||||
val lunFileResult = RootManager.executeCommand(
|
||||
"cat /sys/kernel/config/usb_gadget/*/functions/mass_storage.0/lun.0/file 2>/dev/null"
|
||||
)
|
||||
|
||||
if (lunFileResult.success && lunFileResult.output.isNotBlank()) {
|
||||
val path = lunFileResult.output.trim()
|
||||
if (path.isNotEmpty()) {
|
||||
// Check if it's cdrom mode
|
||||
val cdromResult = RootManager.executeCommand(
|
||||
"cat /sys/kernel/config/usb_gadget/*/functions/mass_storage.0/lun.0/cdrom 2>/dev/null"
|
||||
)
|
||||
val isCdrom = cdromResult.output.trim() == "1"
|
||||
|
||||
// Check if read-only
|
||||
val roResult = RootManager.executeCommand(
|
||||
"cat /sys/kernel/config/usb_gadget/*/functions/mass_storage.0/lun.0/ro 2>/dev/null"
|
||||
)
|
||||
val isReadOnly = roResult.output.trim() != "0"
|
||||
|
||||
return@withContext MountStatus(
|
||||
mounted = true,
|
||||
path = path,
|
||||
type = if (isCdrom) MountType.CDROM else MountType.MASS_STORAGE,
|
||||
readOnly = isReadOnly
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check sysfs method
|
||||
val sysfsResult = RootManager.executeCommand(
|
||||
"cat /sys/class/android_usb/android0/f_mass_storage/lun/file 2>/dev/null"
|
||||
)
|
||||
|
||||
if (sysfsResult.success && sysfsResult.output.isNotBlank()) {
|
||||
val path = sysfsResult.output.trim()
|
||||
if (path.isNotEmpty()) {
|
||||
return@withContext MountStatus(
|
||||
mounted = true,
|
||||
path = path,
|
||||
type = MountType.MASS_STORAGE,
|
||||
readOnly = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MountStatus.UNMOUNTED
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var instance: IsoDriveManager? = null
|
||||
|
||||
fun getInstance(context: Context): IsoDriveManager {
|
||||
return instance ?: synchronized(this) {
|
||||
instance ?: IsoDriveManager(context.applicationContext).also { instance = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class SupportStatus {
|
||||
CONFIGFS_SUPPORTED,
|
||||
SYSFS_SUPPORTED,
|
||||
NOT_SUPPORTED,
|
||||
NO_BINARY
|
||||
}
|
||||
|
||||
data class MountResult(
|
||||
val success: Boolean,
|
||||
val message: String
|
||||
)
|
||||
Reference in New Issue
Block a user