Skip to content

Commit 3fdf635

Browse files
committed
feat: AppOpsRestricted
1 parent 0e8af72 commit 3fdf635

File tree

16 files changed

+214
-59
lines changed

16 files changed

+214
-59
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<uses-permission
2323
android:name="android.permission.WRITE_SECURE_SETTINGS"
2424
tools:ignore="ProtectedPermissions" />
25+
<uses-permission
26+
android:name="android.permission.GET_APP_OPS_STATS"
27+
tools:ignore="ProtectedPermissions" />
2528
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
2629

2730
<application
@@ -298,22 +301,6 @@
298301
</intent-filter>
299302
</service>
300303

301-
<!-- remove useless -->
302-
<activity
303-
android:name="com.blankj.utilcode.util.UtilsTransActivity4MainProcess"
304-
tools:node="remove" />
305-
<activity
306-
android:name="com.blankj.utilcode.util.UtilsTransActivity"
307-
tools:node="remove" />
308-
309-
<provider
310-
android:name="com.blankj.utilcode.util.UtilsFileProvider"
311-
android:authorities="li.songe.gkd.utilcode.fileprovider"
312-
tools:node="remove" />
313-
<service
314-
android:name="com.blankj.utilcode.util.MessengerUtils$ServerService"
315-
tools:node="remove" />
316-
317304
</application>
318305

319306
</manifest>

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import io.ktor.client.statement.bodyAsText
1717
import kotlinx.coroutines.CoroutineScope
1818
import kotlinx.coroutines.Dispatchers
1919
import kotlinx.coroutines.flow.MutableStateFlow
20+
import kotlinx.coroutines.flow.combine
2021
import kotlinx.coroutines.flow.debounce
2122
import kotlinx.coroutines.flow.map
2223
import kotlinx.coroutines.flow.update
@@ -29,6 +30,7 @@ import li.songe.gkd.data.SubsItem
2930
import li.songe.gkd.data.importData
3031
import li.songe.gkd.db.DbSet
3132
import li.songe.gkd.permission.AuthReason
33+
import li.songe.gkd.permission.appOpsRestrictStateList
3234
import li.songe.gkd.permission.canQueryPkgState
3335
import li.songe.gkd.permission.shizukuGrantedState
3436
import li.songe.gkd.service.A11yService
@@ -322,6 +324,13 @@ class MainViewModel : BaseViewModel(), OnSimpleLife {
322324
list.any { it != A11yService.a11yCn }
323325
}
324326

