Skip to content

Commit acc86b4

Browse files
committed
Automatically group split and separate APKs
1 parent 31d5d09 commit acc86b4

File tree

7 files changed

+106
-81
lines changed

7 files changed

+106
-81
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package dev.zwander.installwithoptions;
22

3-
import android.content.res.AssetFileDescriptor;
43
import dev.zwander.installwithoptions.IOptionsApplier;
54
import java.util.List;
5+
import java.util.Map;
66

77
interface IShellInterface {
8-
void install(in AssetFileDescriptor[] descriptors, in int[] options, boolean splits, IOptionsApplier optionsApplier, String installerPackageName) = 1;
8+
void install(in Map descriptors, in int[] options, IOptionsApplier optionsApplier, String installerPackageName) = 1;
99

1010
void destroy() = 16777114;
1111
}

app/src/main/java/dev/zwander/installwithoptions/MainActivity.kt

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package dev.zwander.installwithoptions
22

33
import android.content.Intent
4+
import android.content.pm.PackageParser
45
import android.os.Build
56
import android.os.Bundle
7+
import android.util.Log
68
import androidx.activity.SystemBarStyle
79
import androidx.activity.compose.rememberLauncherForActivityResult
810
import androidx.activity.compose.setContent
@@ -65,6 +67,8 @@ import androidx.compose.ui.graphics.toArgb
6567
import androidx.compose.ui.platform.LocalContext
6668
import androidx.compose.ui.platform.LocalLayoutDirection
6769
import androidx.compose.ui.res.stringResource
70+
import androidx.compose.ui.text.font.FontWeight
71+
import androidx.compose.ui.text.style.TextDecoration
6872
import androidx.compose.ui.unit.dp
6973
import androidx.documentfile.provider.DocumentFile
7074
import dev.icerock.moko.mvvm.flow.compose.collectAsMutableState
@@ -79,6 +83,7 @@ import dev.zwander.installwithoptions.util.ElevatedPermissionHandler
7983
import dev.zwander.installwithoptions.util.plus
8084
import dev.zwander.installwithoptions.util.rememberPackageInstaller
8185

