Skip to content

Commit 56de0c3

Browse files
committed
Add ability to specify installer package name
1 parent 1d59029 commit 56de0c3

File tree

9 files changed

+165
-21
lines changed

9 files changed

+165
-21
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dev.zwander.installwithoptions;
2+
3+
import android.content.pm.PackageInstaller.SessionParams;
4+
5+
interface IOptionsApplier {
6+
SessionParams applyOptions(in SessionParams params) = 1;
7+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package dev.zwander.installwithoptions;
22

33
import android.content.res.AssetFileDescriptor;
4+
import dev.zwander.installwithoptions.IOptionsApplier;
45
import java.util.List;
56

67
interface IShellInterface {
7-
void install(in AssetFileDescriptor[] descriptors, in int[] options, boolean splits) = 1;
8+
void install(in AssetFileDescriptor[] descriptors, in int[] options, boolean splits, IOptionsApplier optionsApplier) = 1;
89

910
void destroy() = 16777114;
1011
}

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

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import androidx.compose.material3.CircularProgressIndicator
3535
import androidx.compose.material3.MaterialTheme
3636
import androidx.compose.material3.OutlinedButton
3737
import androidx.compose.material3.OutlinedCard
38+
import androidx.compose.material3.OutlinedTextField
3839
import androidx.compose.material3.Surface
3940
import androidx.compose.material3.Text
4041
import androidx.compose.material3.TextButton
@@ -55,7 +56,9 @@ import dev.icerock.moko.mvvm.flow.compose.collectAsMutableState
5556
import dev.zwander.installwithoptions.components.Footer
5657
import dev.zwander.installwithoptions.data.DataModel
5758
import dev.zwander.installwithoptions.data.InstallOption
59+
import dev.zwander.installwithoptions.data.MutableOption
5860
import dev.zwander.installwithoptions.data.rememberInstallOptions
61+
import dev.zwander.installwithoptions.data.rememberMutableOptions
5962
import dev.zwander.installwithoptions.ui.theme.InstallWithOptionsTheme
6063
import dev.zwander.installwithoptions.util.ElevatedPermissionHandler
6164
import dev.zwander.installwithoptions.util.rememberPackageInstaller
@@ -110,7 +113,9 @@ fun MainContent(modifier: Modifier = Modifier) {
110113
DocumentFile.fromSingleUri(context, uri)
111114
}
112115
}
113-
val options = rememberInstallOptions()
116+
val options = (rememberInstallOptions() + rememberMutableOptions()).sortedBy {
117+
context.resources.getString(it.labelResource)
118+
}
114119
val (install, isInstalling) = rememberPackageInstaller(selectedFiles)
115120

