Skip to content

Commit 53987a7

Browse files
committed
perf: shizuku appops
1 parent f635f30 commit 53987a7

22 files changed

+294
-135
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@
2828
android:allowBackup="true"
2929
android:icon="@drawable/ic_launcher"
3030
android:label="@string/app_name"
31+
android:networkSecurityConfig="@xml/network_security_config"
3132
android:roundIcon="@drawable/ic_launcher"
3233
android:supportsRtl="false"
33-
android:theme="@style/AppTheme"
34-
android:usesCleartextTraffic="true">
34+
android:theme="@style/AppTheme">
3535

3636
<meta-data
3737
android:name="channel"

app/src/main/kotlin/li/songe/gkd/MainViewModel.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@ class MainViewModel : BaseViewModel(), OnSimpleLife {
288288
shizukuErrorFlow.value = e
289289
}
290290

291-
suspend fun grantPermissionByShizuku(command: String) {
291+
suspend fun guardShizukuContext() {
292+
if (shizukuContextFlow.value.ok) return
292293
if (updateBinderMutex.mutex.isLocked) {
293294
toast("正在连接 Shizuku 服务,请稍后")
294295
stopCoroutine()
@@ -304,10 +305,8 @@ class MainViewModel : BaseViewModel(), OnSimpleLife {
304305
delay(100)
305306
}
306307
}
307-
val service = shizukuContextFlow.value.serviceWrapper ?: stopCoroutine()
308-
if (!service.execCommandForResult(command).ok) {
309-
stopCoroutine()
310-
}
308+
if (shizukuContextFlow.value.ok) return
309+
stopCoroutine()
311310
}
312311

313312
val a11yServiceEnabledFlow = useA11yServiceEnabledFlow()

app/src/main/kotlin/li/songe/gkd/a11y/A11yFeat.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import kotlinx.coroutines.Dispatchers
1515
import kotlinx.coroutines.flow.debounce
1616
import kotlinx.coroutines.flow.update
1717
import kotlinx.coroutines.launch
18-
import li.songe.gkd.META
1918
import li.songe.gkd.appScope
2019
import li.songe.gkd.isActivityVisible
2120
import li.songe.gkd.permission.shizukuOkState
@@ -52,12 +51,12 @@ fun onA11yFeatInit() = service.run {
5251

5352
private fun A11yService.useAttachState() {
5453
onCreated {
55-
if (isActivityVisible() || META.debuggable) {
54+
if (isActivityVisible()) {
5655
toast("无障碍已启动")
5756
}
5857
}
5958
onDestroyed {
60-
if (isActivityVisible() || META.debuggable) {
59+
if (isActivityVisible()) {
6160
if (willDestroyByBlock) {
6261
toast("无障碍已局部关闭")
6362
} else {

app/src/main/kotlin/li/songe/gkd/permission/PermissionDialog.kt

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -53,31 +53,21 @@ sealed class PermissionResult {
5353
data class Denied(val doNotAskAgain: Boolean) : PermissionResult()
5454
}
5555

56-
private suspend fun checkOrRequestPermission(
57-
context: MainActivity,
58-
permissionState: PermissionState
59-
): Boolean {
60-
if (!permissionState.updateAndGet()) {
61-
val result = permissionState.request?.invoke(context)
62-
if (result == null) {
63-
context.mainVm.authReasonFlow.value = permissionState.reason
64-
return false
65-
} else if (result is PermissionResult.Denied) {
66-
if (result.doNotAskAgain) {
67-
context.mainVm.authReasonFlow.value = permissionState.reason
68-
}
69-
return false
70-
}
71-
}
72-
return true
73-
}
74-
7556
suspend fun requiredPermission(
7657
context: MainActivity,
7758
permissionState: PermissionState
7859
) {
79-
val r = checkOrRequestPermission(context, permissionState)
80-
if (!r) {
60+
if (permissionState.updateAndGet()) return
61+
permissionState.grantSelf?.invoke()
62+
if (permissionState.updateAndGet()) return
63+
val result = permissionState.request?.invoke(context)
64+
if (result == null) {
65+
context.mainVm.authReasonFlow.value = permissionState.reason
66+
stopCoroutine()
67+
} else if (result is PermissionResult.Denied) {
68+
if (result.doNotAskAgain) {
69+
context.mainVm.authReasonFlow.value = permissionState.reason
70+
}
8171
stopCoroutine()
8272
}
8373
}

app/src/main/kotlin/li/songe/gkd/permission/PermissionState.kt

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
1515
import kotlinx.coroutines.flow.updateAndGet
1616
import li.songe.gkd.MainActivity
1717
import li.songe.gkd.app
18-
import li.songe.gkd.isActivityVisible
1918
import li.songe.gkd.shizuku.shizukuCheckGranted
19+
import li.songe.gkd.shizuku.shizukuContextFlow
2020
import li.songe.gkd.ui.share.LocalMainViewModel
2121
import li.songe.gkd.util.AndroidTarget
2222
import li.songe.gkd.util.mayQueryPkgNoAccessFlow
@@ -26,26 +26,29 @@ import li.songe.gkd.util.updateAppMutex
2626

2727
class PermissionState(
2828
val check: () -> Boolean,
29+
val grantSelf: (() -> Unit)? = null,
2930
val request: (suspend (context: MainActivity) -> PermissionResult)? = null,
3031
/**
3132
* show it when user doNotAskAgain
3233
*/
3334
val reason: AuthReason? = null,
3435
) {
3536
val stateFlow = MutableStateFlow(false)
36-
val value: Boolean
37-
get() = stateFlow.value
37+
val value get() = stateFlow.value
3838

3939
fun updateAndGet(): Boolean {
4040
return stateFlow.updateAndGet { check() }
4141
}
4242

43-
fun checkOrToast(): Boolean {
43+
fun checkOrToast(): Boolean = if (!updateAndGet()) {
44+
grantSelf?.invoke()
4445
val r = updateAndGet()
4546
if (!r) {
4647
reason?.text?.let { toast(it()) }
4748
}
48-
return r
49+
r
50+
} else {
51+
true
4952
}
5053
}
5154

@@ -83,27 +86,27 @@ private suspend fun asyncRequestPermission(
8386
}
8487

8588
@Suppress("SameParameterValue")
86-
private fun checkOpNoThrow(op: String): Int {
87-
if (AndroidTarget.Q) {
88-
try {
89-
return app.appOpsManager.checkOpNoThrow(
90-
op,
91-
android.os.Process.myUid(),
92-
app.packageName
93-
)
94-
} catch (e: Throwable) {
95-
e.printStackTrace()
96-
}
97-
}
98-
return AppOpsManager.MODE_ALLOWED
89+
private fun checkAllowedOp(op: String): Boolean = app.appOpsManager.checkOpNoThrow(
90+
op,
91+
android.os.Process.myUid(),
92+
app.packageName
93+
).let {
94+
it != AppOpsManager.MODE_IGNORED && it != AppOpsManager.MODE_ERRORED
9995
}
10096

10197
// https://github.com/gkd-kit/gkd/issues/954
10298
// https://github.com/gkd-kit/gkd/issues/887
10399
val foregroundServiceSpecialUseState by lazy {
104100
PermissionState(
105101
check = {
106-
checkOpNoThrow("android:foreground_service_special_use") != AppOpsManager.MODE_IGNORED
102+
if (AndroidTarget.UPSIDE_DOWN_CAKE) {
103+
checkAllowedOp("android:foreground_service_special_use")
104+
} else {
105+
true
106+
}
107+
},
108+
grantSelf = {
109+
shizukuContextFlow.value.appOpsManager?.allowAllSelfMode()
107110
},
108111
reason = AuthReason(
109112
text = { "当前操作权限「特殊用途的前台服务」已被限制, 请先解除限制" },
@@ -123,6 +126,9 @@ val notificationState by lazy {
123126
check = {
124127
XXPermissions.isGrantedPermission(app, permission)
125128
},
129+
grantSelf = {
130+
shizukuContextFlow.value.appOpsManager?.allowAllSelfMode()
131+
},
126132
request = { asyncRequestPermission(it, permission) },
127133
reason = AuthReason(
128134
text = { "当前操作需要「通知权限」\n请先前往权限页面授权" },
@@ -157,13 +163,12 @@ val canDrawOverlaysState by lazy {
157163
// https://developer.android.com/security/fraud-prevention/activities?hl=zh-cn#hide_overlay_windows
158164
Settings.canDrawOverlays(app)
159165
},
166+
grantSelf = {
167+
shizukuContextFlow.value.appOpsManager?.allowAllSelfMode()
168+
},
160169
reason = AuthReason(
161170
text = {
162-
if (isActivityVisible()) {
163-
"当前操作需要「悬浮窗权限」\n请先前往权限页面授权"
164-
} else {
165-
"缺少「悬浮窗权限」请先授权\n或当前应用拒绝显示悬浮窗"
166-
}
171+
"当前操作需要「悬浮窗权限」\n请先前往权限页面授权"
167172
},
168173
confirm = {
169174
XXPermissions.startPermissionActivity(
@@ -206,6 +211,9 @@ val canWriteExternalStorage by lazy {
206211
val writeSecureSettingsState by lazy {
207212
PermissionState(
208213
check = { checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) },
214+
grantSelf = {
215+
shizukuContextFlow.value.packageManager?.grantSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
216+
},
209217
)
210218
}
211219

app/src/main/kotlin/li/songe/gkd/service/GkdTileService.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ fun switchA11yService() = modifyA11yRun {
4949
A11yService.instance?.disableSelf()
5050
} else {
5151
if (!writeSecureSettingsState.updateAndGet()) {
52-
toast("请先授予「写入安全设置权限」")
53-
return@modifyA11yRun
52+
writeSecureSettingsState.grantSelf?.invoke()
53+
if (!writeSecureSettingsState.updateAndGet()) {
54+
toast("请先授予「写入安全设置权限」")
55+
return@modifyA11yRun
56+
}
5457
}
5558
val names = app.getSecureA11yServices()
5659
app.putSecureInt(Settings.Secure.ACCESSIBILITY_ENABLED, 1)

app/src/main/kotlin/li/songe/gkd/service/StatusService.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ class StatusService : Service(), OnSimpleLife {
5353
} else {
5454
META.appName
5555
}
56-
return if (!abRunning) {
56+
return if (shizukuWarn) {
57+
Triple(title, "Shizuku 未连接,请授权或关闭优化", "gkd://page/1")
58+
} else if (!abRunning) {
5759
val text = if (a11yServiceEnabledFlow.value) {
5860
"无障碍发生故障"
5961
} else if (writeSecureSettingsState.updateAndGet()) {
60-
if (store.enableService && a11yPartDisabledFlow.value) {
62+
if (store.enableService && store.enableBlockA11yAppList && a11yPartDisabledFlow.value) {
6163
"无障碍已局部关闭"
6264
} else {
6365
"无障碍已关闭"
@@ -68,8 +70,6 @@ class StatusService : Service(), OnSimpleLife {
6870
Triple(title, text, abNotif.uri)
6971
} else if (!store.enableMatch) {
7072
Triple(title, "暂停规则匹配", "gkd://page?tab=1")
71-
} else if (shizukuWarn) {
72-
Triple(title, "Shizuku 未连接,请授权或关闭优化", "gkd://page/1")
7373
} else if (store.useCustomNotifText) {
7474
Triple(
7575
title,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package li.songe.gkd.shizuku
2+
3+
import android.app.AppOpsManager
4+
import android.app.AppOpsManagerHidden
5+
import android.content.Context
6+
import com.android.internal.app.IAppOpsService
7+
import li.songe.gkd.META
8+
import li.songe.gkd.util.AndroidTarget
9+
import li.songe.gkd.util.checkExistClass
10+
11+
class SafeAppOpsManager(
12+
private val value: IAppOpsService
13+
) {
14+
companion object {
15+
val isAvailable: Boolean
16+
get() = checkExistClass("com.android.internal.app.IAppOpsService")
17+
18+
fun newBinder() = getStubService(
19+
Context.APP_OPS_SERVICE,
20+
isAvailable,
21+
)?.let {
22+
SafeAppOpsManager(IAppOpsService.Stub.asInterface(it))
23+
}
24+
}
25+
26+
fun setMode(
27+
code: Int,
28+
uid: Int = currentUserId,
29+
packageName: String,
30+
mode: Int
31+
) = safeInvokeMethod {
32+
value.setMode(code, uid, packageName, mode)
33+
}
34+
35+
private fun setAllowSelfMode(code: Int) = setMode(
36+
code = code,
37+
packageName = META.appId,
38+
mode = AppOpsManager.MODE_ALLOWED,
39+
)
40+
41+
fun allowAllSelfMode() {
42+
setAllowSelfMode(AppOpsManagerHidden.OP_POST_NOTIFICATION)
43+
setAllowSelfMode(AppOpsManagerHidden.OP_SYSTEM_ALERT_WINDOW)
44+
if (AndroidTarget.TIRAMISU) {
45+
setAllowSelfMode(AppOpsManagerHidden.OP_ACCESS_RESTRICTED_SETTINGS)
46+
}
47+
if (AndroidTarget.UPSIDE_DOWN_CAKE) {
48+
setAllowSelfMode(AppOpsManagerHidden.OP_FOREGROUND_SERVICE_SPECIAL_USE)
49+
}
50+
}
51+
}

app/src/main/kotlin/li/songe/gkd/shizuku/PackageManager.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package li.songe.gkd.shizuku
22

33
import android.content.pm.IPackageManager
44
import android.content.pm.PackageInfo
5+
import li.songe.gkd.META
56
import li.songe.gkd.util.checkExistClass
67
import kotlin.reflect.typeOf
78

@@ -41,4 +42,21 @@ class SafePackageManager(private val value: IPackageManager) {
4142
fun getInstalledPackages(flags: Int, userId: Int): List<PackageInfo> {
4243
return safeInvokeMethod { value.compatGetInstalledPackages(flags, userId) } ?: emptyList()
4344
}
45+
46+
fun grantRuntimePermission(
47+
packageName: String,
48+
permissionName: String,
49+
userId: Int = currentUserId,
50+
) = safeInvokeMethod {
51+
value.grantRuntimePermission(
52+
packageName,
53+
permissionName,
54+
userId
55+
)
56+
}
57+
58+
fun grantSelfPermission(permissionName: String) = grantRuntimePermission(
59+
packageName = META.appId,
60+
permissionName = permissionName,
61+
)
4462
}

0 commit comments

Comments
 (0)