diff --git a/app/build.gradle.kts b/app/build.gradle.kts index be50ad66..862144ec 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,6 +111,7 @@ dependencies { implementation(libs.dev.rikka.rikkax.material) implementation(libs.dev.rikka.rikkax.material.preference) implementation(libs.me.zhanghai.android.appiconloader) + implementation(libs.org.lsposed.hiddenapibypass) compileOnly(libs.dev.rikka.hidden.stub) ksp(libs.com.github.liujingxing.rxhttp.compiler) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fbe9e9da..f0dc34fe 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,9 @@ + + ().also { for (packageInfo in packages) { - if (packageInfo.packageName in Constants.packagesShouldNotHide) continue - val label = pm.getApplicationLabel(packageInfo.applicationInfo).toString() - val icon = hmaApp.appIconLoader.loadIcon(packageInfo.applicationInfo) - it[packageInfo.packageName] = PackageCache(packageInfo, label, icon) + if (packageInfo.info.packageName in Constants.packagesShouldNotHide) continue + val label = pm.getApplicationLabel(packageInfo.info.applicationInfo).toString() + val icon = hmaApp.appIconLoader.loadIcon(packageInfo.info.applicationInfo) + it[packageInfo.info.packageName] = PackageCache(packageInfo.info, label, icon, packageInfo.user) } } } @@ -113,4 +129,81 @@ object PackageHelper { fun isSystem(packageName: String): Boolean = runBlocking { packageCache.first()[packageName]!!.info.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0 } + + fun shellGetUsers(): List { + val users = mutableListOf() + val result = Shell.cmd("pm list users").exec() + if (result.isSuccess) { + val lines = result.out + for (line in lines) { + val trimedLine = line.trim() + if (trimedLine.startsWith("UserInfo")) { + val infos = trimedLine.substringAfter("UserInfo{").substringBefore("}").split(":") + val id = infos[0].toInt() + val name = infos[1] + users.add(UserInfo(id, name)) + } + } + } + return users + } + + fun grantCrossUserPermissions() { + SuUtils.execPrivileged("pm grant ${hmaApp.packageName} android.permission.INTERACT_ACROSS_USERS --user 0") + } + + fun getInstalledPackagesFromUser(user: Int, tryGrantPermission: Boolean = false): List { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + return pm.getInstalledPackages(0) + } + + try { + val res = HiddenApiBypass.invoke(PackageManager::class.java, pm, "getInstalledPackagesAsUser", 0, user) as List<*> + val packages = mutableListOf() + + for (i in res.indices) { + packages += res[i] as PackageInfo + } + + return packages.toList() + } catch(e: SecurityException) { + if (tryGrantPermission) { + grantCrossUserPermissions() + return getInstalledPackagesFromUser(user) + } + return emptyList() + } catch (e: Exception) { + return emptyList() + } + } + + private fun getInstalledPackagesFromAllUsers(deduplicate: Boolean = true): List { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + return pm.getInstalledPackages(0).map { + PackageInfoWithUser(it, 0) + } + } + + val users = shellGetUsers() + + if (users.isEmpty()) { + // Fall back to current user + return pm.getInstalledPackages(0).map { + PackageInfoWithUser(it, 0) + } + } + + val packages = mutableListOf() + + for (user in users) { + val userPackages = getInstalledPackagesFromUser(user.id) + for (packageInfo in userPackages) { + // Deduplicate + if (deduplicate && packages.any { it.info.packageName == packageInfo.packageName }) continue + packages += PackageInfoWithUser(packageInfo, user.id) + } + } + + return packages.toList() + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a2616f2b..e60464c9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,3 +43,4 @@ dev-rikka-rikkax-material = { module = "dev.rikka.rikkax.material:material", ver dev-rikka-rikkax-material-preference = { module = "dev.rikka.rikkax.material:material-preference", version = "2.0.0" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.5.1" } me-zhanghai-android-appiconloader = { module = "me.zhanghai.android.appiconloader:appiconloader", version = "1.5.0" } +org-lsposed-hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version = "4.3" } \ No newline at end of file