86+
@Suppress("DEPRECATION")
8287
class MainActivity : AppCompatActivity() {
8388
private val permissionHandler by lazy {
8489
ElevatedPermissionHandler(
@@ -125,10 +130,19 @@ class MainActivity : AppCompatActivity() {
125130

126131
private fun checkIntentForPackage(intent: Intent) {
127132
if (intent.type == "application/vnd.android.package-archive") {
133+
val selected = DataModel.selectedFiles.value.toMutableMap()
128134
val apkUri = intent.data ?: return
129135
val file = DocumentFile.fromSingleUri(this, apkUri) ?: return
130136

131-
DataModel.selectedFiles.value += file
137+
val fd = contentResolver.openAssetFileDescriptor(apkUri, "r") ?: return
138+
val apkFile = PackageParser.parseApkLite(fd.fileDescriptor, file.name, 0)
139+
140+
val packageList = selected[apkFile.packageName] ?: listOf()
141+
142+
selected[apkFile.packageName] = packageList + file
143+
fd.close()
144+
145+
DataModel.selectedFiles.value = selected
132146
}
133147
}
134148
}
@@ -144,9 +158,20 @@ fun MainContent(modifier: Modifier = Modifier) {
144158
val context = LocalContext.current
145159
val fileSelector =
146160
rememberLauncherForActivityResult(contract = ActivityResultContracts.OpenMultipleDocuments()) { uris ->
147-
selectedFiles = uris.mapNotNull { uri ->
148-
DocumentFile.fromSingleUri(context, uri)
161+
val selected = selectedFiles.toMutableMap()
162+
163+
uris.forEach { uri ->
164+
val file = DocumentFile.fromSingleUri(context, uri) ?: return@forEach
165+
val fd = context.contentResolver.openAssetFileDescriptor(uri, "r") ?: return@forEach
166+
val apkFile = PackageParser.parseApkLite(fd.fileDescriptor, file.name, 0)
167+
168+
val packageList = selected[apkFile.packageName] ?: listOf()
169+
170+
selected[apkFile.packageName] = packageList + file
171+
fd.close()
149172
}
173+
174+
selectedFiles = selected
150175
}
151176
val options = (rememberInstallOptions() + rememberMutableOptions()).sortedBy {
152177
context.resources.getString(it.labelResource)
@@ -204,7 +229,7 @@ fun MainContent(modifier: Modifier = Modifier) {
204229
),
205230
contentAlignment = Alignment.Center,
206231
) {
207-
Text(text = selectedFiles.size.toString())
232+
Text(text = selectedFiles.flatMap { it.value }.size.toString())
208233
}
209234

210235
Spacer(modifier = Modifier.size(8.dp))
@@ -333,10 +358,23 @@ fun MainContent(modifier: Modifier = Modifier) {
333358
text = {
334359
LazyColumn(
335360
modifier = Modifier.fillMaxWidth(),
336-
verticalArrangement = Arrangement.spacedBy(8.dp),
337361
) {
338-
items(items = selectedFiles) {
339-
Text(text = it.name ?: it.uri.toString())
362+
items(items = selectedFiles.entries.flatMap { listOf(it.key) + it.value }) {
363+
if (it is String) {
364+
Text(
365+
text = it,
366+
fontWeight = FontWeight.Bold,
367+
textDecoration = TextDecoration.Underline,
368+
modifier = Modifier.padding(top = 8.dp, bottom = 0.dp),
369+
)
370+
}
371+
372+
if (it is DocumentFile) {
373+
Text(
374+
text = it.name ?: it.uri.toString(),
375+
modifier = Modifier.padding(top = 0.dp, bottom = 4.dp),
376+
)
377+
}
340378
}
341379
}
342380
}

app/src/main/java/dev/zwander/installwithoptions/data/DataModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ object DataModel {
77
val shizukuAvailable = MutableStateFlow(false)
88
val rootGranted = MutableStateFlow(false)
99
val selectedOptions = Settings.Keys.selectedOptions.asMutableStateFlow()
10-
val selectedFiles = MutableStateFlow(listOf<DocumentFile>())
10+
val selectedFiles = MutableStateFlow(mapOf<String, List<DocumentFile>>())
1111
}

app/src/main/java/dev/zwander/installwithoptions/util/Installer.kt

Lines changed: 53 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ data class Installer(
5959
)
6060

6161
@Composable
62-
fun rememberPackageInstaller(files: List<DocumentFile>): Installer {
62+
fun rememberPackageInstaller(files: Map<String, List<DocumentFile>>): Installer {
6363
val context = LocalContext.current
6464
val scope = rememberCoroutineScope()
6565
val permissionStarter =
@@ -76,9 +76,9 @@ fun rememberPackageInstaller(files: List<DocumentFile>): Installer {
7676

7777
val options by DataModel.selectedOptions.collectAsState()
7878

79-
var showingConfirmation by remember {
80-
mutableStateOf(false)
81-
}
79+
// var showingConfirmation by remember {
80+
// mutableStateOf(false)
81+
// }
8282
val rootAdapter = remember {
8383
ShizukuRootAdapter(context)
8484
}
@@ -149,32 +149,35 @@ fun rememberPackageInstaller(files: List<DocumentFile>): Installer {
149149
}
150150
}
151151

152-
fun installPackage(files: List<DocumentFile>, options: List<InstallOption>, split: Boolean) {
152+
fun installPackage(files: Map<String, List<DocumentFile>>, options: List<InstallOption>) {
153153
if (shellInterface != null) {
154154
isInstalling = true
155155
}
156156

157157
scope.launch(Dispatchers.IO) {
158158
try {
159159
shellInterface?.install(
160-
files.map {
161-
context.contentResolver.openAssetFileDescriptor(
162-
it.uri,
163-
"r",
164-
)
165-
}.toTypedArray(),
160+
files.map { (k, v) ->
161+
k to v.map {
162+
context.contentResolver.openAssetFileDescriptor(
163+
it.uri,
164+
"r",
165+
)
166+
}
167+
}.toMap(),
166168
options.map { it.value }.toIntArray(),
167-
split,
168169
applier,
169170
MutableOption.InstallerPackage.settingsKey.getValue(),
170171
)
171172
} catch (e: Exception) {
172-
statuses = files.map {
173-
InstallResult(
174-
status = InstallStatus.FAILURE,
175-
packageName = it.name ?: it.uri.toString(),
176-
message = e.localizedMessage ?: e.message ?: e.toString(),
177-
)
173+
statuses = files.flatMap { (_, v) ->
174+
v.map {
175+
InstallResult(
176+
status = InstallStatus.FAILURE,
177+
packageName = it.name ?: it.uri.toString(),
178+
message = e.localizedMessage ?: e.message ?: e.toString(),
179+
)
180+
}
178181
}
179182
}
180183
}
@@ -199,37 +202,37 @@ fun rememberPackageInstaller(files: List<DocumentFile>): Installer {
199202
}
200203
}
201204

202-
if (showingConfirmation) {
203-
AlertDialog(
204-
onDismissRequest = { showingConfirmation = false },
205-
confirmButton = {
206-
TextButton(
207-
onClick = {
208-
installPackage(files, options ?: listOf(), true)
209-
showingConfirmation = false
210-
},
211-
) {
212-
Text(text = stringResource(id = R.string.split_app))
213-
}
214-
},
215-
dismissButton = {
216-
TextButton(
217-
onClick = {
218-
installPackage(files, options ?: listOf(), false)
219-
showingConfirmation = false
220-
},
221-
) {
222-
Text(text = stringResource(id = R.string.separate_apps))
223-
}
224-
},
225-
title = {
226-
Text(text = stringResource(id = R.string.install_question))
227-
},
228-
text = {
229-
Text(text = stringResource(id = R.string.install_question_desc))
230-
},
231-
)
232-
}
205+
// if (showingConfirmation) {
206+
// AlertDialog(
207+
// onDismissRequest = { showingConfirmation = false },
208+
// confirmButton = {
209+
// TextButton(
210+
// onClick = {
211+
// installPackage(files, options ?: listOf(), true)
212+
// showingConfirmation = false
213+
// },
214+
// ) {
215+
// Text(text = stringResource(id = R.string.split_app))
216+
// }
217+
// },
218+
// dismissButton = {
219+
// TextButton(
220+
// onClick = {
221+
// installPackage(files, options ?: listOf(), false)
222+
// showingConfirmation = false
223+
// },
224+
// ) {
225+
// Text(text = stringResource(id = R.string.separate_apps))
226+
// }
227+
// },
228+
// title = {
229+
// Text(text = stringResource(id = R.string.install_question))
230+
// },
231+
// text = {
232+
// Text(text = stringResource(id = R.string.install_question_desc))
233+
// },
234+
// )
235+
// }
233236

234237
statuses.takeIf { it.isNotEmpty() && it.size == files.size }?.let { s ->
235238
AlertDialog(
@@ -284,11 +287,7 @@ fun rememberPackageInstaller(files: List<DocumentFile>): Installer {
284287
return Installer(
285288
install = remember(files.hashCode(), options.hashCode()) {
286289
{
287-
if (files.size > 1) {
288-
showingConfirmation = true
289-
} else {
290-
installPackage(files, options ?: listOf(), false)
291-
}
290+
installPackage(files, options ?: listOf())
292291
}
293292
},
294293
isInstalling = isInstalling,

app/src/main/java/dev/zwander/installwithoptions/util/InternalInstaller.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,13 @@ class InternalInstaller(private val context: Context) {
2626
.packageInstaller
2727

2828
fun installPackage(
29-
fileDescriptors: Array<AssetFileDescriptor>,
29+
fileDescriptors: Map<String, List<AssetFileDescriptor>>,
3030
options: IntArray,
31-
splits: Boolean,
3231
applier: IOptionsApplier,
3332
installerPackageName: String,
3433
) {
35-
if (splits) {
36-
installPackagesInSession(fileDescriptors, options, applier, installerPackageName)
37-
} else {
38-
fileDescriptors.forEach { fd ->
39-
installPackagesInSession(arrayOf(fd), options, applier, installerPackageName)
40-
}
34+
fileDescriptors.forEach { (_, fds) ->
35+
installPackagesInSession(fds.toTypedArray(), options, applier, installerPackageName)
4136
}
4237
}
4338

app/src/main/java/dev/zwander/installwithoptions/util/ShellInterface.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ class ShellInterface(context: Context) : IShellInterface.Stub() {
3030
})
3131

3232
override fun install(
33-
fileDescriptors: Array<AssetFileDescriptor>,
33+
fileDescriptors: Map<*, *>,
3434
options: IntArray,
35-
splits: Boolean,
3635
applier: IOptionsApplier,
3736
installerPackageName: String?,
3837
) {
@@ -41,9 +40,8 @@ class ShellInterface(context: Context) : IShellInterface.Stub() {
4140
}
4241

4342
installer.installPackage(
44-
fileDescriptors,
43+
fileDescriptors as Map<String, List<AssetFileDescriptor>>,
4544
options,
46-
splits,
4745
applier,
4846
installerPackageName.takeIf { !it.isNullOrBlank() } ?: "shell",
4947
)

app/src/main/res/values/strings.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@
3030
<string name="shizuku_not_running">Shizuku not Running</string>
3131
<string name="shizuku_not_running_desc">Shizuku needs to be running for this app to function. Follow the directions in the Shizuku app to start it.</string>
3232

33-
<string name="install_question">Bulk or Split?</string>
34-
<string name="install_question_desc">You have multiple files selected for installation. Are these separate apps or split files for one app?</string>
35-
<string name="separate_apps">Separate Apps</string>
36-
<string name="split_app">Split App</string>
37-
3833
<string name="replace_existing">Replace Existing</string>
3934
<string name="replace_existing_desc">If the package being installed already exists, replace/upgrade it.</string>
4035

0 commit comments

Comments
 (0)