diff --git a/.gitignore b/.gitignore index 1e118ccd..f0378a26 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ build app/build app/.cxx .gradle/* +.kotlin/* local.properties # User-specific stuff .idea diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ddc9c642..97b83a97 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.plugin.compose") + id("org.jetbrains.kotlinx.atomicfu") id("com.mikepenz.aboutlibraries.plugin") } @@ -110,6 +111,11 @@ android { } } +atomicfu { + dependenciesVersion = "0.25.0" + jvmVariant = "FU" +} + tasks.register("setAssetTs", Task::class) { doLast { File("$rootDir/app/src/main/assets/cp/_ts").writeText((System.currentTimeMillis() / 1000L).toString()) @@ -117,6 +123,7 @@ tasks.register("setAssetTs", Task::class) { } dependencies { + compileOnly("org.jetbrains.kotlinx:atomicfu:0.25.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") diff --git a/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt b/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt index b9fd7b2e..7fb62111 100644 --- a/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt @@ -123,6 +123,7 @@ private fun SelectDroidBoot(c: CreateBackupDataHolder) { @Composable 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) @@ -143,8 +144,8 @@ private fun Flash(c: CreateBackupDataHolder) { c.vm.copyUnpriv(c.vm.activity.contentResolver.openInputStream(c.path!!)!!, f) val result2 = Shell.cmd( File( - c.vm.logic.assetDir, - "Toolkit/simg2img" + c.vm.logic.toolkitDir, + "simg2img" ).absolutePath + " ${f.absolutePath} ${p.path}" ).to(terminal).exec() if (!result2.isSuccess) { diff --git a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt index d4c983f1..4a54804e 100644 --- a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt @@ -914,6 +914,7 @@ private fun Download(c: CreatePartDataHolder) { private fun Flash(c: CreatePartDataHolder) { val vm = c.vm Terminal(logFile = "install_${System.currentTimeMillis()}.txt") { terminal -> + c.vm.logic.extractToolkit(terminal) if (c.t == null) { // OS install val parts = ArrayMap() val fn = c.t2.value @@ -962,8 +963,8 @@ private fun Flash(c: CreatePartDataHolder) { val f2 = f.toFile(c.vm) val result2 = Shell.cmd( File( - c.vm.logic.assetDir, - "Toolkit/simg2img" + c.vm.logic.toolkitDir, + "simg2img" ).absolutePath + " ${f2.absolutePath} ${tp.absolutePath}" ).to(terminal).exec() f.delete() diff --git a/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt b/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt index 303cea7d..cdae2eff 100644 --- a/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt +++ b/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt @@ -5,14 +5,15 @@ import android.util.Log import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.io.SuFile import org.andbootmgr.app.util.SDUtils +import org.andbootmgr.app.util.Toolkit import java.io.File -class DeviceLogic(ctx: Context) { +class DeviceLogic(private val ctx: Context) { private val rootDir = ctx.filesDir.parentFile!! - val assetDir = File(rootDir, "assets") - private val toolkitDir = File(assetDir, "Toolkit") + private val toolkit = Toolkit(ctx) val fileDir = File(rootDir, "files") val cacheDir = File(rootDir, "cache") + val toolkitDir = File(toolkit.targetPath, "Toolkit") // will occasionally be pruned by OS, but it's fine val rootTmpDir = File("/data/local/tmp") val abmBootset = File(rootTmpDir, ".abm_bootset") val abmDb = File(abmBootset, "db") @@ -86,4 +87,14 @@ class DeviceLogic(ctx: Context) { "TMPDIR=\"${cacheDir.absolutePath}\" BOOTSET=\"${abmBootset.absolutePath}\" " + "TK=\"${toolkitDir.absolutePath}\" && cd \"\$TK\" && $cmd") } + suspend fun extractToolkit(terminal: MutableList) { + try { + toolkit.copyAssets { + terminal.add(ctx.getString(R.string.toolkit_extracting)) + } + } catch (e: Exception) { + terminal.add(ctx.getString(R.string.toolkit_error)) + throw e + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt index 7a96e77c..84b6db98 100644 --- a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt @@ -229,6 +229,7 @@ fun SelectInstallSh(vm: WizardActivityState, update: Boolean = false) { private fun Flash(vm: WizardActivityState) { val flashType = "DroidBootFlashType" Terminal(logFile = "blflash_${System.currentTimeMillis()}.txt") { terminal -> + vm.logic.extractToolkit(terminal) terminal.add(vm.activity.getString(R.string.term_preparing_fs)) if (vm.logic.checkMounted()) { terminal.add(vm.activity.getString(R.string.term_mount_state_bad)) diff --git a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt index e4a49897..9233be95 100644 --- a/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt @@ -59,6 +59,7 @@ private fun Start() { @Composable private fun Flash(vm: WizardActivityState) { 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.flashStream("InstallShFlashType"), tmpFile) diff --git a/app/src/main/java/org/andbootmgr/app/MainActivity.kt b/app/src/main/java/org/andbootmgr/app/MainActivity.kt index 945bdf85..f3160258 100644 --- a/app/src/main/java/org/andbootmgr/app/MainActivity.kt +++ b/app/src/main/java/org/andbootmgr/app/MainActivity.kt @@ -31,11 +31,13 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell.FLAG_MOUNT_MASTER import com.topjohnwu.superuser.Shell.FLAG_REDIRECT_STDERR import com.topjohnwu.superuser.io.SuFile +import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -51,13 +53,13 @@ import org.andbootmgr.app.util.Terminal import org.andbootmgr.app.util.Toolkit import java.io.File -class MainActivityState { +class MainActivityState(val activity: MainActivity?) { fun startFlow(flow: String) { val i = Intent(activity!!, WizardActivity::class.java) i.putExtra("codename", deviceInfo!!.codename) i.putExtra("flow", flow) - activity!!.startActivity(i) - activity!!.finish() + activity.startActivity(i) + activity.finish() } fun startCreateFlow(freeSpace: SDUtils.Partition.FreeSpace) { @@ -65,8 +67,8 @@ class MainActivityState { i.putExtra("codename", deviceInfo!!.codename) i.putExtra("flow", "create_part") i.putExtra("part_sid", freeSpace.startSector) - activity!!.startActivity(i) - activity!!.finish() + activity.startActivity(i) + activity.finish() } fun startUpdateFlow(e: String) { @@ -74,8 +76,8 @@ class MainActivityState { i.putExtra("codename", deviceInfo!!.codename) i.putExtra("flow", "update") i.putExtra("entryFilename", e) - activity!!.startActivity(i) - activity!!.finish() + activity.startActivity(i) + activity.finish() } fun startBackupAndRestoreFlow(partition: SDUtils.Partition) { @@ -83,17 +85,14 @@ class MainActivityState { i.putExtra("codename", deviceInfo!!.codename) i.putExtra("flow", "backup_restore") i.putExtra("partitionid", partition.id) - activity!!.startActivity(i) - activity!!.finish() + activity.startActivity(i) + activity.finish() } var noobMode by mutableStateOf(false) - var activity: MainActivity? = null var deviceInfo: DeviceInfo? = null - var currentNav by mutableStateOf("start") val theme = ThemeViewModel(this) var defaultCfg = mutableStateMapOf() - var isReady = false var isOk = false var logic: DeviceLogic? = null @@ -123,7 +122,7 @@ class MainActivityState { Log.e("ABM", Log.getStackTraceString(e)) Toast.makeText( activity!!, - activity!!.getString(R.string.failed2save), Toast.LENGTH_LONG + activity.getString(R.string.failed2save), Toast.LENGTH_LONG ).show() } } @@ -149,14 +148,11 @@ class MainActivityState { class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { + val ready = atomic(false) + installSplashScreen().setKeepOnScreenCondition { !ready.value } super.onCreate(savedInstanceState) - val vm = MainActivityState() - installSplashScreen().setKeepOnScreenCondition { !vm.isReady } - vm.activity = this + val vm = MainActivityState(this) vm.logic = DeviceLogic(this) - - val toast = - Toast.makeText(this, getString(R.string.toolkit_extracting), Toast.LENGTH_LONG) CoroutineScope(Dispatchers.IO).launch { if (Shell.getCachedShell() == null) { Shell.enableVerboseLogging = BuildConfig.DEBUG @@ -166,92 +162,53 @@ class MainActivity : ComponentActivity() { .setTimeout(30) ) } - Toolkit(this@MainActivity).copyAssets({ - withContext(Dispatchers.Main) { - toast.show() + launch { + vm.noobMode = + this@MainActivity.getSharedPreferences("abm", 0) + .getBoolean("noob_mode", BuildConfig.DEFAULT_NOOB_MODE) + } + // TODO I/O on app startup is meh, but can we avoid it? + val di = async { JsonDeviceInfoFactory(vm.activity!!).get(Build.DEVICE) } + val shell = Shell.getShell() // blocking + if (shell.isRoot && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Shell.cmd("pm grant $packageName ${android.Manifest.permission.POST_NOTIFICATIONS}") + .submit() + } + // == temp migration code start == + launch { + if (Shell.cmd("mountpoint -q /data/abm/bootset").exec().isSuccess) { + Shell.cmd("umount /data/abm/bootset").exec() } - }) { fail -> - withContext(Dispatchers.Main) { - toast.cancel() - if (fail) { - setContent { - AlertDialog( - onDismissRequest = {}, - title = { - Text(text = getString(R.string.error)) - }, - text = { - Text(getString(R.string.toolkit_error)) - }, - confirmButton = { - Button( - onClick = { - finish() - }) { - Text(getString(R.string.quit)) - } - } - ) - } - vm.isReady = true - } + SuFile.open("/data/abm").let { + if (it.exists()) + Shell.cmd("rm -rf /data/abm").exec() } - if (!fail) { - CoroutineScope(Dispatchers.IO).launch { - launch { - vm.noobMode = - this@MainActivity.getSharedPreferences("abm", 0) - .getBoolean("noob_mode", BuildConfig.DEFAULT_NOOB_MODE) - } - // TODO I/O on app startup is meh, but can we avoid it? - val di = async { JsonDeviceInfoFactory(vm.activity!!).get(Build.DEVICE) } - val shell = Shell.getShell() // blocking - if (shell.isRoot && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - Shell.cmd("pm grant $packageName ${android.Manifest.permission.POST_NOTIFICATIONS}").submit() - } - // == temp migration code start == - launch { - if (Shell.cmd("mountpoint -q /data/abm/bootset").exec().isSuccess) { - Shell.cmd("umount /data/abm/bootset").exec() - } - SuFile.open("/data/abm").let { - if (it.exists()) - Shell.cmd("rm -rf /data/abm").exec() - } - } - // == temp migration code end == - vm.deviceInfo = di.await() // blocking - val installed = vm.deviceInfo?.isInstalled(vm.logic!!) - if (installed == true) { - vm.mountBootset() - } else { - Log.i("ABM", "not installed, not trying to mount") - } - if (vm.deviceInfo != null) { - vm.isOk = installed!! && vm.deviceInfo!!.isBooted(vm.logic!!) && - !(!vm.logic!!.mounted || vm.deviceInfo!!.isCorrupt(vm.logic!!)) - } - withContext(Dispatchers.Main) { - setContent { - var terminalEnabled by remember { mutableStateOf(StayAliveService.isRunning) } - val newAction = remember { mutableStateOf<(suspend (MutableList) -> Unit)?>(null) } - val action = if (newAction.value != null) { - terminalEnabled = true - newAction.value - } else null - if (terminalEnabled) { - Terminal(null, action) - } else { - val navController = rememberNavController() - AppContent(vm, navController) { - NavGraph(vm, navController, it) - } - } - } - vm.isReady = true + } + // == temp migration code end == + vm.deviceInfo = di.await() // blocking + val installed = vm.deviceInfo?.isInstalled(vm.logic!!) + if (installed == true) { + vm.mountBootset() + } else { + Log.i("ABM", "not installed, not trying to mount") + } + if (vm.deviceInfo != null) { + vm.isOk = installed!! && vm.deviceInfo!!.isBooted(vm.logic!!) && + !(!vm.logic!!.mounted || vm.deviceInfo!!.isCorrupt(vm.logic!!)) + } + withContext(Dispatchers.Main) { + setContent { + // TODO allow rotating device while viewing logs without loosing logs + if (remember { StayAliveService.isRunning }) { + Terminal(null, null) + } else { + val navController = rememberNavController() + AppContent(vm, navController) { + NavGraph(vm, navController, it) } } } + ready.value = true } } } @@ -264,8 +221,9 @@ fun AppContent(vm: MainActivityState, navController: NavHostController, val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() var fabhint by remember { mutableStateOf(false) } + val navBackStackEntry by navController.currentBackStackEntryAsState() val fab = @Composable { - if (vm.noobMode && vm.isOk && vm.currentNav == "start") { + if (vm.noobMode && vm.isOk && navBackStackEntry!!.destination.route == "start") { FloatingActionButton(onClick = { fabhint = true }) { Icon(Icons.Default.Add, stringResource(R.string.add_icon_content_desc)) } @@ -274,45 +232,52 @@ fun AppContent(vm: MainActivityState, navController: NavHostController, AbmTheme { // A surface container using the 'background' color from the theme Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { - ModalNavigationDrawer(drawerContent = { - ModalDrawerSheet { - NavigationDrawerItem( - label = { Text(stringResource(R.string.home)) }, - selected = vm.currentNav == "start", - onClick = { - scope.launch { - navController.navigate("start") - drawerState.close() - } - }, - modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp) - ) - if (vm.isOk) { + ModalNavigationDrawer( + drawerContent = { + ModalDrawerSheet { NavigationDrawerItem( - label = { Text(stringResource(R.string.themes)) }, - selected = vm.currentNav == "themes", + label = { Text(stringResource(R.string.home)) }, + selected = navBackStackEntry!!.destination.route == "start", onClick = { scope.launch { - navController.navigate("themes") - drawerState.close() - } - }, - modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp) - ) - NavigationDrawerItem( - label = { Text(stringResource(R.string.settings)) }, - selected = vm.currentNav == "settings", - onClick = { - scope.launch { - navController.navigate("settings") + navController.navigate("start") { + launchSingleTop = true + } drawerState.close() } }, modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp) ) + if (vm.isOk) { + NavigationDrawerItem( + label = { Text(stringResource(R.string.themes)) }, + selected = navBackStackEntry!!.destination.route == "themes", + onClick = { + scope.launch { + navController.navigate("themes") { + launchSingleTop = true + } + drawerState.close() + } + }, + modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp) + ) + NavigationDrawerItem( + label = { Text(stringResource(R.string.settings)) }, + selected = navBackStackEntry!!.destination.route == "settings", + onClick = { + scope.launch { + navController.navigate("settings") { + launchSingleTop = true + } + drawerState.close() + } + }, + modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp) + ) + } } - } - }, + }, drawerState = drawerState, content = { if (fabhint) { @@ -324,20 +289,28 @@ fun AppContent(vm: MainActivityState, navController: NavHostController, } }) } - Scaffold(topBar = { - CenterAlignedTopAppBar(title = { - Text(stringResource(R.string.app_name)) - }, colors = TopAppBarDefaults.centerAlignedTopAppBarColors(), navigationIcon = { - IconButton(content = { - Icon( - imageVector = Icons.Filled.Menu, - contentDescription = stringResource(R.string.menu) - ) - }, onClick = { - scope.launch { drawerState.open() } - }) - }) - }, content = view, floatingActionButton = fab, modifier = Modifier.fillMaxWidth()) + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { + Text(stringResource(R.string.app_name)) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors(), + navigationIcon = { + IconButton(content = { + Icon( + imageVector = Icons.Filled.Menu, + contentDescription = stringResource(R.string.menu) + ) + }, onClick = { + scope.launch { drawerState.open() } + }) + }) + }, + content = view, + floatingActionButton = fab, + modifier = Modifier.fillMaxWidth() + ) } ) } @@ -348,15 +321,12 @@ fun AppContent(vm: MainActivityState, navController: NavHostController, private fun NavGraph(vm: MainActivityState, navController: NavHostController, it: PaddingValues) { NavHost(navController = navController, startDestination = "start", modifier = Modifier.padding(it).fillMaxSize()) { composable("start") { - vm.currentNav = "start" Start(vm) } composable("themes") { - vm.currentNav = "themes" Themes(vm.theme) } composable("settings") { - vm.currentNav = "settings" Settings(vm) } } @@ -1145,7 +1115,7 @@ private fun PartTool(vm: MainActivityState) { @Preview(showBackground = true) @Composable private fun Preview() { - val vm = MainActivityState() + val vm = MainActivityState(null) AppContent(vm, rememberNavController()) { Box(modifier = Modifier.padding(it)) { Start(vm) diff --git a/app/src/main/java/org/andbootmgr/app/Settings.kt b/app/src/main/java/org/andbootmgr/app/Settings.kt index 65df739f..e53bd297 100644 --- a/app/src/main/java/org/andbootmgr/app/Settings.kt +++ b/app/src/main/java/org/andbootmgr/app/Settings.kt @@ -77,7 +77,7 @@ fun Settings(vm: MainActivityState) { ) Button(onClick = { if (defaultErr || timeoutErr) - Toast.makeText(vm.activity!!, vm.activity!!.getString(R.string.invalid_in), Toast.LENGTH_LONG).show() + Toast.makeText(vm.activity!!, vm.activity.getString(R.string.invalid_in), Toast.LENGTH_LONG).show() else CoroutineScope(Dispatchers.Main).launch { vm.editDefaultCfg(changes) changes.clear() diff --git a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt index 72f776df..041f8688 100644 --- a/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt @@ -57,6 +57,7 @@ private fun Start() { @Composable private fun Flash(vm: WizardActivityState) { 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.flashStream("InstallShFlashType"), tmpFile) diff --git a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt index 8272106f..0eaa9412 100644 --- a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt @@ -248,6 +248,7 @@ private fun dlFile(u: UpdateFlowDataHolder, l: String): File? { @Composable private fun Flash(u: UpdateFlowDataHolder) { Terminal(logFile = "update_${System.currentTimeMillis()}.txt") { terminal -> + u.vm.logic.extractToolkit(terminal) val sp = u.e!!["xpart"]!!.split(":") val meta = SDUtils.generateMeta(u.vm.deviceInfo)!! Shell.cmd(SDUtils.umsd(meta)).exec() @@ -303,8 +304,8 @@ private fun Flash(u: UpdateFlowDataHolder) { if (u.sparse.contains(p.key)) { val result2 = Shell.cmd( File( - u.vm.logic.assetDir, - "Toolkit/simg2img" + u.vm.logic.toolkitDir, + "simg2img" ).absolutePath + " ${f2.absolutePath} ${tp.absolutePath}" ).to(terminal).exec() if (!result2.isSuccess) { diff --git a/app/src/main/java/org/andbootmgr/app/themes/Themes.kt b/app/src/main/java/org/andbootmgr/app/themes/Themes.kt index 205051a1..65afef35 100644 --- a/app/src/main/java/org/andbootmgr/app/themes/Themes.kt +++ b/app/src/main/java/org/andbootmgr/app/themes/Themes.kt @@ -19,18 +19,15 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.Checkbox -import androidx.compose.material3.DrawerValue import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text -import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -42,7 +39,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PlatformImeOptions import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -103,7 +99,7 @@ fun Themes(vm: ThemeViewModel) { if (errors.find { it.value } != null) Toast.makeText( vm.mvm.activity!!, - vm.mvm.activity!!.getString(R.string.invalid_in), + vm.mvm.activity.getString(R.string.invalid_in), Toast.LENGTH_LONG ).show() else CoroutineScope(Dispatchers.Main).launch { @@ -120,7 +116,7 @@ fun Themes(vm: ThemeViewModel) { if (errors.find { it.value } != null) Toast.makeText( vm.mvm.activity!!, - vm.mvm.activity!!.getString(R.string.invalid_in), + vm.mvm.activity.getString(R.string.invalid_in), Toast.LENGTH_LONG ).show() else CoroutineScope(Dispatchers.Main).launch { @@ -130,7 +126,7 @@ fun Themes(vm: ThemeViewModel) { vm.mvm.remountBootset() vm.mvm.activity!!.startActivity( Intent( - vm.mvm.activity!!, + vm.mvm.activity, Simulator::class.java ).apply { putExtra("sdCardBlock", vm.mvm.deviceInfo!!.bdev) @@ -326,7 +322,7 @@ class ThemeViewModel(val mvm: MainActivityState) : ViewModel() { @Preview @Composable fun ThemePreview() { - val vm = MainActivityState() + val vm = MainActivityState(null) AppContent(vm, rememberNavController()) { Box(modifier = Modifier.padding(it)) { Themes(vm.theme) diff --git a/app/src/main/java/org/andbootmgr/app/util/Toolkit.kt b/app/src/main/java/org/andbootmgr/app/util/Toolkit.kt index 0e62468b..70197a84 100644 --- a/app/src/main/java/org/andbootmgr/app/util/Toolkit.kt +++ b/app/src/main/java/org/andbootmgr/app/util/Toolkit.kt @@ -1,7 +1,6 @@ package org.andbootmgr.app.util import android.content.Context -import android.content.res.AssetManager import android.util.Log import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell.FLAG_NON_ROOT_SHELL @@ -16,20 +15,14 @@ class Toolkit(private val ctx: Context) { private const val DEBUG = false } - private var fail = false - private val targetPath = File(ctx.filesDir.parentFile, "assets") + val targetPath = File(ctx.cacheDir, "tools") - suspend fun copyAssets(uinf: suspend () -> Unit, callback: suspend (Boolean) -> Unit) { - val shell = Shell.Builder.create().setFlags(FLAG_NON_ROOT_SHELL).setTimeout(30).setContext(ctx).build() - var b = try { - withContext(Dispatchers.IO) { - ctx.assets.open("cp/_ts").use { it.readBytes() } - } - } catch (e: IOException) { - e.printStackTrace() - fail = true - callback(true) - return + suspend fun copyAssets(extractingNotification: suspend () -> Unit) { + val shell = withContext(Dispatchers.IO) { + Shell.Builder.create().setFlags(FLAG_NON_ROOT_SHELL).setTimeout(90).setContext(ctx).build() + } + var b = withContext(Dispatchers.IO) { + ctx.assets.open("cp/_ts").use { it.readBytes() } } val s = String(b).trim() b = try { @@ -41,68 +34,57 @@ class Toolkit(private val ctx: Context) { } val s2 = String(b).trim() if (s != s2) { - uinf.invoke() - shell.newJob().add("rm -rf " + targetPath.absolutePath).exec() - if (!targetPath.exists()) fail = fail or !targetPath.mkdir() - if (!File(ctx.filesDir.parentFile, "files").exists()) fail = fail or !File(ctx.filesDir.parentFile, "files").mkdir() - if (!File(ctx.filesDir.parentFile, "cache").exists()) fail = fail or !File(ctx.filesDir.parentFile, "cache").mkdir() - copyAssets("Toolkit", "Toolkit") - copyAssets("cp", "") + extractingNotification.invoke() + withContext(Dispatchers.IO) { + targetPath.deleteRecursively() + if (!ctx.filesDir.exists() && !ctx.filesDir.mkdir()) + throw IOException("mkdir failed ${ctx.filesDir}") + if (!targetPath.exists() && !targetPath.mkdir()) + throw IOException("mkdir failed $targetPath") + if (!ctx.cacheDir.exists() && !ctx.cacheDir.mkdir()) + throw IOException("mkdir failed ${ctx.cacheDir}") + copyAssets("Toolkit", "Toolkit") + copyAssets("cp", "") + } + } + withContext(Dispatchers.IO) { + shell.newJob().add("chmod -R +x " + targetPath.absolutePath).exec() } - shell.newJob().add("chmod -R +x " + targetPath.absolutePath).exec() - callback(fail) } private fun copyAssets(src: String, outp: String) { - val assetManager: AssetManager = ctx.assets - var files: Array? = null - try { - files = assetManager.list(src) - } catch (e: IOException) { - Log.e(TAG, "Failed to get asset file list.", e) - fail = true - } - assert(files != null) - for (filename in files!!) { - copyAssets(src, outp, assetManager, filename) + for (filename in ctx.assets.list(src)!!) { + copyAssets(src, outp, filename) } } private fun copyAssets( src: String, - outp: String, - assetManager: AssetManager, + outPath: String, filename: String ) { val `in`: InputStream val out: OutputStream try { - `in` = assetManager.open("$src/$filename") - val outFile = File(File(targetPath, outp), filename) + `in` = ctx.assets.open("$src/$filename") + val outFile = File(File(targetPath, outPath), filename) out = FileOutputStream(outFile) copyFile(`in`, out) `in`.close() - out.flush() out.close() } catch (e: FileNotFoundException) { - val r = File(targetPath, outp).mkdir() + val r = File(targetPath, outPath).mkdir() if (DEBUG) Log.d(TAG, "Result of mkdir #1: $r") if (DEBUG) Log.d(TAG, Log.getStackTraceString(e)) try { - assetManager.open(src + File.separator + filename).close() - copyAssets(src, outp, assetManager, filename) + ctx.assets.open(src + File.separator + filename).close() + copyAssets(src, outPath, filename) } catch (e2: FileNotFoundException) { - val r2 = File(File(targetPath, outp), filename).mkdir() + val r2 = File(File(targetPath, outPath), filename).mkdir() if (DEBUG) Log.d(TAG, "Result of mkdir #2: $r2") if (DEBUG) Log.d(TAG, Log.getStackTraceString(e2)) - copyAssets(src + File.separator + filename, outp + File.separator + filename) - } catch (ex: IOException) { - Log.e(TAG, "Failed to copy asset file: $filename", ex) - fail = true + copyAssets(src + File.separator + filename, outPath + File.separator + filename) } - } catch (e: IOException) { - Log.e(TAG, "Failed to copy asset file: $filename", e) - fail = true } } diff --git a/build.gradle.kts b/build.gradle.kts index 10a0edae..b9d4444e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,5 +4,6 @@ plugins { val kotlinVersion = "2.0.0" id("org.jetbrains.kotlin.android") version kotlinVersion apply false id("org.jetbrains.kotlin.plugin.compose") version kotlinVersion apply false + id("org.jetbrains.kotlinx.atomicfu") version "0.25.0" apply false id("com.mikepenz.aboutlibraries.plugin") version "11.2.1" apply false } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 009f82e0..790c77d9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,4 +36,6 @@ org.gradle.configureondemand=true # Use R8 in full mode instead of ProGuard compatibility mode. android.enableR8.fullMode=true # Build BuildConfig as Bytecode -android.enableBuildConfigAsBytecode=true \ No newline at end of file +android.enableBuildConfigAsBytecode=true +# https://github.com/Kotlin/kotlinx-atomicfu?tab=readme-ov-file#atomicfu-compiler-plugin +kotlinx.atomicfu.enableJvmIrTransformation=true \ No newline at end of file