116121
Box(
@@ -134,21 +139,34 @@ fun MainContent(modifier: Modifier = Modifier) {
134139
contentPadding = PaddingValues(8.dp),
135140
) {
136141
items(items = options, key = { it.labelResource }) { option ->
137-
OptionItem(
138-
option = option,
139-
isSelected = selectedOptions?.contains(option) == true,
140-
onSelectedChange = {
141-
selectedOptions = if (it) {
142-
if (selectedOptions?.contains(option) == false) {
143-
(selectedOptions ?: listOf()) + option
144-
} else {
145-
selectedOptions
142+
when (option) {
143+
is InstallOption -> {
144+
OptionItem(
145+
option = option,
146+
isSelected = selectedOptions?.contains(option) == true,
147+
onSelectedChange = {
148+
selectedOptions = if (it) {
149+
if (selectedOptions?.contains(option) == false) {
150+
(selectedOptions ?: listOf()) + option
151+
} else {
152+
selectedOptions
153+
}
154+
} else {
155+
(selectedOptions ?: listOf()) - option
156+
}
157+
},
158+
)
159+
}
160+
is MutableOption<*> -> {
161+
when (option.value.value) {
162+
is String? -> {
163+
@Suppress("UNCHECKED_CAST")
164+
TextOptionItem(option = option as MutableOption<String>)
146165
}
147-
} else {
148-
(selectedOptions ?: listOf()) - option
149166
}
150-
},
151-
)
167+
}
168+
else -> {}
169+
}
152170
}
153171
}
154172

@@ -289,3 +307,43 @@ fun OptionItem(
289307
}
290308
}
291309
}
310+
311+
@Composable
312+
fun TextOptionItem(
313+
option: MutableOption<String>,
314+
modifier: Modifier = Modifier,
315+
) {
316+
var state by option.value.collectAsMutableState()
317+
318+
OutlinedCard(
319+
modifier = modifier,
320+
) {
321+
Row(
322+
horizontalArrangement = Arrangement.spacedBy(4.dp),
323+
verticalAlignment = Alignment.CenterVertically,
324+
modifier = Modifier.padding(8.dp),
325+
) {
326+
Column(
327+
modifier = Modifier.weight(1f),
328+
) {
329+
Text(
330+
text = stringResource(id = option.labelResource),
331+
style = MaterialTheme.typography.titleMedium,
332+
)
333+
334+
Text(
335+
text = stringResource(id = option.descResource),
336+
style = MaterialTheme.typography.bodySmall,
337+
)
338+
339+
Spacer(modifier = Modifier.size(4.dp))
340+
341+
OutlinedTextField(
342+
value = state ?: "",
343+
onValueChange = { state = it },
344+
modifier = Modifier.fillMaxWidth(),
345+
)
346+
}
347+
}
348+
}
349+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dev.zwander.installwithoptions.data
2+
3+
import android.content.pm.PackageInstaller.SessionParams
4+
import android.os.Build
5+
import androidx.annotation.Keep
6+
import androidx.annotation.StringRes
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.remember
9+
import androidx.compose.ui.platform.LocalContext
10+
import dev.zwander.installwithoptions.R
11+
import kotlinx.coroutines.flow.MutableStateFlow
12+
13+
@Composable
14+
fun rememberMutableOptions(): List<MutableOption<*>> {
15+
val context = LocalContext.current
16+
17+
return remember {
18+
getMutableOptions().sortedBy { opt ->
19+
context.resources.getString(opt.labelResource)
20+
}
21+
}
22+
}
23+
24+
fun getMutableOptions() = MutableOption::class.sealedSubclasses
25+
.mapNotNull { it.objectInstance }
26+
.filter { Build.VERSION.SDK_INT >= it.minSdk && Build.VERSION.SDK_INT <= it.maxSdk }
27+
28+
sealed class MutableOption<T>(
29+
val settingsKey: SettingsKey<T>,
30+
val operator: SessionParams.(value: T?) -> Unit,
31+
override val minSdk: Int = Build.VERSION_CODES.BASE,
32+
override val maxSdk: Int = Int.MAX_VALUE,
33+
@StringRes override val labelResource: Int,
34+
@StringRes override val descResource: Int,
35+
) : BaseOption<MutableStateFlow<T?>>() {
36+
override val value = settingsKey.asMutableStateFlow()
37+
38+
fun apply(params: SessionParams) {
39+
params.operator(value.value)
40+
}
41+
42+
@Keep
43+
data object InstallerPackage : MutableOption<String>(
44+
settingsKey = SettingsKey.String(
45+
key = "installer_package",
46+
default = null,
47+
settings = Settings.settings,
48+
),
49+
operator = {
50+
if (!it.isNullOrBlank()) {
51+
setInstallerPackageName(it)
52+
}
53+
},
54+
labelResource = R.string.installer_package,
55+
descResource = R.string.installer_package_desc,
56+
)
57+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ sealed class SettingsKey<Type> {
209209
override val settings: SharedPreferences,
210210
) : SettingsKey<kotlin.String>() {
211211
override fun getValue(): kotlin.String? {
212-
return default?.let { settings.getString(key, default) }
212+
return settings.getString(key, default)
213213
}
214214

215215
override fun setValue(value: kotlin.String?) {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ import androidx.core.content.ContextCompat
3333
import androidx.core.content.IntentCompat
3434
import androidx.documentfile.provider.DocumentFile
3535
import dev.zwander.installwithoptions.BuildConfig
36+
import dev.zwander.installwithoptions.IOptionsApplier
3637
import dev.zwander.installwithoptions.R
3738
import dev.zwander.installwithoptions.data.DataModel
3839
import dev.zwander.installwithoptions.data.InstallOption
3940
import dev.zwander.installwithoptions.data.Settings
41+
import dev.zwander.installwithoptions.data.getMutableOptions
4042
import kotlinx.coroutines.Dispatchers
4143
import kotlinx.coroutines.launch
4244

@@ -73,6 +75,15 @@ fun rememberPackageInstaller(files: List<DocumentFile>): Installer {
7375
}
7476
val shellInterface = rootAdapter.rememberShellInterface()
7577

78+
val applier = remember {
79+
object : IOptionsApplier.Stub() {
80+
override fun applyOptions(params: PackageInstaller.SessionParams): PackageInstaller.SessionParams {
81+
getMutableOptions().forEach { it.apply(params) }
82+
return params.copy()
83+
}
84+
}
85+
}
86+
7687
val receiver = remember {
7788
object : BroadcastReceiver() {
7889
override fun onReceive(context: Context, intent: Intent) {
@@ -122,6 +133,7 @@ fun rememberPackageInstaller(files: List<DocumentFile>): Installer {
122133
}.toTypedArray(),
123134
options.map { it.value }.toIntArray(),
124135
split,
136+
applier,
125137
)
126138
}
127139
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import android.os.ServiceManager
1616
import android.os.UserHandle
1717
import android.util.Log
1818
import dev.zwander.installwithoptions.BuildConfig
19+
import dev.zwander.installwithoptions.IOptionsApplier
1920
import kotlin.random.Random
2021

2122
class InternalInstaller(private val context: Context) {
@@ -26,13 +27,14 @@ class InternalInstaller(private val context: Context) {
2627
fun installPackage(
2728
fileDescriptors: Array<AssetFileDescriptor>,
2829
options: IntArray,
29-
splits: Boolean
30+
splits: Boolean,
31+
applier: IOptionsApplier,
3032
) {
3133
if (splits) {
32-
installPackagesInSession(fileDescriptors, options)
34+
installPackagesInSession(fileDescriptors, options, applier)
3335
} else {
3436
fileDescriptors.forEach { fd ->
35-
installPackagesInSession(arrayOf(fd), options)
37+
installPackagesInSession(arrayOf(fd), options, applier)
3638
}
3739
}
3840
}
@@ -41,14 +43,16 @@ class InternalInstaller(private val context: Context) {
4143
private fun installPackagesInSession(
4244
fileDescriptors: Array<AssetFileDescriptor>,
4345
options: IntArray,
46+
applier: IOptionsApplier,
4447
) {
4548
var session: IPackageInstallerSession? = null
4649

4750
try {
4851
val params = PackageInstaller.SessionParams(
4952
PackageInstaller.SessionParams.MODE_FULL_INSTALL,
50-
).apply {
53+
).run {
5154
options.reduceOrNull { acc, i -> acc or i }?.let { flags -> installFlags = flags }
55+
applier.applyOptions(this)
5256
}
5357
val sessionId =
5458
packageInstaller.createSession(params, "system", "system", UserHandle.myUserId())

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.os.Looper
77
import androidx.annotation.Keep
88
import androidx.core.os.UserHandleCompat
99
import dev.zwander.installwithoptions.BuildConfig
10+
import dev.zwander.installwithoptions.IOptionsApplier
1011
import dev.zwander.installwithoptions.IShellInterface
1112
import rikka.shizuku.Shizuku
1213
import kotlin.system.exitProcess
@@ -32,12 +33,13 @@ class ShellInterface(context: Context) : IShellInterface.Stub() {
3233
fileDescriptors: Array<AssetFileDescriptor>,
3334
options: IntArray,
3435
splits: Boolean,
36+
applier: IOptionsApplier,
3537
) {
3638
if (Looper.myLooper() == null) {
3739
Looper.prepare()
3840
}
3941

40-
installer.installPackage(fileDescriptors, options, splits)
42+
installer.installPackage(fileDescriptors, options, splits, applier)
4143
}
4244

4345
override fun destroy() {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,7 @@
112112

113113
<string name="ignore_dexopt_profile">Ignore Dexopt Profile</string>
114114
<string name="ignore_dexopt_profile_desc">Ignore dexopt profiles for this package during installation.</string>
115+
116+
<string name="installer_package">Installer Package</string>
117+
<string name="installer_package_desc">Specify the package name of the app that Android should report this APK was installed by (e.g., com.android.vending).</string>
115118
</resources>

0 commit comments

Comments
 (0)