Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,31 @@ package com.aurora.store.compose.ui.preferences.installation

import android.app.admin.DevicePolicyManager
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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
Expand All @@ -32,8 +40,14 @@ import com.aurora.store.R
import com.aurora.store.compose.composable.TopAppBar
import com.aurora.store.compose.navigation.Destination
import com.aurora.store.compose.preview.ThemePreviewProvider
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.Installer
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.INSTALLATION_PROFILE_ALL
import com.aurora.store.util.Preferences.INSTALLATION_PROFILE_CURRENT
import com.aurora.store.util.Preferences.INSTALLATION_PROFILE_WORK
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLATION_PROFILE
import com.aurora.store.util.save

@Composable
Expand Down Expand Up @@ -64,6 +78,65 @@ private fun ScreenContent(
}
var showClearOwnerDialog by remember { mutableStateOf(false) }

// Shizuku is the only installer with the privileges to target other user profiles.
val showInstallProfile = remember {
AppInstaller.getCurrentInstaller(context) == Installer.SHIZUKU
}
var installProfile by remember {
mutableIntStateOf(
Preferences.getInteger(
context,
PREFERENCE_INSTALLATION_PROFILE,
INSTALLATION_PROFILE_CURRENT
)
)
}
var showInstallProfileDialog by remember { mutableStateOf(false) }

if (showInstallProfileDialog) {
AlertDialog(
onDismissRequest = { showInstallProfileDialog = false },
title = { Text(stringResource(R.string.pref_install_profile_title)) },
text = {
Column {
listOf(
INSTALLATION_PROFILE_CURRENT,
INSTALLATION_PROFILE_WORK,
INSTALLATION_PROFILE_ALL
).forEach { mode ->
Row(
modifier = Modifier
.fillMaxWidth()
.selectable(
selected = installProfile == mode,
onClick = {
installProfile = mode
context.save(PREFERENCE_INSTALLATION_PROFILE, mode)
showInstallProfileDialog = false
}
)
.padding(vertical = dimensionResource(R.dimen.spacing_small)),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(selected = installProfile == mode, onClick = null)
Text(
modifier = Modifier.padding(
start = dimensionResource(R.dimen.spacing_small)
),
text = stringResource(installProfileLabel(mode))
)
}
}
}
},
confirmButton = {
TextButton(onClick = { showInstallProfileDialog = false }) {
Text(stringResource(android.R.string.cancel))
}
}
)
}

