Skip to content

Commit

Permalink
Add app installation support
Browse files Browse the repository at this point in the history
  • Loading branch information
fynngodau committed Sep 21, 2024
1 parent e99cbd3 commit 80c9c52
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 25 deletions.
2 changes: 2 additions & 0 deletions vending-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<uses-permission android:name="com.google.android.gms.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

<uses-permission
android:name="android.permission.USE_CREDENTIALS"
Expand Down
47 changes: 38 additions & 9 deletions vending-app/src/main/java/org/microg/vending/ui/VendingActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class VendingActivity : ComponentActivity() {

load(account)

val install: (app: EnterpriseApp) -> Unit = { app ->
val install: (app: EnterpriseApp, isUpdate: Boolean) -> Unit = { app, isUpdate ->
Toast.makeText(this, "installing ${app.displayName} / ${app.packageName}", Toast.LENGTH_SHORT).show()
Thread {
runBlocking {
Expand All @@ -93,8 +93,20 @@ class VendingActivity : ComponentActivity() {
)

Log.d(TAG, res.toString())
// TODO: install
//SplitInstallManager(this@VendingActivity).startInstall(app.packageName, emptyList(), app.deliveryToken)
val triples = setOf(Triple("base", res.response!!.splitReqResult!!.pkgList!!.baseUrl!!, 0)) +
res.response!!.splitReqResult!!.pkgList!!.pkgDownLoadInfo!!.map {
Triple(it.splitPkgName!!, it.downloadUrl!!, 0)
}

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
SplitInstallManager(this@VendingActivity).apply {
triples.forEach { updateSplitInstallRecord(app.packageName, it) }
notify(this@VendingActivity)
installSplitPackage(this@VendingActivity, app.packageName, triples, isUpdate)
}
} else {
TODO("implement installation on Lollipop devices")
}
}
}.start()
}
Expand Down Expand Up @@ -165,15 +177,32 @@ class VendingActivity : ComponentActivity() {
RequestItem(RequestApp(AppMeta(it.packageName)))
}
)
).items.map { it.response }.map { item ->
).items.map { it.response }.filterNotNull().map { item ->
val packageName = item.meta!!.packageName!!
val installedDetails = this@VendingActivity.packageManager.getInstalledPackages(0).find {
it.applicationInfo.packageName == packageName
}

val available = item.offer?.delivery != null

val versionCode = if (available) {
item.offer!!.version!!.versionCode!!
} else null

val state = if (!available && installedDetails == null) App.State.NOT_COMPATIBLE
else if (!available && installedDetails != null) App.State.INSTALLED
else if (available && installedDetails == null) App.State.NOT_INSTALLED
else if (available && installedDetails != null && installedDetails.versionCode > versionCode!!) App.State.UPDATE_AVAILABLE
else /* if (available && installedDetails != null) */ App.State.INSTALLED

EnterpriseApp(
item!!.meta!!.packageName!!,
item.offer?.version?.versionCode,
packageName,
versionCode,
item.detail!!.name!!.displayName!!,
if (item.offer?.delivery == null) App.State.NOT_COMPATIBLE else App.State.NOT_INSTALLED,
state,
item.detail.icon?.icon?.paint?.url,
item.offer?.delivery?.key,
apps.find { it.packageName!! == item.meta!!.packageName }!!.policy!!,
apps.find { it.packageName!! == item.meta.packageName }!!.policy!!,
)
}.onEach {
Log.v(TAG, "${it.packageName} delivery token: ${it.deliveryToken ?: "none acquired"}")
Expand Down Expand Up @@ -206,7 +235,7 @@ class VendingActivity : ComponentActivity() {
@Composable
fun VendingUi(
account: Account,
install: (app: EnterpriseApp) -> Unit,
install: (app: EnterpriseApp, isUpdate: Boolean) -> Unit,
uninstall: (app: EnterpriseApp) -> Unit
) {
MaterialTheme {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import com.android.vending.R
import org.microg.vending.enterprise.App

@Composable
fun AppRow(app: App, install: () -> Unit, uninstall: () -> Unit) {
fun AppRow(app: App, install: () -> Unit, update: () -> Unit, uninstall: () -> Unit) {
Row(
Modifier.padding(top = 8.dp, bottom = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start),
Expand Down Expand Up @@ -55,7 +55,7 @@ fun AppRow(app: App, install: () -> Unit, uninstall: () -> Unit) {
}
}
if (app.state == App.State.UPDATE_AVAILABLE) {
FilledIconButton(install, colors = IconButtonDefaults.filledIconButtonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)) {
FilledIconButton(update, colors = IconButtonDefaults.filledIconButtonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)) {
Icon(painterResource(R.drawable.ic_update), stringResource(R.string.vending_overview_row_action_update), tint = MaterialTheme.colorScheme.secondary)
}
}
Expand All @@ -69,24 +69,24 @@ fun AppRow(app: App, install: () -> Unit, uninstall: () -> Unit) {
@Preview
@Composable
fun AppRowNotCompatiblePreview() {
AppRow(App("org.mozilla.firefox", 0, "Firefox", App.State.NOT_COMPATIBLE, null, null), {}, {})
AppRow(App("org.mozilla.firefox", 0, "Firefox", App.State.NOT_COMPATIBLE, null, null), {}, {}, {})
}

@Preview
@Composable
fun AppRowNotInstalledPreview() {
AppRow(App("org.mozilla.firefox", 0, "Firefox", App.State.NOT_INSTALLED, null, ""), {}, {})
AppRow(App("org.mozilla.firefox", 0, "Firefox", App.State.NOT_INSTALLED, null, ""), {}, {}, {})
}

@Preview
@Composable
fun AppRowUpdateablePreview() {
AppRow(App("org.mozilla.firefox", 0, "Firefox", App.State.UPDATE_AVAILABLE, null, ""), {}, {})
AppRow(App("org.mozilla.firefox", 0, "Firefox", App.State.UPDATE_AVAILABLE, null, ""), {}, {}, {})
}

@Preview
@Composable
fun AppRowInstalledPreview() {
AppRow(App("org.mozilla.firefox", 0, "Firefox", App.State.INSTALLED, null, ""), {}, {})
AppRow(App("org.mozilla.firefox", 0, "Firefox", App.State.INSTALLED, null, ""), {}, {}, {})
}

Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ import org.microg.vending.enterprise.EnterpriseApp


@Composable
fun EnterpriseList(apps: List<EnterpriseApp>, install: (app: EnterpriseApp) -> Unit, uninstall: (app: EnterpriseApp) -> Unit) {
fun EnterpriseList(apps: List<EnterpriseApp>, install: (app: EnterpriseApp, isUpdate: Boolean) -> Unit, uninstall: (app: EnterpriseApp) -> Unit) {
if (apps.isNotEmpty()) LazyColumn(Modifier.padding(horizontal = 16.dp)) {

val requiredApps = apps.filter { it.policy == AppInstallPolicy.MANDATORY }
if (requiredApps.isNotEmpty()) {
item { InListHeading(R.string.vending_overview_enterprise_row_mandatory) }
item { InListWarning(R.string.vending_overview_enterprise_row_mandatory_hint) }
items(requiredApps) { AppRow(it, { install(it) }, { uninstall(it) }) }
items(requiredApps) { AppRow(it, { install(it, false) }, { install(it, true) }, { uninstall(it) }) }
}

val optionalApps = apps.filter { it.policy == AppInstallPolicy.OPTIONAL }
if (optionalApps.isNotEmpty()) {
item { InListHeading(R.string.vending_overview_enterprise_row_offered) }
items(optionalApps) { AppRow(it, { install(it) }, { uninstall(it) }) }
items(optionalApps) { AppRow(it, { install(it, false) }, { install(it, true) }, { uninstall(it) }) }
}

} else Box(
Expand Down Expand Up @@ -103,12 +103,12 @@ fun EnterpriseListPreview() {
EnterpriseApp("com.android.vending", 0, "Market", App.State.INSTALLED, null, "", AppInstallPolicy.MANDATORY),
EnterpriseApp("org.mozilla.firefox", 0, "Firefox", App.State.NOT_INSTALLED, null, "", AppInstallPolicy.OPTIONAL),
EnterpriseApp("org.thoughtcrime.securesms", 0, "Signal", App.State.NOT_COMPATIBLE, null, "", AppInstallPolicy.OPTIONAL)
), {}, {}
), { _, _ -> }, {}
)
}

@Preview
@Composable
fun EnterpriseListEmptyPreview() {
EnterpriseList(emptyList(), {}, {})
EnterpriseList(emptyList(), { _, _ -> }, {})
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class SplitInstallManager(val context: Context) {
}

@RequiresApi(Build.VERSION_CODES.M)
private suspend fun installSplitPackage(context: Context, callingPackage: String, downloadList: ArraySet<Triple<String, String, Int>>): Intent {
internal suspend fun installSplitPackage(context: Context, callingPackage: String, downloadList: Set<Triple<String, String, Int>>, isUpdate: Boolean = false): Intent {
Log.d(TAG, "installSplitPackage start ")
if (!context.splitSaveFile().exists()) context.splitSaveFile().mkdir()
val downloadSplitPackage = downloadSplitPackage(context, callingPackage, downloadList)
Expand All @@ -116,7 +116,14 @@ class SplitInstallManager(val context: Context) {
Log.d(TAG, "installSplitPackage downloaded success")

val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_INHERIT_EXISTING)
val installed = context.packageManager.getInstalledPackages(0).any {
it.applicationInfo.packageName == callingPackage
}
// Contrary to docs, MODE_INHERIT_EXISTING cannot be used if package is not yet installed.
val params = PackageInstaller.SessionParams(
if (!installed || isUpdate) PackageInstaller.SessionParams.MODE_FULL_INSTALL
else PackageInstaller.SessionParams.MODE_INHERIT_EXISTING
)
params.setAppPackageName(callingPackage)
params.setAppLabel(callingPackage + "Subcontracting")
params.setInstallLocation(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY)
Expand Down Expand Up @@ -161,7 +168,7 @@ class SplitInstallManager(val context: Context) {
}

@RequiresApi(Build.VERSION_CODES.M)
private suspend fun downloadSplitPackage(context: Context, callingPackage: String, downloadList: ArraySet<Triple<String, String, Int>>): Boolean =
private suspend fun downloadSplitPackage(context: Context, callingPackage: String, downloadList: Set<Triple<String, String, Int>>): Boolean =
coroutineScope {
val results = downloadList.map { info ->
Log.d(TAG, "downloadSplitPackage: $info")
Expand Down Expand Up @@ -234,7 +241,7 @@ class SplitInstallManager(val context: Context) {
}

@RequiresApi(Build.VERSION_CODES.M)
private fun updateSplitInstallRecord(callingPackage: String, triple: Triple<String, String, Int>) {
internal fun updateSplitInstallRecord(callingPackage: String, triple: Triple<String, String, Int>) {
splitInstallRecord[callingPackage]?.let { triples ->
val find = triples.find { it.first == triple.first }
find?.let { triples.remove(it) }
Expand All @@ -246,7 +253,7 @@ class SplitInstallManager(val context: Context) {
}
}

private fun notify(context: Context) {
internal fun notify(context: Context) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.createNotificationChannel(
Expand Down

0 comments on commit 80c9c52

Please sign in to comment.