@@ -24,6 +24,7 @@ import androidx.compose.material3.Icon
2424import androidx.compose.material3.MaterialTheme
2525import androidx.compose.material3.RadioButton
2626import androidx.compose.material3.RangeSlider
27+ import androidx.compose.material3.Slider
2728import androidx.compose.material3.Text
2829import androidx.compose.material3.TextField
2930import androidx.compose.runtime.Composable
@@ -50,6 +51,7 @@ import kotlinx.coroutines.Dispatchers
5051import kotlinx.coroutines.withContext
5152import org.andbootmgr.app.CreatePartDataHolder.Part
5253import org.andbootmgr.app.util.ConfigFile
54+ import org.andbootmgr.app.util.SDLessUtils
5355import org.andbootmgr.app.util.SDUtils
5456import org.andbootmgr.app.util.SOUtils
5557import org.json.JSONObject
@@ -66,7 +68,10 @@ class CreatePartFlow(private val desiredStartSector: Long?): WizardFlow() {
6668 NavButton (vm.activity.getString(R .string.cancel)) { it.finish() },
6769 NavButton (" " ) {}
6870 ) {
69- Start (c)
71+ if (c.vm.deviceInfo.metaonsd)
72+ Start (c)
73+ else
74+ StartSdLess (c)
7075 }, WizardPage (" shop" ,
7176 NavButton (vm.activity.getString(R .string.prev)) { it.navigate(" start" ) },
7277 NavButton (" " ) {}
@@ -92,10 +97,12 @@ class CreatePartFlow(private val desiredStartSector: Long?): WizardFlow() {
9297}
9398
9499private class CreatePartDataHolder (val vm : WizardState , val desiredStartSector : Long? ) {
95- var meta by mutableStateOf<SDUtils .SDPartitionMeta ?>(null )
96- lateinit var p: SDUtils .Partition .FreeSpace
97- var startSectorRelative = 0L
98- var endSectorRelative = 0L
100+ var meta by mutableStateOf<SDUtils .SDPartitionMeta ?>(null ) // metaonsd only
101+ lateinit var p: SDUtils .Partition .FreeSpace // metaonsd only
102+ var freeSpace: Long? = null // !metaonsd only
103+ var startSectorRelative = 0L // metaonsd only
104+ var endSectorRelative = 0L // metaonsd only
105+ var desiredSize = 0L // !metaonsd only
99106 var partitionName: String? = null
100107
101108 var painter: @Composable (() -> Painter )? = null
@@ -108,13 +115,20 @@ private class CreatePartDataHolder(val vm: WizardState, val desiredStartSector:
108115 var code by mutableStateOf(code)
109116 var id by mutableStateOf(id)
110117 var sparse by mutableStateOf(sparse)
111- fun resolveSectorSize (c : CreatePartDataHolder , remaining : Long ): Long {
118+ fun resolveSectorSize (c : CreatePartDataHolder , remaining : Long ): Long { // metaonsd only
112119 return if (! isPercent /* bytes*/ ) {
113120 size / c.meta!! .logicalSectorSizeBytes
114121 } else /* percent*/ {
115122 (BigDecimal (remaining).multiply(BigDecimal (size).divide(BigDecimal (100 )))).toLong()
116123 }
117124 }
125+ fun resolveBytesSize (c : CreatePartDataHolder , remaining : Long ): Long { // !metaonsd only
126+ return if (! isPercent /* bytes*/ ) {
127+ size
128+ } else /* percent*/ {
129+ (BigDecimal (remaining).multiply(BigDecimal (size).divide(BigDecimal (100 )))).toLong()
130+ }
131+ }
118132 }
119133 val parts = mutableStateListOf<Part >()
120134 val extraIdNeeded = mutableListOf<String >()
@@ -134,17 +148,15 @@ private class CreatePartDataHolder(val vm: WizardState, val desiredStartSector:
134148
135149@Composable
136150private fun Start (c : CreatePartDataHolder ) {
137- LaunchedEffect ( Unit ) {
138- if (c.meta == null ) {
151+ if (c.meta == null ) {
152+ LaunchedEffect ( Unit ) {
139153 withContext(Dispatchers .IO ) {
140- val meta = SDUtils .generateMeta(c.vm.deviceInfo.asMetaOnSdDeviceInfo())!! // TODO !metaonsd
154+ val meta = SDUtils .generateMeta(c.vm.deviceInfo.asMetaOnSdDeviceInfo())!!
141155 c.p =
142156 meta.s.find { c.desiredStartSector == it.startSector } as SDUtils .Partition .FreeSpace
143157 c.meta = meta
144158 }
145159 }
146- }
147- if (c.meta == null ) {
148160 LoadingCircle (stringResource(R .string.loading), modifier = Modifier .fillMaxSize())
149161 return
150162 }
@@ -256,6 +268,116 @@ private fun Start(c: CreatePartDataHolder) {
256268 }
257269}
258270
271+ @Composable
272+ private fun StartSdLess (c : CreatePartDataHolder ) {
273+ if (c.freeSpace == null ) {
274+ LaunchedEffect (Unit ) {
275+ withContext(Dispatchers .IO ) {
276+ c.freeSpace = SDLessUtils .getFreeSpaceBytes()
277+ }
278+ }
279+ LoadingCircle (stringResource(R .string.loading), modifier = Modifier .fillMaxSize())
280+ return
281+ }
282+
283+ val verticalScrollState = rememberScrollState()
284+ var size by remember { mutableStateOf(" 0" ) }
285+ val sizeInvalid by remember { derivedStateOf { ! size.matches(numberRegex) } }
286+ // var partitionName by remember { mutableStateOf("") }
287+ // val partitionNameInvalid by remember { derivedStateOf { !partitionName.matches(asciiNonEmptyRegex) } }
288+ Column (
289+ Modifier
290+ .fillMaxWidth()
291+ .verticalScroll(verticalScrollState)) {
292+ Card (modifier = Modifier
293+ .fillMaxWidth()
294+ .padding(10 .dp)) {
295+ Column (modifier = Modifier
296+ .fillMaxWidth()
297+ .padding(10 .dp)) {
298+ Row (verticalAlignment = Alignment .CenterVertically , modifier = Modifier .padding(10 .dp)) {
299+ Icon (painterResource(id = R .drawable.ic_settings), stringResource(R .string.icon_content_desc), Modifier .padding(end = 10 .dp))
300+ Text (stringResource(R .string.general_settings))
301+ }
302+ Column (
303+ Modifier
304+ .fillMaxWidth()
305+ .padding(5 .dp)) {
306+ TextField (modifier = Modifier .fillMaxWidth(), value = size, onValueChange = {
307+ size = it
308+ }, isError = sizeInvalid, label = {
309+ Text (stringResource(R .string.size))
310+ })
311+ Slider (modifier = Modifier .fillMaxWidth(),
312+ value = size.toLongOrNull()?.toFloat() ? : c.freeSpace!! .toFloat(), onValueChange = {
313+ size = it.toLong().toString()
314+ }, valueRange = 0F .. c.freeSpace!! .toFloat())
315+
316+ Text (stringResource(R .string.approx_size, if (! sizeInvalid)
317+ SOUtils .humanReadableByteCountBin(size.toLong()) else stringResource(R .string.invalid_input)))
318+ Text (stringResource(R .string.available_space_bytes,
319+ SOUtils .humanReadableByteCountBin(c.freeSpace!! ), c.freeSpace!! ))
320+ }
321+ }
322+ }
323+
324+ /* if (c.vm.mvm.noobMode) TODO support portable partition for sd-less
325+ MyInfoCard(stringResource(R.string.option_select), padding = 10.dp)
326+
327+ Card(modifier = Modifier
328+ .fillMaxWidth()
329+ .padding(10.dp)) {
330+ Column(modifier = Modifier
331+ .fillMaxWidth()
332+ .padding(10.dp)) {
333+ Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10.dp)) {
334+ Icon(painterResource(id = R.drawable.ic_sd), stringResource(R.string.icon_content_desc), Modifier.padding(end = 10.dp))
335+ Text(stringResource(R.string.portable_part))
336+ }
337+ TextField(value = partitionName, onValueChange = {
338+ partitionName = it
339+ }, isError = partitionNameInvalid && partitionName.isNotEmpty(), label = {
340+ Text(stringResource(R.string.part_name))
341+ })
342+ Row(horizontalArrangement = Arrangement.End, modifier = Modifier
343+ .fillMaxWidth()
344+ .padding(5.dp)) {
345+ Button(enabled = !(sizeInvalid || partitionNameInvalid), onClick = {
346+ c.desiredSize = size.toLong()
347+ c.partitionName = partitionName
348+ c.vm.navigate("flash")
349+ }) {
350+ Text(stringResource(id = R.string.create))
351+ }
352+ }
353+ }
354+ }*/
355+ Card (modifier = Modifier
356+ .fillMaxWidth()
357+ .padding(10 .dp)) {
358+ Column (modifier = Modifier
359+ .fillMaxWidth()
360+ .padding(10 .dp)) {
361+ Row (verticalAlignment = Alignment .CenterVertically , modifier = Modifier .padding(10 .dp)) {
362+ Icon (painterResource(id = R .drawable.ic_droidbooticon), stringResource(R .string.icon_content_desc), Modifier .padding(end = 10 .dp))
363+ Text (stringResource(R .string.install_os))
364+ }
365+ Row (horizontalArrangement = Arrangement .End , modifier = Modifier
366+ .fillMaxWidth()
367+ .padding(5 .dp)) {
368+ Button (enabled = ! sizeInvalid, onClick = {
369+ c.desiredSize = size.toLong()
370+ c.partitionName = null
371+ c.vm.navigate(" shop" )
372+ }) {
373+ Text (stringResource(R .string.cont))
374+ }
375+ }
376+ }
377+ }
378+ }
379+ }
380+
259381@Composable
260382private fun Shop (c : CreatePartDataHolder ) {
261383 var loading by remember { mutableStateOf(true ) }
@@ -621,55 +743,100 @@ private fun Flash(c: CreatePartDataHolder) {
621743 c.vm.logic.extractToolkit(terminal)
622744 c.vm.downloadRemainingFiles(terminal)
623745 if (c.partitionName == null ) { // OS install
624- val createdParts = mutableListOf<Pair <Part , Int >>() // order is important
746+ val createdParts = mutableListOf<Pair <Part , Pair < Int , File > >>() // order is important
625747 val fn = c.romFolderName
626748 terminal.add(vm.activity.getString(R .string.term_f_name, fn))
627749 terminal.add(vm.activity.getString(R .string.term_g_name, c.romDisplayName))
628750 val tmpFile = c.vm.chosen[" _install.sh_" ]!! .toFile(vm)
629751 tmpFile.setExecutable(true )
752+ val entryFolder = File (vm.logic.abmBootset, fn)
753+ if (! SuFile .open(entryFolder.toURI()).mkdir()) {
754+ terminal.add(vm.activity.getString(R .string.term_mkdir_failed))
755+ return @WizardTerminalWork
756+ }
630757 terminal.add(vm.activity.getString(R .string.term_creating_pt))
631758
632- vm.logic.unmountBootset(vm.deviceInfo)
633- val startSectorAbsolute = c.p.startSector + c.startSectorRelative
634- val endSectorAbsolute = c.p.startSector + c.endSectorRelative
635- if (endSectorAbsolute > c.p.endSector)
636- throw IllegalArgumentException (" $endSectorAbsolute can't be bigger than ${c.p.endSector} " )
637- c.parts.forEachIndexed { index, part -> // TODO !metaonsd
638- terminal.add(vm.activity.getString(R .string.term_create_part))
639- val start = c.p.startSector.coerceAtLeast(startSectorAbsolute)
640- val end = c.p.endSector.coerceAtMost(endSectorAbsolute)
641- val k = part.resolveSectorSize(c, end - start)
642- if (start + k > end)
643- throw IllegalStateException (" $start + $k = ${start + k} shouldn't be bigger than $end " )
644- if (k < 0 )
645- throw IllegalStateException (" $k shouldn't be smaller than 0" )
646- // create(start, end) values are relative to the free space area
647- val r = vm.logic.create(c.p, start - c.p.startSector,
648- (start + k) - c.p.startSector, part.code, " " ).to(terminal).exec()
649- if (r.out .joinToString(" \n " ).contains(" kpartx" )) {
650- terminal.add(vm.activity.getString(R .string.term_reboot_asap))
759+ if (vm.deviceInfo.metaonsd) {
760+ vm.logic.unmountBootset(vm.deviceInfo)
761+ val startSectorAbsolute = c.p.startSector + c.startSectorRelative
762+ val endSectorAbsolute = c.p.startSector + c.endSectorRelative
763+ if (endSectorAbsolute > c.p.endSector)
764+ throw IllegalArgumentException (" $endSectorAbsolute can't be bigger than ${c.p.endSector} " )
765+ c.parts.forEachIndexed { index, part ->
766+ terminal.add(vm.activity.getString(R .string.term_create_part))
767+ val start = c.p.startSector.coerceAtLeast(startSectorAbsolute)
768+ val end = c.p.endSector.coerceAtMost(endSectorAbsolute)
769+ val k = part.resolveSectorSize(c, end - start)
770+ if (start + k > end)
771+ throw IllegalStateException (" $start + $k = ${start + k} shouldn't be bigger than $end " )
772+ if (k < 0 )
773+ throw IllegalStateException (" $k shouldn't be smaller than 0" )
774+ // create(start, end) values are relative to the free space area
775+ val r = vm.logic.create(
776+ c.p, start - c.p.startSector,
777+ (start + k) - c.p.startSector, part.code, " "
778+ ).to(terminal).exec()
779+ if (r.out .joinToString(" \n " ).contains(" kpartx" )) {
780+ terminal.add(vm.activity.getString(R .string.term_reboot_asap))
781+ }
782+ val nid = c.meta!! .nid
783+ c.meta = SDUtils .generateMeta(c.vm.deviceInfo.asMetaOnSdDeviceInfo())!!
784+ createdParts.add(part to (nid to File (c.meta!! .dumpKernelPartition(nid).path)))
785+ // do not assert there is leftover space if we just created the last partition we want to create
786+ if (index < c.parts.size - 1 ) {
787+ c.p =
788+ c.meta!! .s.find { it.type == SDUtils .PartitionType .FREE && start + k < it.startSector } as SDUtils .Partition .FreeSpace
789+ }
790+ if (r.isSuccess) {
791+ terminal.add(vm.activity.getString(R .string.term_created_part))
792+ } else {
793+ terminal.add(vm.activity.getString(R .string.term_failure))
794+ return @WizardTerminalWork
795+ }
651796 }
652- createdParts.add(Pair (part, c.meta!! .nid))
653- c.meta = SDUtils .generateMeta(c.vm.deviceInfo.asMetaOnSdDeviceInfo())
654- // do not assert there is leftover space if we just created the last partition we want to create
655- if (index < c.parts.size - 1 ) {
656- c.p =
657- c.meta!! .s.find { it.type == SDUtils .PartitionType .FREE && start + k < it.startSector } as SDUtils .Partition .FreeSpace
797+ if (c.meta == null ) {
798+ terminal.add(vm.activity.getString(R .string.term_cant_get_meta))
799+ return @WizardTerminalWork
658800 }
659- if (r.isSuccess) {
801+ vm.logic.mountBootset(vm.deviceInfo)
802+ } else {
803+ var space = c.desiredSize
804+ val imgFolder = File (c.vm.logic.abmSdLessBootset, fn)
805+ var i = 0
806+ if (imgFolder.exists())
807+ throw IllegalStateException (" image folder ${imgFolder.absolutePath} already exists" )
808+ if (! imgFolder.mkdir())
809+ throw IllegalStateException (" image folder ${imgFolder.absolutePath} could not be created" )
810+ c.parts.forEachIndexed { index, part ->
811+ terminal.add(vm.activity.getString(R .string.term_create_part))
812+ val id = i++
813+ val img = File (imgFolder, id.toString())
814+ val map = File (entryFolder, " $id .map" )
815+ val mappedName = " abm_${fn} _$id "
816+ val bytes = part.resolveBytesSize(c, space)
817+ space - = bytes
818+ if (space < 0 )
819+ throw IllegalStateException (" remaining space $space shouldn't be smaller than 0" )
820+ if (! Shell .cmd(" fallocate -l $bytes " + img.absolutePath).to(terminal).exec().isSuccess) {
821+ terminal.add(vm.activity.getString(R .string.term_failed_fallocate))
822+ return @WizardTerminalWork
823+ }
824+ if (! Shell .cmd(" uncrypt ${img.absolutePath} " + map.absolutePath).to(terminal).exec().isSuccess) {
825+ terminal.add(vm.activity.getString(R .string.term_failed_uncrypt))
826+ return @WizardTerminalWork
827+ }
828+ if (! SDLessUtils .unmap(vm.logic, mappedName, false , terminal))
829+ throw IllegalStateException (" failed to unmap $mappedName which shouldn't even exist?" )
830+ if (! SDLessUtils .map(vm.logic, mappedName, map, terminal)) {
831+ terminal.add(vm.activity.getString(R .string.term_failed_map_other))
832+ return @WizardTerminalWork
833+ }
834+ val mapped = File (vm.logic.dmBase, mappedName)
835+ createdParts.add(part to (i to mapped))
660836 terminal.add(vm.activity.getString(R .string.term_created_part))
661- } else {
662- terminal.add(vm.activity.getString(R .string.term_failure))
663- return @WizardTerminalWork
664837 }
665838 }
666839 terminal.add(vm.activity.getString(R .string.term_created_pt))
667- vm.logic.mountBootset(vm.deviceInfo)
668- val meta = SDUtils .generateMeta(vm.deviceInfo.asMetaOnSdDeviceInfo())
669- if (meta == null ) {
670- terminal.add(vm.activity.getString(R .string.term_cant_get_meta))
671- return @WizardTerminalWork
672- }
673840 terminal.add(vm.activity.getString(R .string.term_building_cfg))
674841
675842 val entry = ConfigFile ()
@@ -681,21 +848,17 @@ private fun Flash(c: CreatePartDataHolder) {
681848 entry[" dtbo" ] = " $fn /dtbo.dtbo"
682849 entry[" options" ] = c.cmdline
683850 entry[" xtype" ] = c.rtype
684- entry[" xpart" ] = createdParts.map { it.second }.joinToString(" :" )
851+ entry[" xpart" ] = createdParts.map { it.second.first }.joinToString(" :" )
685852 if (c.dmaMeta.contains(" updateJson" ) && c.dmaMeta[" updateJson" ] != null )
686853 entry[" xupdate" ] = c.dmaMeta[" updateJson" ]!!
687854 entry.exportToFile(File (vm.logic.abmEntries, " $fn .conf" ))
688- if (! SuFile .open(File (vm.logic.abmBootset, fn).toURI()).mkdir()) {
689- terminal.add(vm.activity.getString(R .string.term_mkdir_failed))
690- return @WizardTerminalWork
691- }
692855
693856 terminal.add(vm.activity.getString(R .string.term_flashing_imgs))
694857 for (part in c.parts) {
695858 if (! c.vm.idNeeded.contains(part.id)) continue
696859 terminal.add(vm.activity.getString(R .string.term_flashing_s, part.id))
697860 val f = c.vm.chosen[part.id]!!
698- val tp = File (meta.dumpKernelPartition( createdParts.find { it.first == part }!! .second).path)
861+ val tp = createdParts.first { it.first == part }.second.second
699862 if (part.sparse) {
700863 val result2 = Shell .cmd(
701864 File (
@@ -720,7 +883,12 @@ private fun Flash(c: CreatePartDataHolder) {
720883 cmd + = " " + c.vm.chosen[i]!! .toFile(vm).absolutePath
721884 }
722885 for (i in c.parts) {
723- cmd + = " " + createdParts.find { it.first == i }!! .second
886+ cmd + = " " + createdParts.first { it.first == i }.second.let {
887+ if (vm.deviceInfo.metaonsd)
888+ it.first // partition number
889+ else
890+ it.second.absolutePath // path to .img file
891+ }
724892 }
725893 val result = vm.logic.runShFileWithArgs(cmd).to(terminal).exec()
726894 if (! result.isSuccess) {
0 commit comments