if (showClearOwnerDialog) {
AlertDialog(
onDismissRequest = { showClearOwnerDialog = false },
Expand Down Expand Up @@ -104,6 +177,19 @@ private fun ScreenContent(
supportingContent = { Text(stringResource(R.string.pref_install_mode_summary)) }
)
}
if (showInstallProfile) {
item {
ListItem(
modifier = Modifier.clickable { showInstallProfileDialog = true },
headlineContent = {
Text(stringResource(R.string.pref_install_profile_title))
},
supportingContent = {
Text(stringResource(installProfileLabel(installProfile)))
}
)
}
}
item { HorizontalDivider() }
item {
ListItem(
Expand Down Expand Up @@ -148,6 +234,12 @@ private fun ScreenContent(
}
}

private fun installProfileLabel(mode: Int): Int = when (mode) {
INSTALLATION_PROFILE_WORK -> R.string.pref_install_profile_work
INSTALLATION_PROFILE_ALL -> R.string.pref_install_profile_all
else -> R.string.pref_install_profile_current
}

@PreviewWrapper(ThemePreviewProvider::class)
@Preview
@Composable
Expand Down
124 changes: 98 additions & 26 deletions app/src/main/java/com/aurora/store/data/installer/ShizukuInstaller.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionParams
import android.content.pm.PackageInstallerHidden
import android.content.pm.PackageManagerHidden
import android.content.pm.UserInfo
import android.os.Build
import android.os.IBinder
import android.os.IInterface
import android.os.IUserManager
import android.os.Process
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.PendingIntentCompat
Expand All @@ -49,6 +52,11 @@ import com.aurora.store.data.model.InstallerInfo
import com.aurora.store.data.receiver.InstallerStatusReceiver
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.PackageUtil.isSharedLibraryInstalled
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.INSTALLATION_PROFILE_ALL
import com.aurora.store.util.Preferences.INSTALLATION_PROFILE_CURRENT
import com.aurora.store.util.Preferences.INSTALLATION_PROFILE_WORK
import com.aurora.store.util.Preferences.PREFERENCE_INSTALLATION_PROFILE
import dagger.hilt.android.qualifiers.ApplicationContext
import dev.rikka.tools.refine.Refine
import javax.inject.Inject
Expand Down Expand Up @@ -90,17 +98,75 @@ class ShizukuInstaller @Inject constructor(
IPackageInstaller.Stub.asInterface(iPackageManager.packageInstaller.asShizukuBinder())
}

private val packageInstaller: PackageInstaller? by lazy {
if (isSAndAbove) {
Refine.unsafeCast<PackageInstaller>(
PackageInstallerHidden(iPackageInstaller, PLAY_PACKAGE_NAME, null, 0)
)
} else if (isOAndAbove) {
Refine.unsafeCast<PackageInstaller>(
PackageInstallerHidden(iPackageInstaller, PLAY_PACKAGE_NAME, 0)
)
} else {
null
private val iUserManager: IUserManager by lazy {
IUserManager.Stub.asInterface(SystemServiceHelper.getSystemService("user").wrap())
}

/**
* Id of the user/profile Aurora Store itself runs in. Mirrors the calculation used by the
* root installer ([RootInstaller]); the multiplier is Android's per-user UID range.
*/
private val currentUserId: Int
get() = Process.myUid() / 100_000

/**
* Builds a [PackageInstaller] bound to [userId] so sessions are created for that profile.
* The hidden [PackageInstallerHidden] constructor takes the target user id as its last
* argument, which is how installing into a work profile is achieved with Shizuku privileges.
*/
private fun getPackageInstaller(userId: Int): PackageInstaller? = when {
isSAndAbove -> Refine.unsafeCast<PackageInstaller>(
PackageInstallerHidden(iPackageInstaller, PLAY_PACKAGE_NAME, null, userId)
)

isOAndAbove -> Refine.unsafeCast<PackageInstaller>(
PackageInstallerHidden(iPackageInstaller, PLAY_PACKAGE_NAME, userId)
)

else -> null
}

/**
* Resolves the user/profile ids to install into based on the user's preference.
* Falls back to the current profile when the requested profile can't be resolved (e.g. the
* work profile preference is set but no managed profile exists).
*/
private fun getTargetUserIds(): List<Int> {
val mode = Preferences.getInteger(
context,
PREFERENCE_INSTALLATION_PROFILE,
INSTALLATION_PROFILE_CURRENT
)
if (mode == INSTALLATION_PROFILE_CURRENT) return listOf(currentUserId)

val users = runCatching {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
iUserManager.getUsers(true, true, true)
} else {
@Suppress("DEPRECATION")
iUserManager.getUsers(true)
}
}.getOrElse { ex ->
Log.e(TAG, "Failed to query users, using current profile", ex)
emptyList<UserInfo>()
}

return when (mode) {
INSTALLATION_PROFILE_WORK ->
users
.filter { (it.flags and UserInfo.FLAG_MANAGED_PROFILE) != 0 }
.map { it.id }
.ifEmpty {
Log.i(TAG, "No work profile found, falling back to current profile")
listOf(currentUserId)
}

INSTALLATION_PROFILE_ALL ->
users
.map { it.id }
.ifEmpty { listOf(currentUserId) }

else -> listOf(currentUserId)
}
}

Expand All @@ -110,33 +176,39 @@ class ShizukuInstaller @Inject constructor(
if (isAlreadyQueued(download.packageName)) {
Log.i(TAG, "${download.packageName} already queued")
} else {
download.sharedLibs.forEach {
// Shared library packages cannot be updated
if (!isSharedLibraryInstalled(context, it.packageName, it.versionCode)) {
install(
packageName = download.packageName,
versionCode = download.versionCode,
sharedLibPkgName = it.packageName
)
getTargetUserIds().forEach { userId ->
download.sharedLibs.forEach {
// Shared library packages cannot be updated
if (!isSharedLibraryInstalled(context, it.packageName, it.versionCode)) {
install(
packageName = download.packageName,
versionCode = download.versionCode,
userId = userId,
sharedLibPkgName = it.packageName
)
}
}
install(
packageName = download.packageName,
versionCode = download.versionCode,
userId = userId,
displayName = download.displayName
)
}
install(
packageName = download.packageName,
versionCode = download.versionCode,
displayName = download.displayName
)
}
}

private fun install(
packageName: String,
versionCode: Long,
userId: Int,
sharedLibPkgName: String = "",
displayName: String = ""
) {
Log.i(
TAG,
"Received session install request for ${sharedLibPkgName.ifBlank { packageName }}"
"Received session install request for " +
"${sharedLibPkgName.ifBlank { packageName }} (user $userId)"
)

val (sessionId, session) = kotlin.runCatching {
Expand All @@ -149,7 +221,7 @@ class ShizukuInstaller @Inject constructor(
Refine.unsafeCast<PackageInstallerHidden.SessionParamsHidden>(params).installFlags =
flags

val sessionId = packageInstaller!!.createSession(params)
val sessionId = getPackageInstaller(userId)!!.createSession(params)
val iSession = IPackageInstallerSession.Stub.asInterface(
iPackageInstaller.openSession(sessionId).asShizukuBinder()
)
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/com/aurora/store/util/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ object Preferences {

const val PREFERENCE_INSTALLATION_DEVICE_OWNER = "PREFERENCE_INSTALLATION_DEVICE_OWNER"

/**
* Target profile for the Shizuku installer. Stores one of [INSTALLATION_PROFILE_CURRENT],
* [INSTALLATION_PROFILE_WORK] or [INSTALLATION_PROFILE_ALL]. Only Shizuku has the privileges
* to install across user profiles, so other installers ignore this value.
*/
const val PREFERENCE_INSTALLATION_PROFILE = "PREFERENCE_INSTALLATION_PROFILE"

const val INSTALLATION_PROFILE_CURRENT = 0
const val INSTALLATION_PROFILE_WORK = 1
const val INSTALLATION_PROFILE_ALL = 2

const val PREFERENCE_PROXY_URL = "PREFERENCE_PROXY_URL"
const val PREFERENCE_PROXY_INFO = "PREFERENCE_PROXY_INFO"

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -635,4 +635,8 @@
<item quantity="many">تعذّر تحديث %1$d حسابًا</item>
<item quantity="other">تعذّر تحديث %1$d حساب</item>
</plurals>
<string name="pref_install_profile_title">التثبيت في الملف الشخصي</string>
<string name="pref_install_profile_current">الملف الشخصي الحالي</string>
<string name="pref_install_profile_work">ملف العمل</string>
<string name="pref_install_profile_all">جميع الملفات الشخصية</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-ast/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,8 @@
<string name="shizuku_installer_desc">Requires Shizuku or Sui, needs to be setup and permission granted</string>
<string name="source_code_title">Códigu fonte</string>
<string name="about_aurora_store_subtitle">Conoz más tocante a Aurora Store</string>
<string name="pref_install_profile_title">Instalar nel perfil</string>
<string name="pref_install_profile_current">Perfil actual</string>
<string name="pref_install_profile_work">Perfil del trabayu</string>
<string name="pref_install_profile_all">Tolos perfiles</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-az/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@
<string name="select_app_for_details">Ətraflı məlumat üçün tətbiq seç</string>
<string name="action_filter_no_ads">Reklam yoxdur</string>
<string name="action_filter_no_gms">play xidmətləri yoxdur</string>
<string name="pref_install_profile_title">Profilə quraşdır</string>
<string name="pref_install_profile_current">Cari profil</string>
<string name="pref_install_profile_work">İş profili</string>
<string name="pref_install_profile_all">Bütün profillər</string>
<string name="account_set_default">Varsayılan olaraq təyin edin</string>
<string name="account_default">standart</string>
<string name="account_remove">çıxarın</string>
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-be/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -472,4 +472,8 @@
<string name="page">Старонка <xliff:g id="number">%1$d</xliff:g></string>
<string name="action_skip">Прапусціць</string>
<string name="force_restart_snack">Перазапусціць, каб прымяніць змены?</string>
<string name="pref_install_profile_title">Усталяваць у профіль</string>
<string name="pref_install_profile_current">Бягучы профіль</string>
<string name="pref_install_profile_work">Працоўны профіль</string>
<string name="pref_install_profile_all">Усе профілі</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-bg/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,8 @@
<string name="exodus_report_trackers"><xliff:g id="number_of_trackers">%1$d</xliff:g> тракер(и) намерени в <xliff:g id="app_version_name">%2$s</xliff:g></string>
<string name="filter_latest">Най-нови</string>
<string name="installer_microg_misconfigured">Грешка при инсталирането на MicroG, най-вероятно поради грешка в конфигурацията.</string>
<string name="pref_install_profile_title">Инсталиране в профил</string>
<string name="pref_install_profile_current">Текущ профил</string>
<string name="pref_install_profile_work">Служебен профил</string>
<string name="pref_install_profile_all">Всички профили</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-ca/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,8 @@
<string name="session_scrapped">L\'última sessió s\'ha descartat</string>
<string name="action_skip">Ometre</string>
<string name="force_restart_snack">Reiniciar per tal d\'aplicar els canvis?</string>
<string name="pref_install_profile_title">Instal·la al perfil</string>
<string name="pref_install_profile_current">Perfil actual</string>
<string name="pref_install_profile_work">Perfil de treball</string>
<string name="pref_install_profile_all">Tots els perfils</string>
</resources>
Loading