diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4a1c4e09..ddc9c642 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -117,7 +117,8 @@ tasks.register("setAssetTs", Task::class) { } dependencies { - implementation("androidx.appcompat:appcompat:1.7.0") + implementation("androidx.core:core-splashscreen:1.0.1") + implementation("androidx.appcompat:appcompat:1.7.0") implementation("androidx.legacy:legacy-support-v4:1.0.0") implementation("com.google.android.material:material:1.12.0") implementation("androidx.cardview:cardview:1.0.0") diff --git a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt index 40a95298..7a96e77c 100644 --- a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt @@ -334,18 +334,22 @@ private fun Flash(vm: WizardActivityState) { entry.exportToFile(File(vm.logic.abmEntries, "real.conf")) if (!vm.deviceInfo.isBooted(vm.logic)) { terminal.add(vm.activity.getString(R.string.term_flashing_droidboot)) + val backupLk = File(vm.logic.fileDir, "backup_lk1.img") val f = SuFile.open(vm.deviceInfo.blBlock) if (!f.canWrite()) terminal.add(vm.activity.getString(R.string.term_cant_write_bl)) - vm.copyPriv( - SuFileInputStream.open(vm.deviceInfo.blBlock), - File(vm.logic.fileDir, "backup_lk1.img") - ) + vm.copyPriv(SuFileInputStream.open(vm.deviceInfo.blBlock), backupLk) try { vm.copyPriv(vm.flashStream(flashType), File(vm.deviceInfo.blBlock)) } catch (e: IOException) { terminal.add(vm.activity.getString(R.string.term_bl_failed)) - terminal.add(if (e.message != null) e.message!! else "(null)") + terminal.add(e.message ?: "(null)") + terminal.add(vm.activity.getString(R.string.term_consult_doc)) + return@Terminal + } catch (e: HashMismatchException) { + terminal.add(e.message ?: "(null)") + terminal.add(vm.activity.getString(R.string.restoring_backup)) + vm.copyPriv(SuFileInputStream.open(backupLk), File(vm.deviceInfo.blBlock)) terminal.add(vm.activity.getString(R.string.term_consult_doc)) return@Terminal } diff --git a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt index c28cdfdf..e4a49897 100644 --- a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt @@ -66,15 +66,22 @@ private fun Flash(vm: WizardActivityState) { tmpFile } else null terminal.add(vm.activity.getString(R.string.term_flashing_droidboot)) + val backupLk = File(vm.logic.fileDir, "backup_lk.img") val f = SuFile.open(vm.deviceInfo.blBlock) if (!f.canWrite()) terminal.add(vm.activity.getString(R.string.term_cant_write_bl)) - vm.copyPriv(SuFileInputStream.open(vm.deviceInfo.blBlock), File(vm.logic.fileDir, "backup_lk.img")) + vm.copyPriv(SuFileInputStream.open(vm.deviceInfo.blBlock), backupLk) try { vm.copyPriv(vm.flashStream("DroidBootFlashType"), File(vm.deviceInfo.blBlock)) } catch (e: IOException) { terminal.add(vm.activity.getString(R.string.term_bl_failed)) - terminal.add(if (e.message != null) e.message!! else "(null)") + terminal.add(e.message ?: "(null)") + terminal.add(vm.activity.getString(R.string.term_consult_doc)) + return@Terminal + } catch (e: HashMismatchException) { + terminal.add(e.message ?: "(null)") + terminal.add(vm.activity.getString(R.string.restoring_backup)) + vm.copyPriv(SuFileInputStream.open(backupLk), File(vm.deviceInfo.blBlock)) terminal.add(vm.activity.getString(R.string.term_consult_doc)) return@Terminal } diff --git a/app/src/main/java/org/andbootmgr/app/MainActivity.kt b/app/src/main/java/org/andbootmgr/app/MainActivity.kt index 579e8a76..b6c9873d 100644 --- a/app/src/main/java/org/andbootmgr/app/MainActivity.kt +++ b/app/src/main/java/org/andbootmgr/app/MainActivity.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -92,7 +93,6 @@ class MainActivityState { val theme = ThemeViewModel(this) var defaultCfg = mutableStateMapOf() var isReady = false - var name by mutableStateOf("") /* default value moved to onCreate() */ var navController: NavHostController? = null var drawerState: DrawerState? = null var scope: CoroutineScope? = null @@ -154,27 +154,10 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val vm = MainActivityState() - vm.name = getString(R.string.android) + installSplashScreen().setKeepOnScreenCondition { !vm.isReady } vm.activity = this vm.logic = DeviceLogic(this) - val content: View = findViewById(android.R.id.content) - content.viewTreeObserver.addOnPreDrawListener( - object : ViewTreeObserver.OnPreDrawListener { - override fun onPreDraw(): Boolean { - // Check if the initial data is ready. - return if (vm.isReady) { - // The content is ready; start drawing. - content.viewTreeObserver.removeOnPreDrawListener(this) - true - } else { - // The content is not ready; suspend. - false - } - } - } - ) - val toast = Toast.makeText(this, getString(R.string.toolkit_extracting), Toast.LENGTH_LONG) CoroutineScope(Dispatchers.IO).launch { diff --git a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt index 2a4628b6..72f776df 100644 --- a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt @@ -64,15 +64,22 @@ private fun Flash(vm: WizardActivityState) { tmpFile } else null terminal.add(vm.activity.getString(R.string.term_flashing_droidboot)) + val backupLk = File(vm.logic.fileDir, "backup2_lk.img") val f = SuFile.open(vm.deviceInfo.blBlock) if (!f.canWrite()) terminal.add(vm.activity.getString(R.string.term_cant_write_bl)) - vm.copyPriv(SuFileInputStream.open(vm.deviceInfo.blBlock), File(vm.logic.fileDir, "backup2_lk.img")) + vm.copyPriv(SuFileInputStream.open(vm.deviceInfo.blBlock), backupLk) try { vm.copyPriv(vm.flashStream("DroidBootFlashType"), File(vm.deviceInfo.blBlock)) } catch (e: IOException) { terminal.add(vm.activity.getString(R.string.term_bl_failed)) - terminal.add(if (e.message != null) e.message!! else "(null)") + terminal.add(e.message ?: "(null)") + terminal.add(vm.activity.getString(R.string.term_consult_doc)) + return@Terminal + } catch (e: HashMismatchException) { + terminal.add(e.message ?: "(null)") + terminal.add(vm.activity.getString(R.string.restoring_backup)) + vm.copyPriv(SuFileInputStream.open(backupLk), File(vm.deviceInfo.blBlock)) terminal.add(vm.activity.getString(R.string.term_consult_doc)) return@Terminal } diff --git a/app/src/main/java/org/andbootmgr/app/WizardActivity.kt b/app/src/main/java/org/andbootmgr/app/WizardActivity.kt index 8acc53d6..475b248f 100644 --- a/app/src/main/java/org/andbootmgr/app/WizardActivity.kt +++ b/app/src/main/java/org/andbootmgr/app/WizardActivity.kt @@ -226,7 +226,6 @@ class WizardActivityState(val codename: String) { navController.navigate(current.value) } - // TODO have callers handle HashMismatchException when appropriate fun copy(inputStream: InputStream, outputStream: OutputStream): Long { var nread = 0L val buf = ByteArray(8192) diff --git a/app/src/main/java/org/andbootmgr/app/util/StayAliveService.kt b/app/src/main/java/org/andbootmgr/app/util/StayAliveService.kt index e300dc95..465bd3ee 100644 --- a/app/src/main/java/org/andbootmgr/app/util/StayAliveService.kt +++ b/app/src/main/java/org/andbootmgr/app/util/StayAliveService.kt @@ -7,6 +7,8 @@ import android.content.Intent import android.content.ServiceConnection import android.os.Binder import android.os.IBinder +import android.os.PowerManager +import android.os.PowerManager.WakeLock import android.util.Log import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationCompat @@ -23,6 +25,7 @@ interface IStayAlive { } class StayAliveService : LifecycleService(), IStayAlive { + private lateinit var wakeLock: WakeLock private var work: (suspend (Context) -> Unit)? = null var isWorkDone = false get() { @@ -44,6 +47,7 @@ class StayAliveService : LifecycleService(), IStayAlive { } private var destroyed = false private var onDone: (() -> Unit)? = null + @SuppressLint("WakelockTimeout") override fun startWork(work: suspend (Context) -> Unit, extra: Any) { if (destroyed) { throw IllegalStateException("This StayAliveService was leaked. It is already destroyed.") @@ -56,9 +60,14 @@ class StayAliveService : LifecycleService(), IStayAlive { this.work = work this.extra = extra lifecycleScope.launch { - this@StayAliveService.work!!.invoke(this@StayAliveService) - isWorkDone = true - onDone!!.invoke() + wakeLock.acquire() + try { + this@StayAliveService.work!!.invoke(this@StayAliveService) + isWorkDone = true + onDone!!.invoke() + } finally { + wakeLock.release() + } } } fun finish() { @@ -89,6 +98,8 @@ class StayAliveService : LifecycleService(), IStayAlive { .setOnlyAlertOnce(true) .setLocalOnly(true) .build()) + wakeLock = getSystemService(PowerManager::class.java) + .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ABM::StayAlive(user_task)") lifecycleScope.launch { delay(10000) // If there was nothing started after 10 seconds, there's a bug. diff --git a/app/src/main/java/org/andbootmgr/app/util/Terminal.kt b/app/src/main/java/org/andbootmgr/app/util/Terminal.kt index 7839340d..74152049 100644 --- a/app/src/main/java/org/andbootmgr/app/util/Terminal.kt +++ b/app/src/main/java/org/andbootmgr/app/util/Terminal.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -24,7 +25,8 @@ import org.andbootmgr.app.R import java.io.File import java.io.FileOutputStream -private class BudgetCallbackList(private val log: FileOutputStream?) : MutableList { +private class BudgetCallbackList(private val scope: CoroutineScope, + private val log: FileOutputStream?) : MutableList { val internalList = ArrayList() var cb: ((String) -> Unit)? = null override val size: Int @@ -119,7 +121,9 @@ private class BudgetCallbackList(private val log: FileOutputStream?) : MutableLi } fun onAdd(element: String) { - log?.write((element + "\n").encodeToByteArray()) + scope.launch { + log?.write((element + "\n").encodeToByteArray()) + } cb?.invoke(element) } } @@ -139,7 +143,7 @@ fun Terminal(logFile: String? = null, action: (suspend (MutableList) -> StayAliveConnection(ctx) { service -> if (action != null) { val log = logFile?.let { FileOutputStream(File(ctx.externalCacheDir, it)) } - val s = BudgetCallbackList(log) + val s = BudgetCallbackList(CoroutineScope(Dispatchers.IO), log) s.cb = { element -> scope.launch { text.value += element + "\n" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bbe90d5b..34cdccd9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -259,4 +259,5 @@ Service notifications ABM is processing Please make sure you do not leave the app to avoid issues + Restoring backup…