327+
328+
val appOpsRestrictedFlow = combine(
329+
*appOpsRestrictStateList.map { it.stateFlow }.toTypedArray(),
330+
) { list ->
331+
list.any { !it }
332+
}.stateInit(true)
333+
325334
init {
326335
// preload
327336
appIconMapFlow.value

app/src/main/kotlin/li/songe/gkd/notif/Notif.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import li.songe.gkd.META
1515
import li.songe.gkd.MainActivity
1616
import li.songe.gkd.R
1717
import li.songe.gkd.app
18+
import li.songe.gkd.permission.foregroundServiceSpecialUseState
1819
import li.songe.gkd.permission.notificationState
1920
import li.songe.gkd.service.ActivityService
2021
import li.songe.gkd.service.ButtonService
@@ -73,12 +74,15 @@ data class Notif(
7374

7475
fun notifySelf() {
7576
if (!notificationState.updateAndGet()) return
77+
if (!foregroundServiceSpecialUseState.updateAndGet()) return
7678
@SuppressLint("MissingPermission")
7779
NotificationManagerCompat.from(app).notify(id, toNotification())
7880
}
7981

8082
context(service: Service)
8183
fun notifyService() {
84+
if (!notificationState.updateAndGet()) return
85+
if (!foregroundServiceSpecialUseState.updateAndGet()) return
8286
ServiceCompat.startForeground(
8387
service,
8488
id,

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import li.songe.gkd.util.stopCoroutine
1414
data class AuthReason(
1515
val text: () -> String,
1616
val confirm: ((Activity) -> Unit)? = null,
17-
val renderConfirm: @Composable (() -> ((Activity) -> Unit))? = null,
1817
)
1918

2019
@Composable
@@ -31,10 +30,9 @@ fun AuthDialog(authReasonFlow: MutableStateFlow<AuthReason?>) {
3130
},
3231
onDismissRequest = { authReasonFlow.value = null },
3332
confirmButton = {
34-
val composeConfirm = authAction.renderConfirm?.invoke()
3533
TextButton(onClick = {
3634
authReasonFlow.value = null
37-
(composeConfirm ?: authAction.confirm)?.invoke(context)
35+
authAction.confirm?.invoke(context)
3836
}) {
3937
Text(text = "确认")
4038
}

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

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import kotlinx.coroutines.CompletableDeferred
1414
import kotlinx.coroutines.flow.MutableStateFlow
1515
import kotlinx.coroutines.flow.updateAndGet
1616
import li.songe.gkd.MainActivity
17+
import li.songe.gkd.MainViewModel
1718
import li.songe.gkd.app
19+
import li.songe.gkd.shizuku.SafeAppOpsService
1820
import li.songe.gkd.shizuku.SafePackageManager
1921
import li.songe.gkd.shizuku.shizukuContextFlow
20-
import li.songe.gkd.ui.share.LocalMainViewModel
2122
import li.songe.gkd.util.AndroidTarget
2223
import li.songe.gkd.util.toast
2324
import li.songe.gkd.util.updateAllAppInfo
@@ -88,8 +89,7 @@ private suspend fun asyncRequestPermission(
8889
return deferred.await()
8990
}
9091

91-
@Suppress("SameParameterValue")
92-
private fun checkAllowedOp(op: String): Boolean = app.appOpsManager.checkOpNoThrow(
92+
fun checkAllowedOp(op: String): Boolean = app.appOpsManager.checkOpNoThrow(
9393
op,
9494
android.os.Process.myUid(),
9595
app.packageName
@@ -111,16 +111,74 @@ val foregroundServiceSpecialUseState by lazy {
111111
},
112112
reason = AuthReason(
113113
text = { "当前操作权限「特殊用途的前台服务」已被限制, 请先解除限制" },
114-
renderConfirm = {
115-
val mainVm = LocalMainViewModel.current
116-
{
117-
mainVm.navigatePage(AppOpsAllowPageDestination)
118-
}
119-
}
114+
confirm = {
115+
MainViewModel.instance.navigatePage(AppOpsAllowPageDestination)
116+
},
120117
),
121118
)
122119
}
123120

121+
// https://github.com/orgs/gkd-kit/discussions/1234
122+
val accessA11yState by lazy {
123+
PermissionState(
124+
name = "访问无障碍",
125+
check = {
126+
if (AndroidTarget.Q) {
127+
checkAllowedOp("android:access_accessibility")
128+
} else {
129+
true
130+
}
131+
},
132+
)
133+
}
134+
135+
val createA11yOverlayState by lazy {
136+
PermissionState(
137+
name = "创建无障碍悬浮窗",
138+
check = {
139+
if (SafeAppOpsService.supportCreateA11yOverlay) {
140+
checkAllowedOp("android:create_accessibility_overlay")
141+
} else {
142+
true
143+
}
144+
},
145+
)
146+
}
147+
148+
val getAppOpsStatsState by lazy {
149+
PermissionState(
150+
name = "获取应用操作状态",
151+
check = {
152+
ContextCompat.checkSelfPermission(
153+
app,
154+
"android.permission.GET_APP_OPS_STATS",
155+
) == PackageManager.PERMISSION_GRANTED
156+
},
157+
)
158+
}
159+
160+
val accessRestrictedSettingsState by lazy {
161+
PermissionState(
162+
name = "访问受限设置",
163+
check = {
164+
if (AndroidTarget.TIRAMISU && getAppOpsStatsState.updateAndGet()) {
165+
checkAllowedOp("android:access_restricted_settings")
166+
} else {
167+
true
168+
}
169+
},
170+
)
171+
}
172+
173+
val appOpsRestrictStateList by lazy {
174+
arrayOf(
175+
accessA11yState,
176+
createA11yOverlayState,
177+
accessRestrictedSettingsState,
178+
foregroundServiceSpecialUseState,
179+
)
180+
}
181+
124182
val notificationState by lazy {
125183
val permission = PermissionLists.getNotificationServicePermission()
126184
PermissionState(
@@ -258,6 +316,10 @@ val allPermissionStates by lazy {
258316
listOf(
259317
notificationState,
260318
foregroundServiceSpecialUseState,
319+
accessA11yState,
320+
createA11yOverlayState,
321+
getAppOpsStatsState,
322+
accessRestrictedSettingsState,
261323
canDrawOverlaysState,
262324
canWriteExternalStorage,
263325
ignoreBatteryOptimizationsState,

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.Intent
55
import android.os.Binder
66
import li.songe.gkd.appScope
77
import li.songe.gkd.notif.exposeNotif
8+
import li.songe.gkd.syncFixState
89
import li.songe.gkd.util.LogUtils
910
import li.songe.gkd.util.SnapshotExt
1011
import li.songe.gkd.util.componentName
@@ -31,7 +32,10 @@ class ExposeService : Service() {
3132
LogUtils.d("ExposeService::handleIntent", expose, data)
3233
when (expose) {
3334
0 -> SnapshotExt.captureSnapshot()
34-
1 -> toast("执行成功")
35+
1 -> {
36+
toast("执行成功")
37+
syncFixState()
38+
}
3539

3640
else -> {
3741
toast("未知调用: expose=$expose data=$data")

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import li.songe.gkd.MainActivity
1515
import li.songe.gkd.a11y.useA11yServiceEnabledFlow
1616
import li.songe.gkd.app
1717
import li.songe.gkd.notif.abNotif
18+
import li.songe.gkd.permission.accessA11yState
1819
import li.songe.gkd.permission.foregroundServiceSpecialUseState
1920
import li.songe.gkd.permission.notificationState
2021
import li.songe.gkd.permission.requiredPermission
@@ -47,6 +48,7 @@ class StatusService : Service(), OnSimpleLife {
4748
val a11yServiceEnabledFlow = useA11yServiceEnabledFlow()
4849

4950
fun statusTriple(): Triple<String, String, String?> {
51+
val a11yRestricted = accessA11yState.stateFlow.value
5052
val abRunning = A11yService.isRunning.value
5153
val store = storeFlow.value
5254
val ruleSummary = ruleSummaryFlow.value
@@ -57,7 +59,9 @@ class StatusService : Service(), OnSimpleLife {
5759
} else {
5860
META.appName
5961
}
60-
return if (shizukuWarn) {
62+
return if (!a11yRestricted) {
63+
Triple(title, "无障碍访问受限,请解除限制", "gkd://page")
64+
} else if (shizukuWarn) {
6165
Triple(title, "Shizuku 未连接,请授权或关闭优化", "gkd://page/1")
6266
} else if (!abRunning) {
6367
val text = if (a11yServiceEnabledFlow.value) {
@@ -103,6 +107,7 @@ class StatusService : Service(), OnSimpleLife {
103107
shizukuWarnFlow,
104108
a11yServiceEnabledFlow,
105109
writeSecureSettingsState.stateFlow,
110+
accessA11yState.stateFlow,
106111
topAppIdFlow,
107112
actionCountFlow.debounce(1000L),
108113
) {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ class SafeAppOpsService(
2121
)?.let {
2222
SafeAppOpsService(IAppOpsService.Stub.asInterface(it))
2323
}
24+
25+
val supportCreateA11yOverlay by lazy {
26+
try {
27+
AppOpsManager::class.java.getField("OP_CREATE_ACCESSIBILITY_OVERLAY")
28+
} catch (_: NoSuchFieldException) {
29+
null
30+
} != null
31+
}
2432
}
2533

2634
fun setMode(
@@ -50,5 +58,8 @@ class SafeAppOpsService(
5058
if (AndroidTarget.UPSIDE_DOWN_CAKE) {
5159
setAllowSelfMode(AppOpsManagerHidden.OP_FOREGROUND_SERVICE_SPECIAL_USE)
5260
}
61+
if (supportCreateA11yOverlay) {
62+
setAllowSelfMode(AppOpsManagerHidden.OP_CREATE_ACCESSIBILITY_OVERLAY)
63+
}
5364
}
5465
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class SafePackageManager(private val value: IPackageManager) {
7575
grantSelfPermission("com.android.permission.GET_INSTALLED_APPS")
7676
} catch (e: Throwable) {
7777
}
78+
grantSelfPermission("android.permission.GET_APP_OPS_STATS")
7879
grantSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
7980
if (AndroidTarget.TIRAMISU) {
8081
grantSelfPermission(Manifest.permission.POST_NOTIFICATIONS)

0 commit comments

Comments
 (0)