Skip to content

Commit

Permalink
feat: WorkProfile App List
Browse files Browse the repository at this point in the history
  • Loading branch information
lisonge committed Jan 8, 2025
1 parent 1183d9c commit de9a68b
Show file tree
Hide file tree
Showing 36 changed files with 852 additions and 370 deletions.
3 changes: 2 additions & 1 deletion app/src/main/kotlin/li/songe/gkd/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import li.songe.gkd.ui.component.SubsSheet
import li.songe.gkd.ui.theme.AppTheme
import li.songe.gkd.util.EditGithubCookieDlg
import li.songe.gkd.util.LocalNavController
import li.songe.gkd.util.ShortUrlSet
import li.songe.gkd.util.UpgradeDialog
import li.songe.gkd.util.appInfoCacheFlow
import li.songe.gkd.util.componentName
Expand Down Expand Up @@ -263,7 +264,7 @@ private fun ShizukuErrorDialog(stateFlow: MutableStateFlow<Boolean>) {
} else {
TextButton(onClick = {
stateFlow.value = false
openUri("https://gkd.li?r=4")
openUri(ShortUrlSet.URL4)
}) {
Text(text = "去下载")
}
Expand Down
19 changes: 12 additions & 7 deletions app/src/main/kotlin/li/songe/gkd/data/AppInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ data class AppInfo(
val isSystem: Boolean,
val mtime: Long,
val hidden: Boolean,
// null=0
val userId: Int? = null,
)

val selfAppInfo by lazy {
Expand All @@ -28,8 +30,10 @@ val selfAppInfo by lazy {
/**
* 平均单次调用时间 11ms
*/
fun PackageInfo.toAppInfo(): AppInfo {
val info = applicationInfo
fun PackageInfo.toAppInfo(
userId: Int? = null,
hidden: Boolean? = null,
): AppInfo {
return AppInfo(
id = packageName,
versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Expand All @@ -40,9 +44,10 @@ fun PackageInfo.toAppInfo(): AppInfo {
},
versionName = versionName,
mtime = lastUpdateTime,
hidden = app.packageManager.getLaunchIntentForPackage(packageName) == null,
name = info?.run { loadLabel(app.packageManager).toString() } ?: packageName,
icon = info?.loadIcon(app.packageManager),
isSystem = info?.run { (ApplicationInfo.FLAG_SYSTEM and flags) != 0 } ?: false,
isSystem = applicationInfo?.let { it.flags and ApplicationInfo.FLAG_SYSTEM != 0 } ?: false,
name = applicationInfo?.run { loadLabel(app.packageManager).toString() } ?: packageName,
icon = applicationInfo?.loadIcon(app.packageManager),
userId = userId,
hidden = hidden ?: (app.packageManager.getLaunchIntentForPackage(packageName) == null),
)
}
}
3 changes: 0 additions & 3 deletions app/src/main/kotlin/li/songe/gkd/data/Tuple.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package li.songe.gkd.data

import androidx.compose.runtime.Immutable

@Immutable
data class Tuple3<T0, T1, T2>(
val t0: T0,
val t1: T1,
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/kotlin/li/songe/gkd/data/UserInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package li.songe.gkd.data

import kotlinx.coroutines.flow.MutableStateFlow

data class UserInfo(
val id: Int,
val name: String,
)

val otherUserMapFlow = MutableStateFlow(emptyMap<Int, UserInfo>())
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.core.content.ContextCompat
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.updateAndGet
import li.songe.gkd.app
Expand All @@ -18,6 +19,7 @@ import li.songe.gkd.util.initOrResetAppInfoCache
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.mayQueryPkgNoAccessFlow
import li.songe.gkd.util.toast
import li.songe.gkd.util.updateAppMutex
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

Expand Down Expand Up @@ -185,8 +187,8 @@ fun updatePermissionState() {
writeSecureSettingsState,
shizukuOkState,
).forEach { it.updateAndGet() }
if (canQueryPkgState.stateFlow.value != canQueryPkgState.updateAndGet() || mayQueryPkgNoAccessFlow.value) {
appScope.launchTry {
if (!updateAppMutex.mutex.isLocked && (canQueryPkgState.stateFlow.value != canQueryPkgState.updateAndGet() || mayQueryPkgNoAccessFlow.value)) {
appScope.launchTry(Dispatchers.IO) {
initOrResetAppInfoCache()
}
}
Expand Down
121 changes: 121 additions & 0 deletions app/src/main/kotlin/li/songe/gkd/shizuku/ActivityTaskManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package li.songe.gkd.shizuku

import android.app.ActivityManager
import android.app.IActivityTaskManager
import android.view.Display
import com.blankj.utilcode.util.LogUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import li.songe.gkd.appScope
import li.songe.gkd.data.DeviceInfo
import li.songe.gkd.permission.shizukuOkState
import li.songe.gkd.service.TopActivity
import li.songe.gkd.util.storeFlow
import li.songe.gkd.util.toast
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.reflect.typeOf

/**
* -1: invalid fc
* 1: (int) -> List<Task>
* 3: (int, boolean, boolean) -> List<Task>
* 4: (int, boolean, boolean, int) -> List<Task>
*/
private var getTasksFcType: Int? = null
private fun IActivityTaskManager.compatGetTasks(maxNum: Int = 1): List<ActivityManager.RunningTaskInfo> {
if (getTasksFcType == null) {
val fcs = this::class.declaredMemberFunctions
val parameters = fcs.find { d -> d.name == "getTasks" }?.parameters
if (parameters != null) {
if (parameters.size == 5 && parameters[1].type == typeOf<Int>() && parameters[2].type == typeOf<Boolean>() && parameters[3].type == typeOf<Boolean>() && parameters[4].type == typeOf<Int>()) {
getTasksFcType = 4
} else if (parameters.size == 4 && parameters[1].type == typeOf<Int>() && parameters[2].type == typeOf<Boolean>() && parameters[3].type == typeOf<Boolean>()) {
getTasksFcType = 3
} else if (parameters.size == 2 && parameters[1].type == typeOf<Int>()) {
getTasksFcType = 1
} else {
getTasksFcType = -1
LogUtils.d(DeviceInfo.instance)
LogUtils.d(fcs)
toast("Shizuku获取方法签名错误")
}
}
}
return try {
// https://bugly.qq.com/v2/crash-reporting/crashes/d0ce46b353/106137?pid=1
// binder haven't been received
when (getTasksFcType) {
1 -> this.getTasks(maxNum)
3 -> this.getTasks(maxNum, false, true)
4 -> this.getTasks(maxNum, false, true, Display.DEFAULT_DISPLAY)
else -> emptyList()
}
} catch (e: Throwable) {
LogUtils.d(e)
emptyList()
}
}

// https://github.com/gkd-kit/gkd/issues/44
// fix java.lang.ClassNotFoundException:Didn't find class "android.app.IActivityTaskManager" on path: DexPathList
interface SafeActivityTaskManager {
fun compatGetTasks(maxNum: Int): List<ActivityManager.RunningTaskInfo>
fun compatGetTasks(): List<ActivityManager.RunningTaskInfo>
}

private fun newActivityTaskManager(): SafeActivityTaskManager? {
val service = SystemServiceHelper.getSystemService("activity_task")
if (service == null) {
LogUtils.d("shizuku 无法获取 activity_task")
return null
}
val manager = service.let(::ShizukuBinderWrapper).let(IActivityTaskManager.Stub::asInterface)
return object : SafeActivityTaskManager {
override fun compatGetTasks(maxNum: Int) = manager.compatGetTasks(maxNum)
override fun compatGetTasks() = manager.compatGetTasks()
}
}

private val shizukuActivityUsedFlow by lazy {
combine(shizukuOkState.stateFlow, storeFlow) { shizukuOk, store ->
shizukuOk && store.enableShizukuActivity
}.stateIn(appScope, SharingStarted.Eagerly, false)
}

private val activityTaskManagerFlow by lazy<StateFlow<SafeActivityTaskManager?>> {
val stateFlow = MutableStateFlow<SafeActivityTaskManager?>(null)
appScope.launch(Dispatchers.IO) {
shizukuActivityUsedFlow.collect {
stateFlow.value = if (it) newActivityTaskManager() else null
}
}
stateFlow
}

fun shizukuCheckActivity(): Boolean {
return (try {
newActivityTaskManager()?.compatGetTasks(1)?.isNotEmpty() == true
} catch (e: Throwable) {
e.printStackTrace()
false
})
}

fun safeGetTopActivity(): TopActivity? {
if (!shizukuActivityUsedFlow.value) return null
try {
val taskManager = activityTaskManagerFlow.value ?: return null
val top = taskManager.compatGetTasks(1).lastOrNull()?.topActivity ?: return null
return TopActivity(appId = top.packageName, activityId = top.className)
} catch (e: Throwable) {
e.printStackTrace()
return null
}
}
74 changes: 74 additions & 0 deletions app/src/main/kotlin/li/songe/gkd/shizuku/PackageManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package li.songe.gkd.shizuku

import android.content.IntentFilter
import android.content.pm.IPackageManager
import android.content.pm.PackageInfo
import com.blankj.utilcode.util.LogUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import li.songe.gkd.appScope
import li.songe.gkd.permission.shizukuOkState
import li.songe.gkd.util.storeFlow
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.typeOf


private var packageFlagsParamsLongType: Boolean? = null
private fun IPackageManager.compatGetInstalledPackages(
flags: Long,
userId: Int
): List<PackageInfo> {
if (packageFlagsParamsLongType == null) {
val method = this::class.declaredFunctions.find { it.name == "getInstalledPackages" }!!
packageFlagsParamsLongType = method.parameters[1].type == typeOf<Long>()
}
return if (packageFlagsParamsLongType == true) {
getInstalledPackages(flags, userId).list
} else {
getInstalledPackages(flags.toInt(), userId).list
}
}

interface SafePackageManager {
fun compatGetInstalledPackages(flags: Long, userId: Int): List<PackageInfo>
fun getAllIntentFilters(packageName: String): List<IntentFilter>
}

fun newPackageManager(): SafePackageManager? {
val service = SystemServiceHelper.getSystemService("package")
if (service == null) {
LogUtils.d("shizuku 无法获取 package")
return null
}
val manager = service.let(::ShizukuBinderWrapper).let(IPackageManager.Stub::asInterface)
return object : SafePackageManager {
override fun compatGetInstalledPackages(flags: Long, userId: Int) =
manager.compatGetInstalledPackages(flags, userId)

override fun getAllIntentFilters(packageName: String) =
manager.getAllIntentFilters(packageName).list
}
}

val shizukuWorkProfileUsedFlow by lazy {
combine(shizukuOkState.stateFlow, storeFlow) { shizukuOk, store ->
shizukuOk && store.enableShizukuWorkProfile
}.stateIn(appScope, SharingStarted.Eagerly, false)
}

val packageManagerFlow by lazy<StateFlow<SafePackageManager?>> {
val stateFlow = MutableStateFlow<SafePackageManager?>(null)
appScope.launch(Dispatchers.IO) {
shizukuWorkProfileUsedFlow.collect {
stateFlow.value = if (it) newPackageManager() else null
}
}
stateFlow
}
Loading

0 comments on commit de9a68b

Please sign in to comment.