diff --git a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt index dc4990d6..f3efbb21 100644 --- a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt @@ -82,7 +82,7 @@ class CreatePartFlow(private val desiredStartSector: Long): WizardFlow() { NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton("") {} ) { - WizardDownloader(c.vm) + WizardDownloader(c.vm, "flash") }, WizardPage("flash", NavButton("") {}, NavButton("") {} diff --git a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt index f2ea95f0..e1bc9dcd 100644 --- a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt @@ -1,17 +1,13 @@ package org.andbootmgr.app -import android.net.Uri import android.os.Handler import android.os.Looper 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.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextField @@ -23,8 +19,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -46,7 +40,7 @@ import java.net.URL class DroidBootFlow : WizardFlow() { override fun get(vm: WizardActivityState): List { - val booted = vm.deviceInfo.isBooted(vm.logic) + val d = DroidBootFlowDataHolder(vm) return listOf(WizardPage("start", NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton(vm.activity.getString(R.string.next)) { it.navigate("input") }) @@ -56,21 +50,25 @@ class DroidBootFlow : WizardFlow() { NavButton(vm.activity.getString(R.string.prev)) { it.navigate("start") }, NavButton("") {} ) { - Input(vm) + Input(d) }, WizardPage("dload", NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton("") {} ) { - WizardDownloader(vm) + WizardDownloader(vm, "flash") }, WizardPage("flash", NavButton("") {}, NavButton("") {} ) { - Flash(vm) + Flash(d) }) } } +class DroidBootFlowDataHolder(val vm: WizardActivityState) { + var osName by mutableStateOf(vm.activity.getString(R.string.android)) +} + @Composable private fun Start(vm: WizardActivityState) { Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, @@ -118,10 +116,10 @@ fun LoadDroidBootJson(vm: WizardActivityState, content: @Composable () -> Unit) val i = json.getJSONObject("installScript") val url = i.getString("url") val sha = i.optString("sha256") - vm.inetAvailable["install"] = WizardActivityState.Downloadable( + vm.inetAvailable["_install.sh_"] = WizardActivityState.Downloadable( url, sha, vm.activity.getString(R.string.installer_sh) ) - vm.idNeeded.add("install") + vm.idNeeded.add("_install.sh_") } loading = false } catch (e: Exception) { @@ -143,18 +141,14 @@ fun LoadDroidBootJson(vm: WizardActivityState, content: @Composable () -> Unit) } @Composable -private fun Input(vm: WizardActivityState) { - LoadDroidBootJson(vm) { +private fun Input(d: DroidBootFlowDataHolder) { + LoadDroidBootJson(d.vm) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize() ) { - LaunchedEffect(Unit) { // TODO can't I do this better? - if (vm.texts.isBlank()) - vm.texts = vm.activity.getString(R.string.android) - } - val e = vm.texts.isBlank() || !vm.texts.matches(Regex("[\\dA-Za-z]+")) + val e = d.osName.isBlank() || !d.osName.matches(Regex("[\\dA-Za-z]+")) Text( stringResource(R.string.enter_name_for_current), @@ -162,9 +156,9 @@ private fun Input(vm: WizardActivityState) { modifier = Modifier.padding(vertical = 5.dp) ) TextField( - value = vm.texts, + value = d.osName, onValueChange = { - vm.texts = it + d.osName = it }, label = { Text(stringResource(R.string.os_name)) }, isError = e @@ -176,11 +170,11 @@ private fun Input(vm: WizardActivityState) { } LaunchedEffect(e) { if (e) { - vm.nextText = "" - vm.onNext = {} + d.vm.nextText = "" + d.vm.onNext = {} } else { - vm.nextText = vm.activity.getString(R.string.next) - vm.onNext = { it.navigate(if (vm.idNeeded.isNotEmpty()) "dload" else "flash") } + d.vm.nextText = d.vm.activity.getString(R.string.next) + d.vm.onNext = { it.navigate(if (d.vm.idNeeded.isNotEmpty()) "dload" else "flash") } } } } @@ -188,8 +182,8 @@ private fun Input(vm: WizardActivityState) { } @Composable -private fun Flash(vm: WizardActivityState) { - val flashType = "DroidBootFlashType" +private fun Flash(d: DroidBootFlowDataHolder) { + val vm = d.vm Terminal(logFile = "blflash_${System.currentTimeMillis()}.txt") { terminal -> vm.logic.extractToolkit(terminal) terminal.add(vm.activity.getString(R.string.term_preparing_fs)) @@ -276,7 +270,7 @@ private fun Flash(vm: WizardActivityState) { } val tmpFile = if (vm.deviceInfo.postInstallScript) { val tmpFile = createTempFileSu("abm", ".sh", vm.logic.rootTmpDir) - vm.copyPriv(vm.chosen["install"]!!.openInputStream(vm), tmpFile) + vm.copyPriv(vm.chosen["_install.sh_"]!!.openInputStream(vm), tmpFile) tmpFile.setExecutable(true) tmpFile } else null @@ -287,7 +281,7 @@ private fun Flash(vm: WizardActivityState) { db["timeout"] = "5" db.exportToFile(File(vm.logic.abmDb, "db.conf")) val entry = ConfigFile() - entry["title"] = vm.texts.trim() + entry["title"] = d.osName.trim() entry["linux"] = "null" entry["initrd"] = "null" entry["dtb"] = "null" diff --git a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt index 9282a9e5..69e24ce0 100644 --- a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt @@ -28,7 +28,7 @@ class FixDroidBootFlow(): WizardFlow() { NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton("") {} ) { - WizardDownloader(vm) + WizardDownloader(vm, "flash") }, WizardPage("flash", NavButton("") {}, NavButton("") {} @@ -62,7 +62,7 @@ private fun Flash(vm: WizardActivityState) { vm.logic.extractToolkit(terminal) val tmpFile = if (vm.deviceInfo.postInstallScript) { val tmpFile = createTempFileSu("abm", ".sh", vm.logic.rootTmpDir) - vm.copyPriv(vm.chosen["install"]!!.openInputStream(vm), tmpFile) + vm.copyPriv(vm.chosen["_install.sh_"]!!.openInputStream(vm), tmpFile) tmpFile.setExecutable(true) tmpFile } else null diff --git a/app/src/main/java/org/andbootmgr/app/MainActivity.kt b/app/src/main/java/org/andbootmgr/app/MainActivity.kt index 9db4560b..79fc6626 100644 --- a/app/src/main/java/org/andbootmgr/app/MainActivity.kt +++ b/app/src/main/java/org/andbootmgr/app/MainActivity.kt @@ -62,6 +62,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.andbootmgr.app.themes.ThemeViewModel import org.andbootmgr.app.themes.Themes @@ -81,8 +82,10 @@ class MainActivityState(val activity: MainActivity?) { var logic: DeviceLogic? = null private fun loadDefaultCfg() { - CoroutineScope(Dispatchers.IO).launch { - val cfg = ConfigFile.importFromFile(logic!!.abmDbConf).toMap() + runBlocking { + val cfg = withContext(Dispatchers.IO) { + ConfigFile.importFromFile(logic!!.abmDbConf).toMap() + } withContext(Dispatchers.Main) { defaultCfg.clear() defaultCfg.putAll(cfg) diff --git a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt index fe9b3f65..cad5730b 100644 --- a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt @@ -28,7 +28,7 @@ class UpdateDroidBootFlow: WizardFlow() { NavButton(vm.activity.getString(R.string.cancel)) { it.finish() }, NavButton("") {} ) { - WizardDownloader(vm) + WizardDownloader(vm, "flash") }, WizardPage("flash", NavButton("") {}, NavButton("") {} @@ -62,7 +62,7 @@ private fun Flash(vm: WizardActivityState) { vm.logic.extractToolkit(terminal) val tmpFile = if (vm.deviceInfo.postInstallScript) { val tmpFile = createTempFileSu("abm", ".sh", vm.logic.rootTmpDir) - vm.copyPriv(vm.chosen["install"]!!.openInputStream(vm), tmpFile) + vm.copyPriv(vm.chosen["_install.sh_"]!!.openInputStream(vm), tmpFile) tmpFile.setExecutable(true) tmpFile } else null diff --git a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt index a14ac912..1a98096e 100644 --- a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt @@ -11,6 +11,7 @@ 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 @@ -52,7 +53,11 @@ class UpdateFlow(private val entryName: String): WizardFlow() { NavButton(vm.activity.getString(R.string.online_update)) { vm.navigate("start") } ) { Local(c) - // TODO add dl + }, WizardPage("dload", + NavButton("") {}, + NavButton("") {} + ) { + WizardDownloader(c.vm, "flash") }, WizardPage("flash", NavButton("") {}, NavButton("") {} @@ -63,16 +68,9 @@ class UpdateFlow(private val entryName: String): WizardFlow() { } private class UpdateFlowDataHolder(val vm: WizardActivityState, val entryFilename: String) { - val client = OkHttpClient().newBuilder().readTimeout(1L, TimeUnit.HOURS).build() var json: JSONObject? = null - var currentDl: Call? = null - var sbootfile = mutableStateListOf() - var e: ConfigFile? = null var ef: File? = null - var hasUpdate = false - val partMapping = HashMap() - var extraParts = ArrayList() var updateJson: String? = null val sparse = ArrayList() } @@ -80,6 +78,7 @@ private class UpdateFlowDataHolder(val vm: WizardActivityState, val entryFilenam @Composable private fun Start(u: UpdateFlowDataHolder) { var hasChecked by remember { mutableStateOf(false) } + var hasUpdate by remember { mutableStateOf(false) } val ioDispatcher = rememberCoroutineScope { Dispatchers.IO } LaunchedEffect(Unit) { ioDispatcher.launch { @@ -97,7 +96,7 @@ private fun Start(u: UpdateFlowDataHolder) { Log.e("ABM", Log.getStackTraceString(e)) } if (u.json != null) { - u.hasUpdate = u.json!!.optBoolean("hasUpdate", false) + hasUpdate = u.json!!.optBoolean("hasUpdate", false) } hasChecked = true } @@ -107,7 +106,7 @@ private fun Start(u: UpdateFlowDataHolder) { if (u.json == null) { Text(stringResource(R.string.update_check_failed)) } else { - if (u.hasUpdate) { + if (hasUpdate) { Text(stringResource(id = R.string.found_update)) Button(onClick = { try { @@ -117,21 +116,30 @@ private fun Start(u: UpdateFlowDataHolder) { val extraIdNeeded = j.getJSONArray("extraIds") var i = 0 while (i < extraIdNeeded.length()) { - extraParts.add(extraIdNeeded.get(i) as String) + vm.inetAvailable["boot$i"] = WizardActivityState.Downloadable( + extraIdNeeded.get(i) as String, + null, + "" + ) + vm.idNeeded.add("boot$i") i++ } - vm.inetAvailable["install"] = WizardActivityState.Downloadable( + vm.inetAvailable["_install.sh_"] = WizardActivityState.Downloadable( j.getString("script"), j.optString("scriptSha256"), vm.activity.getString(R.string.installer_sh) ) - vm.idNeeded.add("install") + vm.idNeeded.add("_install.sh_") } if (j.has("parts")) { - val sp = u.e!!["xpart"]!!.split(":") val p = j.getJSONObject("parts") for (k in p.keys()) { - partMapping[sp[k.toInt()].toInt()] = p.getString(k) + vm.inetAvailable["part$k"] = WizardActivityState.Downloadable( + p.getString(k), + null, + "" + ) + vm.idNeeded.add("part$k") } } updateJson = j.optString("updateJson") @@ -143,7 +151,7 @@ private fun Start(u: UpdateFlowDataHolder) { } } } - u.vm.navigate("flash") + u.vm.navigate("dload") } catch (e: Exception) { Log.e("ABM", u.json?.toString() ?: "(null json)") Log.e("ABM", Log.getStackTraceString(e)) @@ -172,80 +180,26 @@ private fun Local(u: UpdateFlowDataHolder) { Text(stringResource(R.string.local_updater_3)) } Column { - Text(stringResource(R.string.script_name)) - if (u.vm.chosen.containsKey("install")) { - Button(onClick = { - u.vm.chosen.remove("install") - }) { - Text(stringResource(id = R.string.undo)) + Text(stringResource(R.string.how_many_extras)) + var i by remember { mutableIntStateOf(0) } + Row { + Button({ i++ }) { + Text("+") } - } else { - Button(onClick = { - u.vm.activity.chooseFile("*/*") { - u.vm.chosen["install"] = DledFile(it, null) - } - }) { - Text(stringResource(id = R.string.choose_file)) + Button({ i-- }) { + Text("-") } - } - for (i in u.sbootfile) { - Row { - Text(stringResource(R.string.file_selected) + " " + (i.lastPathSegment ?: "(null)")) - Button(onClick = { u.sbootfile.remove(i) }) { - Text(stringResource(id = R.string.undo)) + Button({ + u.vm.idNeeded.add("_install.sh_") + for (j in 0..i) { + u.vm.idNeeded.add("boot$j") } - } - } - if (u.sbootfile.isNotEmpty()) { - Button(onClick = { - if (u.vm.chosen.containsKey("install")) { - u.hasUpdate = false - u.vm.navigate("flash") - } - }, enabled = u.vm.chosen.containsKey("install")) { + u.vm.navigate("dload") + }) { Text(stringResource(R.string.install_update)) } } - Button(onClick = { u.vm.activity.chooseFile("*/*") { - u.sbootfile.add(it) - } }) { - Text(stringResource(id = R.string.choose_file)) - } - } - } -} - -private fun dlFile(u: UpdateFlowDataHolder, l: String): File? { - val downloadedFile = File(u.vm.logic.cacheDir, System.currentTimeMillis().toString()) - try { - val request = - Request.Builder().url(l).build() - u.currentDl = u.client.newCall(request) - val response = u.currentDl!!.execute() - - val sink = downloadedFile.sink().buffer() - try { - sink.writeAll(response.body!!.source()) - } catch (e: StreamResetException) { - if (e.message == "stream was reset: CANCEL") - throw ActionAbortedCleanlyError(Exception(u.vm.activity.getString(R.string.install_canceled))) - else - throw e } - sink.close() - - if (u.currentDl!!.isCanceled()) - throw ActionAbortedCleanlyError(Exception(u.vm.activity.getString(R.string.install_canceled))) - - u.currentDl = null - return downloadedFile - } catch (e: ActionAbortedCleanlyError) { - throw e - } catch (e: Exception) { - Log.e("ABM", Log.getStackTraceString(e)) - downloadedFile.delete() - u.currentDl = null - return null } } @@ -256,122 +210,52 @@ private fun Flash(u: UpdateFlowDataHolder) { val sp = u.e!!["xpart"]!!.split(":") val meta = SDUtils.generateMeta(u.vm.deviceInfo)!! Shell.cmd(SDUtils.umsd(meta)).exec() - - if (u.hasUpdate) { // online - u.vm.nextText = u.vm.activity.getString(R.string.cancel) - u.vm.onNext = { u.currentDl?.cancel() } - try { - val bootfile = ArrayList() - if (u.extraParts.isNotEmpty()) { - for (boot in u.extraParts) { - terminal.add(u.vm.activity.getString(R.string.term_dl_updated_bi)) - var bootf = dlFile(u, boot) - if (bootf == null) { - terminal.add(u.vm.activity.getString(R.string.term_dl_fail1)) - bootf = dlFile(u, boot) - if (bootf == null) { - terminal.add(u.vm.activity.getString(R.string.term_dl_fail2)) - throw ActionAbortedCleanlyError(Exception(u.vm.activity.getString(R.string.install_canceled))) - } - } - bootfile.add(bootf) - } - } - val pmap = HashMap() - for (p in u.partMapping.entries) { - terminal.add(u.vm.activity.getString(R.string.term_dling_part_image)) - var f = dlFile(u, p.value) - if (f == null) { - terminal.add(u.vm.activity.getString(R.string.term_dl_fail1)) - f = dlFile(u, p.value) - if (f == null) { - terminal.add(u.vm.activity.getString(R.string.term_dl_fail2)) - bootfile.forEach { it.delete() } - pmap.values.forEach { it.delete() } - throw ActionAbortedCleanlyError(Exception(u.vm.activity.getString(R.string.install_canceled))) - } - } - pmap[p.key] = f - } - val tmpFile = createTempFileSu("abm", ".sh", u.vm.logic.rootTmpDir) - u.vm.copyPriv(u.vm.chosen["install"]!!.openInputStream(u.vm), tmpFile) - tmpFile.setExecutable(true) - u.vm.nextText = "" - u.vm.onNext = {} - - for (p in u.partMapping.entries) { - val v = sp.find { p.key.toString() == it } - terminal.add(u.vm.activity.getString(R.string.term_flashing_p, v)) - val f2 = pmap[p.key]!! - val tp = File(meta.dumpKernelPartition(p.key).path) - if (u.sparse.contains(p.key)) { - val result2 = Shell.cmd( - File( - u.vm.logic.toolkitDir, - "simg2img" - ).absolutePath + " ${f2.absolutePath} ${tp.absolutePath}" - ).to(terminal).exec() - if (!result2.isSuccess) { - throw IllegalStateException(u.vm.activity.getString(R.string.term_simg2img_fail)) - } - } else { - u.vm.copyPriv(f2.inputStream(), tp) - } - terminal.add(u.vm.activity.getString(R.string.term_done)) - } - if (u.extraParts.isNotEmpty()) { - terminal.add(u.vm.activity.getString(R.string.term_patch_update)) - var cmd = "FORMATDATA=false " + tmpFile.absolutePath + " ${u.ef!!.nameWithoutExtension}" - for (i in bootfile) { - cmd += " " + i.absolutePath - } - for (i in sp) { - cmd += " $i" - } - val r = u.vm.logic.runShFileWithArgs(cmd).to(terminal).exec() - tmpFile.delete() - bootfile.forEach { it.delete() } - if (!r.isSuccess) { - throw IllegalStateException(u.vm.activity.getString(R.string.term_script_fail)) - } + 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) + it.setExecutable(true) + } + } else null + for (p in u.vm.idNeeded.filter { it.startsWith("part") }.map { it.substring(4) }) { + val physicalId = sp[p.toInt()].toInt() + terminal.add(u.vm.activity.getString(R.string.term_flashing_p, p)) + val f2 = u.vm.chosen["part$p"]!! + val tp = File(meta.dumpKernelPartition(physicalId).path) + if (u.sparse.contains(p.toInt())) { + val result2 = Shell.cmd( + File( + u.vm.logic.toolkitDir, + "simg2img" + ).absolutePath + " ${f2.toFile(u.vm)} ${tp.absolutePath}" + ).to(terminal).exec() + if (!result2.isSuccess) { + throw IllegalStateException(u.vm.activity.getString(R.string.term_simg2img_fail)) } - u.e!!["xupdate"] = u.updateJson ?: "" - u.e!!.exportToFile(u.ef!!) - terminal.add(u.vm.activity.getString(R.string.term_success)) - } catch (e: ActionAbortedCleanlyError) { - terminal.add("-- " + e.message) + } else { + u.vm.copyPriv(f2.openInputStream(u.vm), tp) } - } else if (u.sbootfile.isNotEmpty()) { - val bootfile = ArrayList() - val tmpFile = createTempFileSu("abm", ".sh", u.vm.logic.rootTmpDir) - u.vm.copyPriv(u.vm.chosen["flashes"]!!.openInputStream(u.vm), tmpFile) - tmpFile.setExecutable(true) + terminal.add(u.vm.activity.getString(R.string.term_done)) + } + val bootFiles = u.vm.idNeeded.filter { it.startsWith("boot") } + if (bootFiles.isNotEmpty()) { terminal.add(u.vm.activity.getString(R.string.term_patch_update)) - u.sbootfile.forEach { - val bootf = File(u.vm.logic.cacheDir, System.currentTimeMillis().toString()) - u.vm.copyUnpriv( - u.vm.activity.contentResolver.openInputStream(it)!!, - bootf - ) - bootfile.add(bootf) - } - var cmd = "FORMATDATA=false " + tmpFile.absolutePath + " ${u.ef!!.nameWithoutExtension}" - for (i in bootfile) { - cmd += " " + i.absolutePath + var cmd = + "FORMATDATA=false " + tmpFile!!.absolutePath + " ${u.ef!!.nameWithoutExtension}" + for (i in bootFiles) { + cmd += " " + u.vm.chosen[i]!!.toFile(u.vm) } for (i in sp) { cmd += " $i" } val r = u.vm.logic.runShFileWithArgs(cmd).to(terminal).exec() - tmpFile.delete() - bootfile.forEach { it.delete() } if (!r.isSuccess) { throw IllegalStateException(u.vm.activity.getString(R.string.term_script_fail)) } - terminal.add(u.vm.activity.getString(R.string.term_success)) - } else { - terminal.add(u.vm.activity.getString(R.string.term_update_failed_prep)) } + u.e!!["xupdate"] = u.updateJson ?: "" + u.e!!.exportToFile(u.ef!!) + terminal.add(u.vm.activity.getString(R.string.term_success)) + tmpFile?.delete() u.vm.nextText = u.vm.activity.getString(R.string.finish) u.vm.onNext = { it.finish() } } diff --git a/app/src/main/java/org/andbootmgr/app/WizardActivity.kt b/app/src/main/java/org/andbootmgr/app/WizardActivity.kt index 51b01c12..e19cc769 100644 --- a/app/src/main/java/org/andbootmgr/app/WizardActivity.kt +++ b/app/src/main/java/org/andbootmgr/app/WizardActivity.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.core.net.toFile import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -47,14 +46,10 @@ import org.andbootmgr.app.util.AbmOkHttp import org.andbootmgr.app.util.SOUtils import java.io.File import java.io.FileInputStream -import java.io.IOException import java.io.InputStream import java.io.OutputStream -import java.net.URL import java.nio.file.Files import java.nio.file.StandardCopyOption -import java.security.DigestInputStream -import java.security.MessageDigest abstract class WizardFlow { abstract fun get(vm: WizardActivityState): List @@ -96,17 +91,6 @@ fun WizardCompat(mvm: MainActivityState, flow: WizardFlow) { } } -private class ExpectedDigestInputStream(stream: InputStream?, - digest: MessageDigest?, - private val expectedDigest: String -) : DigestInputStream(stream, digest) { - @OptIn(ExperimentalStdlibApi::class) - fun doAssert() { - val hash = digest.digest().toHexString() - if (hash != expectedDigest) - throw HashMismatchException("digest $hash does not match expected hash $expectedDigest") - } -} class HashMismatchException(message: String) : Exception(message) class WizardActivityState(val mvm: MainActivityState) { @@ -120,13 +104,42 @@ class WizardActivityState(val mvm: MainActivityState) { var onPrev by mutableStateOf<((WizardActivityState) -> Unit)?>(null) var onNext by mutableStateOf<((WizardActivityState) -> Unit)?>(null) - // TODO remove flashes - var flashes: HashMap> = HashMap() - var texts by mutableStateOf("") - val inetAvailable = HashMap() + val inetAvailable = mutableStateMapOf() val idNeeded = mutableStateListOf() - val chosen = mutableStateMapOf() + val chosen = mutableStateMapOf() class Downloadable(val url: String, val hash: String?, val desc: String) + class DownloadedFile(private val safFile: Uri?, private val netFile: File?) { + fun delete() { + netFile?.delete() + } + + fun openInputStream(vm: WizardActivityState): InputStream { + netFile?.let { + return FileInputStream(it) + } + safFile?.let { + val istr = vm.activity.contentResolver.openInputStream(it) + if (istr != null) { + return istr + } + } + throw IllegalStateException("invalid DledFile OR failure") + } + + fun toFile(vm: WizardActivityState): File { + netFile?.let { return it } + safFile?.let { + val istr = vm.activity.contentResolver.openInputStream(it) + if (istr != null) { + val f = File(vm.logic.cacheDir, System.currentTimeMillis().toString()) + vm.copyUnpriv(istr, f) + istr.close() + return f + } + } + throw IllegalStateException("invalid DledFile OR safFile failure") + } + } fun navigate(next: String) { prevText = null @@ -153,29 +166,9 @@ class WizardActivityState(val mvm: MainActivityState) { inputStream.close() outputStream.flush() outputStream.close() - if (inputStream is ExpectedDigestInputStream) - inputStream.doAssert() return nread } - fun flashStream(flashType: String): InputStream { - return flashes[flashType]?.let { - val i = when (it.first.scheme) { - "content" -> - activity.contentResolver.openInputStream(it.first) - ?: throw IOException("in == null") - "file" -> - FileInputStream(it.first.toFile()) - "http", "https" -> - URL(it.first.toString()).openStream() - else -> null - } - if (it.second != null) - ExpectedDigestInputStream(i, MessageDigest.getInstance("SHA-256"), it.second!!) - else i - } ?: throw IllegalArgumentException() - } - fun copyUnpriv(inputStream: InputStream, output: File) { Files.copy(inputStream, output.toPath(), StandardCopyOption.REPLACE_EXISTING) inputStream.close() @@ -187,39 +180,6 @@ class WizardActivityState(val mvm: MainActivityState) { } } -class DledFile(val safFile: Uri?, val netFile: File?) { - fun delete() { - netFile?.delete() - } - - fun openInputStream(vm: WizardActivityState): InputStream { - netFile?.let { - return FileInputStream(it) - } - safFile?.let { - val istr = vm.activity.contentResolver.openInputStream(it) - if (istr != null) { - return istr - } - } - throw IllegalStateException("invalid DledFile OR failure") - } - - fun toFile(vm: WizardActivityState): File { - netFile?.let { return it } - safFile?.let { - val istr = vm.activity.contentResolver.openInputStream(it) - if (istr != null) { - val f = File(vm.logic.cacheDir, System.currentTimeMillis().toString()) - vm.copyUnpriv(istr, f) - istr.close() - return f - } - } - throw IllegalStateException("invalid DledFile OR safFile failure") - } -} - class NavButton(val text: String, val onClick: (WizardActivityState) -> Unit) class WizardPage(override val name: String, override val prev: NavButton, @@ -251,7 +211,7 @@ fun BasicButtonRow(prev: String, onPrev: () -> Unit, } @Composable -fun WizardDownloader(vm: WizardActivityState) { +fun WizardDownloader(vm: WizardActivityState, next: String) { Column(Modifier.fillMaxSize()) { Card { Row( @@ -317,7 +277,7 @@ fun WizardDownloader(vm: WizardActivityState) { cancelDownload = null } if (client.run()) { - vm.chosen[i] = DledFile(null, downloadedFile) + vm.chosen[i] = WizardActivityState.DownloadedFile(null, downloadedFile) } } catch (e: Exception) { Log.e("ABM", Log.getStackTraceString(e)) @@ -337,7 +297,7 @@ fun WizardDownloader(vm: WizardActivityState) { } Button(onClick = { vm.activity.chooseFile("*/*") { - vm.chosen[i] = DledFile(it, null) + vm.chosen[i] = WizardActivityState.DownloadedFile(it, null) } }) { Text(stringResource(R.string.choose)) @@ -349,7 +309,7 @@ fun WizardDownloader(vm: WizardActivityState) { val isOk = vm.idNeeded.find { !vm.chosen.containsKey(it) } == null LaunchedEffect(isOk) { if (isOk) { - vm.onNext = { it.navigate("flash") } + vm.onNext = { it.navigate(next) } vm.nextText = vm.activity.getString(R.string.install) } else { vm.onNext = {} diff --git a/app/src/main/java/org/andbootmgr/app/util/AbmOkHttp.kt b/app/src/main/java/org/andbootmgr/app/util/AbmOkHttp.kt index dba0ebb6..db42e39d 100644 --- a/app/src/main/java/org/andbootmgr/app/util/AbmOkHttp.kt +++ b/app/src/main/java/org/andbootmgr/app/util/AbmOkHttp.kt @@ -8,6 +8,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.ResponseBody +import okhttp3.internal.http2.StreamResetException import okio.Buffer import okio.BufferedSource import okio.ForwardingSource @@ -45,10 +46,18 @@ class AbmOkHttp(private val url: String, private val f: File, private val hash: val rawSink = f.sink() val sink = if (hash != null) HashingSink.sha256(rawSink) else rawSink val buffer = sink.buffer() - buffer.writeAll(response.body!!.source()) + try { + buffer.writeAll(response.body!!.source()) + } catch (e: StreamResetException) { + if (e.message != "stream was reset: CANCEL") + throw e + buffer.close() + f.delete() + return@withContext false + } buffer.close() - val realHash = if (hash != null) (sink as HashingSink).hash.hex() else null if (!call.isCanceled()) { + val realHash = if (hash != null) (sink as HashingSink).hash.hex() else null if (hash != null && realHash != hash) { try { f.delete() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e8fa987..78afeb52 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -260,4 +260,5 @@ Restoring backup… bytes percent + How many extra IDs?