Enable microG Installer and add microG installer prerequisite dialog
Partially addresses: #1485 Show the microG installer whenever the companion app is present instead of relying on the misleading vending_apps_install metadata flag, and warn users to enable it from microG settings before use. The dialog also offers a shortcut to open microG settings. microG only exposes the top-level settings activity; the App Installer Settings screen is a Navigation fragment with no deep link, so the dialog guides users through the remaining steps.
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||||
|
* SPDX-FileCopyrightText: 2026 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.commons
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||||
|
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||||
|
import com.aurora.extensions.TAG
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||||
|
|
||||||
|
private const val MICROG_SETTINGS_ACTIVITY = "org.microg.gms.ui.SettingsActivity"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog informing users about the prerequisite for using the microG installer
|
||||||
|
* @param onConfirm Callback on confirmation
|
||||||
|
* @param onDismiss Callback on dismissal
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MicroGInstallerPrerequisiteDialog(onConfirm: () -> Unit = {}, onDismiss: () -> Unit = {}) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
title = { Text(text = stringResource(R.string.microg_installer_prerequisite_title)) },
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(
|
||||||
|
dimensionResource(R.dimen.spacing_small)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.microg_installer_prerequisite_desc))
|
||||||
|
OutlinedButton (
|
||||||
|
onClick = {
|
||||||
|
try {
|
||||||
|
context.startActivity(
|
||||||
|
Intent().apply {
|
||||||
|
component = ComponentName(
|
||||||
|
PACKAGE_NAME_GMS,
|
||||||
|
MICROG_SETTINGS_ACTIVITY
|
||||||
|
)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
Log.i(TAG, "Unable to launch microG settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.microg_installer_open_settings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onConfirm) {
|
||||||
|
Text(text = stringResource(R.string.action_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewWrapper(ThemePreviewProvider::class)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun MicroGInstallerPrerequisiteDialogPreview() {
|
||||||
|
MicroGInstallerPrerequisiteDialog()
|
||||||
|
}
|
||||||
@@ -15,7 +15,9 @@ import androidx.compose.material3.SnackbarHostState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
@@ -28,6 +30,7 @@ import com.aurora.store.R
|
|||||||
import com.aurora.store.compose.composable.InstallerListItem
|
import com.aurora.store.compose.composable.InstallerListItem
|
||||||
import com.aurora.store.compose.composable.TopAppBar
|
import com.aurora.store.compose.composable.TopAppBar
|
||||||
import com.aurora.store.compose.preview.ThemePreviewProvider
|
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||||
|
import com.aurora.store.compose.ui.commons.MicroGInstallerPrerequisiteDialog
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
import com.aurora.store.data.installer.SessionInstaller
|
import com.aurora.store.data.installer.SessionInstaller
|
||||||
import com.aurora.store.data.model.Installer
|
import com.aurora.store.data.model.Installer
|
||||||
@@ -61,6 +64,17 @@ private fun ScreenContent(
|
|||||||
onInstallerSelected: (installer: Installer) -> Unit = {}
|
onInstallerSelected: (installer: Installer) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val snackBarHostState = remember { snackBarHostState }
|
val snackBarHostState = remember { snackBarHostState }
|
||||||
|
var showMicroGPrerequisite by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showMicroGPrerequisite) {
|
||||||
|
MicroGInstallerPrerequisiteDialog(
|
||||||
|
onConfirm = {
|
||||||
|
showMicroGPrerequisite = false
|
||||||
|
onInstallerSelected(Installer.MICROG)
|
||||||
|
},
|
||||||
|
onDismiss = { showMicroGPrerequisite = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
@@ -82,7 +96,13 @@ private fun ScreenContent(
|
|||||||
InstallerListItem(
|
InstallerListItem(
|
||||||
installerInfo = installerInfo,
|
installerInfo = installerInfo,
|
||||||
isSelected = installerInfo.installer == currentInstaller,
|
isSelected = installerInfo.installer == currentInstaller,
|
||||||
onClick = { onInstallerSelected(installerInfo.installer) }
|
onClick = {
|
||||||
|
if (installerInfo.installer == Installer.MICROG) {
|
||||||
|
showMicroGPrerequisite = true
|
||||||
|
} else {
|
||||||
|
onInstallerSelected(installerInfo.installer)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import com.aurora.store.data.installer.base.IInstaller
|
|||||||
import com.aurora.store.data.model.Installer
|
import com.aurora.store.data.model.Installer
|
||||||
import com.aurora.store.data.model.InstallerInfo
|
import com.aurora.store.data.model.InstallerInfo
|
||||||
import com.aurora.store.util.PackageUtil
|
import com.aurora.store.util.PackageUtil
|
||||||
|
import com.aurora.store.util.PackageUtil.hasMicroGCompanion
|
||||||
import com.aurora.store.util.Preferences
|
import com.aurora.store.util.Preferences
|
||||||
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@@ -83,7 +84,7 @@ class AppInstaller @Inject constructor(
|
|||||||
if (hasAuroraService(context)) ServiceInstaller.installerInfo else null,
|
if (hasAuroraService(context)) ServiceInstaller.installerInfo else null,
|
||||||
if (hasAppManager(context)) AMInstaller.installerInfo else null,
|
if (hasAppManager(context)) AMInstaller.installerInfo else null,
|
||||||
if (hasShizukuOrSui(context)) ShizukuInstaller.installerInfo else null,
|
if (hasShizukuOrSui(context)) ShizukuInstaller.installerInfo else null,
|
||||||
if (hasMicroGInstaller(context)) MicroGInstaller.installerInfo else null
|
if (hasMicroGCompanion(context)) MicroGInstaller.installerInfo else null
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,6 +155,7 @@ class AppInstaller @Inject constructor(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use microG's proposed API instead of relying on a hardcoded metadata flag that can be misleading (e.g. user can enable the installer from UI)
|
||||||
fun hasMicroGInstaller(context: Context): Boolean {
|
fun hasMicroGInstaller(context: Context): Boolean {
|
||||||
if (!PackageUtil.hasMicroGCompanion(context)) return false
|
if (!PackageUtil.hasMicroGCompanion(context)) return false
|
||||||
return try {
|
return try {
|
||||||
@@ -236,7 +238,7 @@ class AppInstaller @Inject constructor(
|
|||||||
defaultInstaller
|
defaultInstaller
|
||||||
}
|
}
|
||||||
|
|
||||||
Installer.MICROG -> if (hasMicroGInstaller(context)) {
|
Installer.MICROG -> if (hasMicroGCompanion(context)) {
|
||||||
microGInstaller
|
microGInstaller
|
||||||
} else {
|
} else {
|
||||||
defaultInstaller
|
defaultInstaller
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import com.aurora.store.data.installer.base.InstallerBase
|
|||||||
import com.aurora.store.data.model.Installer
|
import com.aurora.store.data.model.Installer
|
||||||
import com.aurora.store.data.model.InstallerInfo
|
import com.aurora.store.data.model.InstallerInfo
|
||||||
import com.aurora.store.data.room.download.Download
|
import com.aurora.store.data.room.download.Download
|
||||||
|
import com.aurora.store.util.PackageUtil.hasMicroGCompanion
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -68,7 +69,7 @@ class MicroGInstaller @Inject constructor(
|
|||||||
Log.i(TAG, "${download.packageName} already queued")
|
Log.i(TAG, "${download.packageName} already queued")
|
||||||
}
|
}
|
||||||
|
|
||||||
AppInstaller.hasMicroGInstaller(context) -> {
|
hasMicroGCompanion(context) -> {
|
||||||
Log.i(TAG, "Received microG install request for ${download.packageName}")
|
Log.i(TAG, "Received microG install request for ${download.packageName}")
|
||||||
|
|
||||||
val files = getFiles(download.packageName, download.versionCode)
|
val files = getFiles(download.packageName, download.versionCode)
|
||||||
|
|||||||
@@ -469,6 +469,9 @@
|
|||||||
<string name="microg_removed_auth_warning">microG is no longer installed. Run setup again to keep things working smoothly.</string>
|
<string name="microg_removed_auth_warning">microG is no longer installed. Run setup again to keep things working smoothly.</string>
|
||||||
<string name="microg_installer_subtitle">Requires microG companion app to be installed</string>
|
<string name="microg_installer_subtitle">Requires microG companion app to be installed</string>
|
||||||
<string name="microg_installer_desc">Helps you bypass App Integrity (installer only) check</string>
|
<string name="microg_installer_desc">Helps you bypass App Integrity (installer only) check</string>
|
||||||
|
<string name="microg_installer_prerequisite_title" translatable="false">Enable microG installer</string>
|
||||||
|
<string name="microg_installer_prerequisite_desc" translatable="false">Before using the microG installer, please ensure you have enabled it.\n\nPlay Store services → ⋮ → App Installer Settings → Allow App Installation</string>
|
||||||
|
<string name="microg_installer_open_settings" translatable="false">Open microG settings</string>
|
||||||
<string name="am_installer_subtitle">Full-featured open source package manager</string>
|
<string name="am_installer_subtitle">Full-featured open source package manager</string>
|
||||||
<string name="am_installer_desc">Requires App Manager, need adb/root mode to install when miui optimization is on</string>
|
<string name="am_installer_desc">Requires App Manager, need adb/root mode to install when miui optimization is on</string>
|
||||||
<string name="shizuku_installer_subtitle">Using system APIs directly with adb/root privileges</string>
|
<string name="shizuku_installer_subtitle">Using system APIs directly with adb/root privileges</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user