diff --git a/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt b/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt index ffe364b6..53289b47 100644 --- a/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt @@ -24,7 +24,7 @@ import java.io.File import java.io.IOException class BackupRestoreFlow(private val partitionId: Int): WizardFlow() { - override fun get(vm: WizardActivityState): List { + override fun get(vm: WizardState): List { val c = CreateBackupDataHolder(vm, partitionId) return listOf(WizardPage("start", NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, @@ -45,7 +45,7 @@ class BackupRestoreFlow(private val partitionId: Int): WizardFlow() { } } -private class CreateBackupDataHolder(val vm: WizardActivityState, val pi: Int) { +private class CreateBackupDataHolder(val vm: WizardState, val pi: Int) { var action: Int = 0 var path: Uri? = null var meta: SDUtils.SDPartitionMeta? = null @@ -93,17 +93,22 @@ private fun SelectDroidBoot(c: CreateBackupDataHolder) { else -> "" } ) - val next = { it: Uri -> - c.path = it - nextButtonAvailable = true - c.vm.nextText = c.vm.activity.getString(R.string.next) - c.vm.onNext = { i -> i.navigate("go") } - } + val next = Button(onClick = { if (c.action != 1) { - c.vm.activity.chooseFile("*/*", next) + c.vm.activity.chooseFile("*/*") { + c.vm.chosen["file"] = WizardState.DownloadedFile(it, null) + nextButtonAvailable = true + c.vm.nextText = c.vm.activity.getString(R.string.next) + c.vm.onNext = { i -> i.navigate("go") } + } } else { - c.vm.activity.createFile("${c.meta!!.dumpKernelPartition(c.pi).name}.img", next) + c.vm.activity.createFile("${c.meta!!.dumpKernelPartition(c.pi).name}.img") { + c.path = it + nextButtonAvailable = true + c.vm.nextText = c.vm.activity.getString(R.string.next) + c.vm.onNext = { i -> i.navigate("go") } + } } }) { Text(stringResource(if (c.action != 1) R.string.choose_file else R.string.create_file)) @@ -117,40 +122,32 @@ private fun Flash(c: CreateBackupDataHolder) { Terminal(logFile = "flash_${System.currentTimeMillis()}.txt") { terminal -> c.vm.logic.extractToolkit(terminal) terminal.add(c.vm.activity.getString(R.string.term_starting)) - try { - val p = c.meta!!.dumpKernelPartition(c.pi) - if (!c.vm.logic.unmount(p).to(terminal).exec().isSuccess) - throw IOException(c.vm.activity.getString(R.string.term_cant_umount)) - if (c.action == 1) { - c.vm.copy( - SuFileInputStream.open(File(p.path)), - c.vm.activity.contentResolver.openOutputStream(c.path!!)!! - ) - } else if (c.action == 2) { - c.vm.copyPriv( - c.vm.activity.contentResolver.openInputStream(c.path!!)!!, - File(p.path) - ) - } else if (c.action == 3) { - val f = File(c.vm.logic.cacheDir, System.currentTimeMillis().toString()) - c.vm.copyUnpriv(c.vm.activity.contentResolver.openInputStream(c.path!!)!!, f) - val result2 = Shell.cmd( - File( - c.vm.logic.toolkitDir, - "simg2img" - ).absolutePath + " ${f.absolutePath} ${p.path}" - ).to(terminal).exec() - if (!result2.isSuccess) { - terminal.add(c.vm.activity.getString(R.string.term_failure)) - return@Terminal - } - } else { - throw IOException(c.vm.activity.getString(R.string.term_invalid_action)) + val p = c.meta!!.dumpKernelPartition(c.pi) + if (!c.vm.logic.unmount(p).to(terminal).exec().isSuccess) + throw IOException(c.vm.activity.getString(R.string.term_cant_umount)) + if (c.action == 1) { + c.vm.copy( + SuFileInputStream.open(File(p.path)), + c.vm.activity.contentResolver.openOutputStream(c.path!!)!! + ) + } else if (c.action == 2) { + c.vm.copyPriv( + c.vm.chosen["file"]!!.openInputStream(c.vm), + File(p.path) + ) + } else if (c.action == 3) { + val result2 = Shell.cmd( + File( + c.vm.logic.toolkitDir, + "simg2img" + ).absolutePath + " ${c.vm.chosen["file"]!!.toFile(c.vm).absolutePath} ${p.path}" + ).to(terminal).exec() + if (!result2.isSuccess) { + terminal.add(c.vm.activity.getString(R.string.term_failure)) + return@Terminal } - } catch (e: IOException) { - terminal.add(c.vm.activity.getString(R.string.term_backup_restore_fail)) - terminal.add(if (e.message != null) e.message!! else "(null)") - terminal.add(c.vm.activity.getString(R.string.term_contact_support)) + } else { + throw IOException(c.vm.activity.getString(R.string.term_invalid_action)) } terminal.add(c.vm.activity.getString(R.string.term_success)) c.vm.nextText = c.vm.activity.getString(R.string.finish) diff --git a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt index f3efbb21..c2cb2c33 100644 --- a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt @@ -61,7 +61,7 @@ import java.math.BigDecimal import java.net.URL class CreatePartFlow(private val desiredStartSector: Long): WizardFlow() { - override fun get(vm: WizardActivityState): List { + override fun get(vm: WizardState): List { val c = CreatePartDataHolder(vm, desiredStartSector) return listOf(WizardPage("start", NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, @@ -92,7 +92,7 @@ class CreatePartFlow(private val desiredStartSector: Long): WizardFlow() { } } -private class CreatePartDataHolder(val vm: WizardActivityState, val desiredStartSector: Long) { +private class CreatePartDataHolder(val vm: WizardState, val desiredStartSector: Long) { var meta by mutableStateOf(null) lateinit var p: SDUtils.Partition.FreeSpace var startSectorRelative = 0L @@ -376,17 +376,17 @@ private fun Shop(c: CreatePartDataHolder) { while (i < inets.length()) { val l = inets.getJSONObject(i) vm.inetAvailable[l.getString("id")] = - WizardActivityState.Downloadable( + WizardState.Downloadable( l.getString("url"), - l.optString("hash"), + l.getStringOrNull("hash"), l.getString("desc") ) i++ } vm.idNeeded.add("_install.sh_") - vm.inetAvailable["_install.sh_"] = WizardActivityState.Downloadable( + vm.inetAvailable["_install.sh_"] = WizardState.Downloadable( o.getString("scriptname"), - o.optString("scriptSha256"), + o.getStringOrNull("scriptSha256"), vm.activity.getString(R.string.installer_sh) ) @@ -655,7 +655,7 @@ private fun Flash(c: CreatePartDataHolder) { Terminal(logFile = "install_${System.currentTimeMillis()}.txt") { terminal -> c.vm.logic.extractToolkit(terminal) if (c.partitionName == null) { // OS install - val createdParts = ArrayMap() // order is important + val createdParts = mutableListOf>() // order is important val fn = c.romFolderName terminal.add(vm.activity.getString(R.string.term_f_name, fn)) terminal.add(vm.activity.getString(R.string.term_g_name, c.romDisplayName)) @@ -683,7 +683,7 @@ private fun Flash(c: CreatePartDataHolder) { if (r.out.joinToString("\n").contains("kpartx")) { terminal.add(vm.activity.getString(R.string.term_reboot_asap)) } - createdParts[part] = c.meta!!.nid + createdParts.add(Pair(part, c.meta!!.nid)) c.meta = SDUtils.generateMeta(c.vm.deviceInfo) // do not assert there is leftover space if we just created the last partition we want to create if (index < c.parts.size - 1) { @@ -715,7 +715,7 @@ private fun Flash(c: CreatePartDataHolder) { entry["dtbo"] = "$fn/dtbo.dtbo" entry["options"] = c.cmdline entry["xtype"] = c.rtype - entry["xpart"] = createdParts.values.joinToString(":") + entry["xpart"] = createdParts.map { it.second }.joinToString(":") if (c.dmaMeta.contains("updateJson") && c.dmaMeta["updateJson"] != null) entry["xupdate"] = c.dmaMeta["updateJson"]!! entry.exportToFile(File(vm.logic.abmEntries, "$fn.conf")) @@ -729,14 +729,13 @@ private fun Flash(c: CreatePartDataHolder) { if (!c.vm.idNeeded.contains(part.id)) continue terminal.add(vm.activity.getString(R.string.term_flashing_s, part.id)) val f = c.vm.chosen[part.id]!! - val tp = File(meta.dumpKernelPartition(createdParts[part]!!).path) + val tp = File(meta.dumpKernelPartition(createdParts.find { it.first == part }!!.second).path) if (part.sparse) { - val f2 = f.toFile(c.vm) val result2 = Shell.cmd( File( c.vm.logic.toolkitDir, "simg2img" - ).absolutePath + " ${f2.absolutePath} ${tp.absolutePath}" + ).absolutePath + " ${f.toFile(c.vm).absolutePath} ${tp.absolutePath}" ).to(terminal).exec() f.delete() if (!result2.isSuccess) { @@ -744,8 +743,7 @@ private fun Flash(c: CreatePartDataHolder) { return@Terminal } } else { - val f2 = f.openInputStream(c.vm) - c.vm.copyPriv(f2, tp) + c.vm.copyPriv(f.openInputStream(c.vm), tp) } terminal.add(vm.activity.getString(R.string.term_done)) } @@ -756,7 +754,7 @@ private fun Flash(c: CreatePartDataHolder) { cmd += " " + c.vm.chosen[i]!!.toFile(vm).absolutePath } for (i in c.parts) { - cmd += " " + createdParts[i] + cmd += " " + createdParts.find { it.first == i }!!.second } val result = vm.logic.runShFileWithArgs(cmd).to(terminal).exec() if (!result.isSuccess) { diff --git a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt index e1bc9dcd..7b846305 100644 --- a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt @@ -39,7 +39,7 @@ import java.io.IOException import java.net.URL class DroidBootFlow : WizardFlow() { - override fun get(vm: WizardActivityState): List { + override fun get(vm: WizardState): List { val d = DroidBootFlowDataHolder(vm) return listOf(WizardPage("start", NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, @@ -65,12 +65,12 @@ class DroidBootFlow : WizardFlow() { } } -class DroidBootFlowDataHolder(val vm: WizardActivityState) { +class DroidBootFlowDataHolder(val vm: WizardState) { var osName by mutableStateOf(vm.activity.getString(R.string.android)) } @Composable -private fun Start(vm: WizardActivityState) { +private fun Start(vm: WizardState) { Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize() ) { @@ -91,7 +91,7 @@ private fun Start(vm: WizardActivityState) { // shared across DroidBootFlow, UpdateDroidBootFlow, FixDroidBootFlow @Composable -fun LoadDroidBootJson(vm: WizardActivityState, content: @Composable () -> Unit) { +fun LoadDroidBootJson(vm: WizardState, content: @Composable () -> Unit) { var loading by remember { mutableStateOf(!vm.deviceInfo.isBooted(vm.logic) || vm.deviceInfo.postInstallScript) } var error by remember { mutableStateOf(false) } LaunchedEffect(Unit) { @@ -106,8 +106,8 @@ fun LoadDroidBootJson(vm: WizardActivityState, content: @Composable () -> Unit) if (!vm.deviceInfo.isBooted(vm.logic)) { val bl = json.getJSONObject("bootloader") val url = bl.getString("url") - val sha = bl.optString("sha256") - vm.inetAvailable["droidboot"] = WizardActivityState.Downloadable( + val sha = bl.getStringOrNull("sha256") + vm.inetAvailable["droidboot"] = WizardState.Downloadable( url, sha, vm.activity.getString(R.string.droidboot_online) ) vm.idNeeded.add("droidboot") @@ -115,8 +115,8 @@ fun LoadDroidBootJson(vm: WizardActivityState, content: @Composable () -> Unit) if (vm.deviceInfo.postInstallScript) { val i = json.getJSONObject("installScript") val url = i.getString("url") - val sha = i.optString("sha256") - vm.inetAvailable["_install.sh_"] = WizardActivityState.Downloadable( + val sha = i.getStringOrNull("sha256") + vm.inetAvailable["_install.sh_"] = WizardState.Downloadable( url, sha, vm.activity.getString(R.string.installer_sh) ) vm.idNeeded.add("_install.sh_") @@ -269,10 +269,9 @@ private fun Flash(d: DroidBootFlowDataHolder) { } } val tmpFile = if (vm.deviceInfo.postInstallScript) { - val tmpFile = createTempFileSu("abm", ".sh", vm.logic.rootTmpDir) - vm.copyPriv(vm.chosen["_install.sh_"]!!.openInputStream(vm), tmpFile) - tmpFile.setExecutable(true) - tmpFile + vm.chosen["_install.sh_"]!!.toFile(vm).also { + it.setExecutable(true) + } } else null terminal.add(vm.activity.getString(R.string.term_building_cfg)) diff --git a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt index 69e24ce0..2f6a4e26 100644 --- a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt @@ -18,7 +18,7 @@ import java.io.File import java.io.IOException class FixDroidBootFlow(): WizardFlow() { - override fun get(vm: WizardActivityState): List { + override fun get(vm: WizardState): List { return listOf(WizardPage("start", NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton("") {}) @@ -39,7 +39,7 @@ class FixDroidBootFlow(): WizardFlow() { } @Composable -private fun Start(vm: WizardActivityState) { +private fun Start(vm: WizardState) { LoadDroidBootJson(vm) { LaunchedEffect(Unit) { vm.nextText = vm.activity.getString(R.string.next) @@ -57,14 +57,13 @@ private fun Start(vm: WizardActivityState) { } @Composable -private fun Flash(vm: WizardActivityState) { +private fun Flash(vm: WizardState) { Terminal(logFile = "blfix_${System.currentTimeMillis()}.txt") { terminal -> vm.logic.extractToolkit(terminal) val tmpFile = if (vm.deviceInfo.postInstallScript) { - val tmpFile = createTempFileSu("abm", ".sh", vm.logic.rootTmpDir) - vm.copyPriv(vm.chosen["_install.sh_"]!!.openInputStream(vm), tmpFile) - tmpFile.setExecutable(true) - tmpFile + vm.chosen["_install.sh_"]!!.toFile(vm).also { + it.setExecutable(true) + } } else null terminal.add(vm.activity.getString(R.string.term_flashing_droidboot)) val backupLk = File(vm.logic.fileDir, "backup_lk.img") diff --git a/app/src/main/java/org/andbootmgr/app/MainActivity.kt b/app/src/main/java/org/andbootmgr/app/MainActivity.kt index 79fc6626..528bb91f 100644 --- a/app/src/main/java/org/andbootmgr/app/MainActivity.kt +++ b/app/src/main/java/org/andbootmgr/app/MainActivity.kt @@ -86,10 +86,8 @@ class MainActivityState(val activity: MainActivity?) { val cfg = withContext(Dispatchers.IO) { ConfigFile.importFromFile(logic!!.abmDbConf).toMap() } - withContext(Dispatchers.Main) { - defaultCfg.clear() - defaultCfg.putAll(cfg) - } + defaultCfg.clear() + defaultCfg.putAll(cfg) } } diff --git a/app/src/main/java/org/andbootmgr/app/Start.kt b/app/src/main/java/org/andbootmgr/app/Start.kt index d56b0a90..c65ac84a 100644 --- a/app/src/main/java/org/andbootmgr/app/Start.kt +++ b/app/src/main/java/org/andbootmgr/app/Start.kt @@ -579,7 +579,7 @@ private fun PartTool(vm: MainActivityState) { var initrdT by remember { mutableStateOf(e["initrd"] ?: "") } val initrdE by remember { derivedStateOf { !initrdT.matches(asciiRegex) } } var dtbT by remember { mutableStateOf(e["dtb"] ?: "") } - val dtbE by remember { derivedStateOf { dtbT.matches(asciiRegex) } } + val dtbE by remember { derivedStateOf { !dtbT.matches(asciiRegex) } } var optionsT by remember { mutableStateOf(e["options"] ?: "") } val optionsE by remember { derivedStateOf { !optionsT.matches(asciiRegex) } } var xtypeT by remember { mutableStateOf(e["xtype"] ?: "") } diff --git a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt index cad5730b..91b2ee1f 100644 --- a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt @@ -18,7 +18,7 @@ import java.io.File import java.io.IOException class UpdateDroidBootFlow: WizardFlow() { - override fun get(vm: WizardActivityState): List { + override fun get(vm: WizardState): List { return listOf(WizardPage("start", NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton("") {}) @@ -39,7 +39,7 @@ class UpdateDroidBootFlow: WizardFlow() { } @Composable -private fun Start(vm: WizardActivityState) { +private fun Start(vm: WizardState) { LoadDroidBootJson(vm) { LaunchedEffect(Unit) { vm.nextText = vm.activity.getString(R.string.next) @@ -57,14 +57,13 @@ private fun Start(vm: WizardActivityState) { } @Composable -private fun Flash(vm: WizardActivityState) { +private fun Flash(vm: WizardState) { Terminal(logFile = "blup_${System.currentTimeMillis()}.txt") { terminal -> vm.logic.extractToolkit(terminal) val tmpFile = if (vm.deviceInfo.postInstallScript) { - val tmpFile = createTempFileSu("abm", ".sh", vm.logic.rootTmpDir) - vm.copyPriv(vm.chosen["_install.sh_"]!!.openInputStream(vm), tmpFile) - tmpFile.setExecutable(true) - tmpFile + vm.chosen["_install.sh_"]!!.toFile(vm).also { + it.setExecutable(true) + } } else null terminal.add(vm.activity.getString(R.string.term_flashing_droidboot)) val backupLk = File(vm.logic.fileDir, "backup2_lk.img") diff --git a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt index 1a98096e..6d3baf97 100644 --- a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt @@ -1,34 +1,31 @@ package org.andbootmgr.app -import android.net.Uri import android.util.Log +import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.io.SuFile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.internal.http2.StreamResetException -import okio.buffer -import okio.sink import org.andbootmgr.app.util.ConfigFile import org.andbootmgr.app.util.SDUtils import org.andbootmgr.app.util.Terminal @@ -36,10 +33,9 @@ import org.json.JSONObject import org.json.JSONTokener import java.io.File import java.net.URL -import java.util.concurrent.TimeUnit class UpdateFlow(private val entryName: String): WizardFlow() { - override fun get(vm: WizardActivityState): List { + override fun get(vm: WizardState): List { val c = UpdateFlowDataHolder(vm, entryName) return listOf(WizardPage("start", NavButton(vm.activity.getString(R.string.cancel)) { @@ -54,7 +50,7 @@ class UpdateFlow(private val entryName: String): WizardFlow() { ) { Local(c) }, WizardPage("dload", - NavButton("") {}, + NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton("") {} ) { WizardDownloader(c.vm, "flash") @@ -67,7 +63,7 @@ class UpdateFlow(private val entryName: String): WizardFlow() { } } -private class UpdateFlowDataHolder(val vm: WizardActivityState, val entryFilename: String) { +private class UpdateFlowDataHolder(val vm: WizardState, val entryFilename: String) { var json: JSONObject? = null var e: ConfigFile? = null var ef: File? = null @@ -88,11 +84,15 @@ private fun Start(u: UpdateFlowDataHolder) { u.entryFilename ) ) + u.ef = u.vm.logic.abmEntries.resolve(u.entryFilename) try { val jsonText = URL(u.e!!["xupdate"]).readText() u.json = JSONTokener(jsonText).nextValue() as JSONObject } catch (e: Exception) { + launch(Dispatchers.Main) { + Toast.makeText(u.vm.activity, e.message, Toast.LENGTH_LONG).show() + } Log.e("ABM", Log.getStackTraceString(e)) } if (u.json != null) { @@ -116,7 +116,7 @@ private fun Start(u: UpdateFlowDataHolder) { val extraIdNeeded = j.getJSONArray("extraIds") var i = 0 while (i < extraIdNeeded.length()) { - vm.inetAvailable["boot$i"] = WizardActivityState.Downloadable( + vm.inetAvailable["boot$i"] = WizardState.Downloadable( extraIdNeeded.get(i) as String, null, "" @@ -124,9 +124,9 @@ private fun Start(u: UpdateFlowDataHolder) { vm.idNeeded.add("boot$i") i++ } - vm.inetAvailable["_install.sh_"] = WizardActivityState.Downloadable( + vm.inetAvailable["_install.sh_"] = WizardState.Downloadable( j.getString("script"), - j.optString("scriptSha256"), + j.getStringOrNull("scriptSha256"), vm.activity.getString(R.string.installer_sh) ) vm.idNeeded.add("_install.sh_") @@ -134,7 +134,7 @@ private fun Start(u: UpdateFlowDataHolder) { if (j.has("parts")) { val p = j.getJSONObject("parts") for (k in p.keys()) { - vm.inetAvailable["part$k"] = WizardActivityState.Downloadable( + vm.inetAvailable["part$k"] = WizardState.Downloadable( p.getString(k), null, "" @@ -142,7 +142,7 @@ private fun Start(u: UpdateFlowDataHolder) { vm.idNeeded.add("part$k") } } - updateJson = j.optString("updateJson") + updateJson = j.getStringOrNull("updateJson") val a = j.optJSONArray("sparse") if (a != null) { for (i in 0 until a.length()) { @@ -180,24 +180,26 @@ private fun Local(u: UpdateFlowDataHolder) { Text(stringResource(R.string.local_updater_3)) } Column { - Text(stringResource(R.string.how_many_extras)) var i by remember { mutableIntStateOf(0) } + Text(stringResource(R.string.how_many_extras, i)) Row { Button({ i++ }) { Text("+") } + Spacer(Modifier.width(5.dp)) Button({ i-- }) { Text("-") } - Button({ - u.vm.idNeeded.add("_install.sh_") - for (j in 0..i) { - u.vm.idNeeded.add("boot$j") - } - u.vm.navigate("dload") - }) { - Text(stringResource(R.string.install_update)) + } + Spacer(Modifier.height(5.dp)) + Button({ + u.vm.idNeeded.add("_install.sh_") + for (j in 1..i) { + u.vm.idNeeded.add("boot${j - 1}") } + u.vm.navigate("dload") + }) { + Text(stringResource(R.string.install_update)) } } } @@ -211,8 +213,7 @@ private fun Flash(u: UpdateFlowDataHolder) { val meta = SDUtils.generateMeta(u.vm.deviceInfo)!! Shell.cmd(SDUtils.umsd(meta)).exec() val tmpFile = if (u.vm.idNeeded.contains("_install.sh_")) { - createTempFileSu("abm", ".sh", u.vm.logic.rootTmpDir).also { - u.vm.copyPriv(u.vm.chosen["_install.sh_"]!!.openInputStream(u.vm), it) + u.vm.chosen["_install.sh_"]!!.toFile(u.vm).also { it.setExecutable(true) } } else null diff --git a/app/src/main/java/org/andbootmgr/app/WizardActivity.kt b/app/src/main/java/org/andbootmgr/app/Wizard.kt similarity index 91% rename from app/src/main/java/org/andbootmgr/app/WizardActivity.kt rename to app/src/main/java/org/andbootmgr/app/Wizard.kt index e19cc769..72600d81 100644 --- a/app/src/main/java/org/andbootmgr/app/WizardActivity.kt +++ b/app/src/main/java/org/andbootmgr/app/Wizard.kt @@ -52,7 +52,7 @@ import java.nio.file.Files import java.nio.file.StandardCopyOption abstract class WizardFlow { - abstract fun get(vm: WizardActivityState): List + abstract fun get(vm: WizardState): List } @Composable @@ -61,7 +61,7 @@ fun WizardCompat(mvm: MainActivityState, flow: WizardFlow) { mvm.activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) onDispose { mvm.activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } } - val vm = remember { WizardActivityState(mvm) } + val vm = remember { WizardState(mvm) } vm.navController = rememberNavController() val wizardPages = remember(flow) { flow.get(vm) } NavHost( @@ -93,7 +93,7 @@ fun WizardCompat(mvm: MainActivityState, flow: WizardFlow) { class HashMismatchException(message: String) : Exception(message) -class WizardActivityState(val mvm: MainActivityState) { +class WizardState(val mvm: MainActivityState) { val codename = mvm.deviceInfo!!.codename val activity = mvm.activity!! lateinit var navController: NavHostController @@ -101,8 +101,8 @@ class WizardActivityState(val mvm: MainActivityState) { val deviceInfo = mvm.deviceInfo!! var prevText by mutableStateOf(null) var nextText by mutableStateOf(null) - var onPrev by mutableStateOf<((WizardActivityState) -> Unit)?>(null) - var onNext by mutableStateOf<((WizardActivityState) -> Unit)?>(null) + var onPrev by mutableStateOf<((WizardState) -> Unit)?>(null) + var onNext by mutableStateOf<((WizardState) -> Unit)?>(null) val inetAvailable = mutableStateMapOf() val idNeeded = mutableStateListOf() @@ -113,7 +113,7 @@ class WizardActivityState(val mvm: MainActivityState) { netFile?.delete() } - fun openInputStream(vm: WizardActivityState): InputStream { + fun openInputStream(vm: WizardState): InputStream { netFile?.let { return FileInputStream(it) } @@ -126,7 +126,7 @@ class WizardActivityState(val mvm: MainActivityState) { throw IllegalStateException("invalid DledFile OR failure") } - fun toFile(vm: WizardActivityState): File { + fun toFile(vm: WizardState): File { netFile?.let { return it } safFile?.let { val istr = vm.activity.contentResolver.openInputStream(it) @@ -140,6 +140,7 @@ class WizardActivityState(val mvm: MainActivityState) { throw IllegalStateException("invalid DledFile OR safFile failure") } } + //suspend fun downloadRemainingFiles fun navigate(next: String) { prevText = null @@ -181,7 +182,7 @@ class WizardActivityState(val mvm: MainActivityState) { } -class NavButton(val text: String, val onClick: (WizardActivityState) -> Unit) +class NavButton(val text: String, val onClick: (WizardState) -> Unit) class WizardPage(override val name: String, override val prev: NavButton, override val next: NavButton, override val run: @Composable () -> Unit ) : IWizardPage @@ -211,7 +212,7 @@ fun BasicButtonRow(prev: String, onPrev: () -> Unit, } @Composable -fun WizardDownloader(vm: WizardActivityState, next: String) { +fun WizardDownloader(vm: WizardState, next: String) { Column(Modifier.fillMaxSize()) { Card { Row( @@ -260,7 +261,7 @@ fun WizardDownloader(vm: WizardActivityState, next: String) { Text(stringResource(R.string.undo)) } } else { - if (vm.inetAvailable.containsKey(i)) { + /*if (vm.inetAvailable.containsKey(i)) { Button(onClick = { CoroutineScope(Dispatchers.Main).launch { val url = vm.inetAvailable[i]!!.url @@ -277,7 +278,7 @@ fun WizardDownloader(vm: WizardActivityState, next: String) { cancelDownload = null } if (client.run()) { - vm.chosen[i] = WizardActivityState.DownloadedFile(null, downloadedFile) + vm.chosen[i] = WizardState.DownloadedFile(null, downloadedFile) } } catch (e: Exception) { Log.e("ABM", Log.getStackTraceString(e)) @@ -294,10 +295,10 @@ fun WizardDownloader(vm: WizardActivityState, next: String) { }) { Text(stringResource(R.string.download)) } - } + }*/ Button(onClick = { vm.activity.chooseFile("*/*") { - vm.chosen[i] = WizardActivityState.DownloadedFile(it, null) + vm.chosen[i] = WizardState.DownloadedFile(it, null) } }) { Text(stringResource(R.string.choose)) @@ -306,7 +307,8 @@ fun WizardDownloader(vm: WizardActivityState, next: String) { } } } - val isOk = vm.idNeeded.find { !vm.chosen.containsKey(it) } == null + val isOk = vm.idNeeded.find { !vm.chosen.containsKey(it) && + !vm.inetAvailable.containsKey(it) } == null LaunchedEffect(isOk) { if (isOk) { vm.onNext = { it.navigate(next) } diff --git a/app/src/main/java/org/andbootmgr/app/ext.kt b/app/src/main/java/org/andbootmgr/app/ext.kt index dd6f409c..5e27d9e3 100644 --- a/app/src/main/java/org/andbootmgr/app/ext.kt +++ b/app/src/main/java/org/andbootmgr/app/ext.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.topjohnwu.superuser.io.SuFile import com.topjohnwu.superuser.nio.ExtendedFile +import org.json.JSONObject import java.io.File import java.io.IOException import kotlin.math.abs @@ -21,41 +22,6 @@ import kotlin.math.abs open class ActionAbortedError(e: Exception?) : Exception(e) class ActionAbortedCleanlyError(e: Exception?) : ActionAbortedError(e) -@Throws(IOException::class) -private fun generateFile(prefix: String, suffix: String, dir: File?): File { - var n = (Math.random() * Long.MAX_VALUE).toLong() - n = if (n == Long.MIN_VALUE) { - 0 // corner case - } else { - abs(n.toDouble()).toLong() - } - val name = prefix + n.toString() + suffix - val f = File(dir, name) - if (name != f.name) - throw IOException("Unable to create temporary file, $f") - - return f -} - -@Throws(IOException::class) -fun createTempFileSu( - prefix: String, suffix: String?, - directory: File? -): ExtendedFile { - require(prefix.length >= 3) { "Prefix string too short" } - - val tmpdir = if ((directory != null)) directory else File(System.getProperty("java.io.tmpdir", ".")!!) - var f: File - do { - f = generateFile(prefix, suffix ?: ".tmp", tmpdir) - } while (f.exists()) - val ff = SuFile.open(f.toURI()) - - if (!ff.createNewFile()) throw IOException("Unable to create temporary file") - - return ff -} - @Composable fun LoadingCircle(text: String, modifier: Modifier = Modifier, paddingBetween: Dp = 20.dp) { Row( @@ -68,6 +34,8 @@ fun LoadingCircle(text: String, modifier: Modifier = Modifier, paddingBetween: D } } +fun JSONObject.getStringOrNull(key: String) = if (has(key)) getString(key) else null + val safeFsRegex = Regex("\\A[A-Za-z0-9_-]+\\z") val asciiNonEmptyRegex = Regex("\\A\\p{ASCII}+\\z") val numberRegex = Regex("\\A\\d+\\z") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 78afeb52..6e41346f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -260,5 +260,5 @@ Restoring backup… bytes percent - How many extra IDs? + How many extra IDs? Currently: %d