mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 11:42:22 +08:00
feat: auth writeSecureSettings
This commit is contained in:
parent
f859fa1d15
commit
2ce08c81a0
|
@ -11,7 +11,12 @@ import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.animation.core.AnimationConstants
|
import androidx.compose.animation.core.AnimationConstants
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
@ -40,9 +45,12 @@ import li.songe.gkd.ui.component.BuildDialog
|
||||||
import li.songe.gkd.ui.theme.AppTheme
|
import li.songe.gkd.ui.theme.AppTheme
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
import li.songe.gkd.util.UpgradeDialog
|
import li.songe.gkd.util.UpgradeDialog
|
||||||
|
import li.songe.gkd.util.appInfoCacheFlow
|
||||||
import li.songe.gkd.util.initFolder
|
import li.songe.gkd.util.initFolder
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
import li.songe.gkd.util.map
|
import li.songe.gkd.util.map
|
||||||
|
import li.songe.gkd.util.openApp
|
||||||
|
import li.songe.gkd.util.openUri
|
||||||
import li.songe.gkd.util.storeFlow
|
import li.songe.gkd.util.storeFlow
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
@ -81,6 +89,7 @@ class MainActivity : ComponentActivity() {
|
||||||
navGraph = NavGraphs.root,
|
navGraph = NavGraphs.root,
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
|
ShizukuErrorDialog(mainVm.shizukuErrorFlow)
|
||||||
AuthDialog(mainVm.authReasonFlow)
|
AuthDialog(mainVm.authReasonFlow)
|
||||||
BuildDialog(mainVm.dialogFlow)
|
BuildDialog(mainVm.dialogFlow)
|
||||||
if (META.updateEnabled) {
|
if (META.updateEnabled) {
|
||||||
|
@ -183,3 +192,48 @@ private fun Activity.fixTopPadding() {
|
||||||
ViewCompat.onApplyWindowInsets(view, windowInsets)
|
ViewCompat.onApplyWindowInsets(view, windowInsets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShizukuErrorDialog(stateFlow: MutableStateFlow<Boolean>) {
|
||||||
|
val state = stateFlow.collectAsState()
|
||||||
|
if (state.value) {
|
||||||
|
val appId = "moe.shizuku.privileged.api"
|
||||||
|
val appInfoCache = appInfoCacheFlow.collectAsState()
|
||||||
|
val installed = appInfoCache.value.contains(appId)
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { stateFlow.value = false },
|
||||||
|
title = { Text(text = "授权错误") },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = if (installed) {
|
||||||
|
"Shizuku 授权失败, 请检查是否运行"
|
||||||
|
} else {
|
||||||
|
"Shizuku 未安装, 请先下载后安装"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
if (installed) {
|
||||||
|
TextButton(onClick = {
|
||||||
|
stateFlow.value = false
|
||||||
|
app.openApp(appId)
|
||||||
|
}) {
|
||||||
|
Text(text = "打开 Shizuku")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TextButton(onClick = {
|
||||||
|
stateFlow.value = false
|
||||||
|
app.openUri("https://shizuku.rikka.app/")
|
||||||
|
}) {
|
||||||
|
Text(text = "去下载")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { stateFlow.value = false }) {
|
||||||
|
Text(text = "我知道了")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@ class MainViewModel : ViewModel() {
|
||||||
|
|
||||||
val updateStatus = UpdateStatus()
|
val updateStatus = UpdateStatus()
|
||||||
|
|
||||||
|
val shizukuErrorFlow = MutableStateFlow(false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launchTry(Dispatchers.IO) {
|
viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
val subsItems = DbSet.subsItemDao.queryAll()
|
val subsItems = DbSet.subsItemDao.queryAll()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.app.ActivityManager
|
||||||
import android.app.IActivityTaskManager
|
import android.app.IActivityTaskManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.content.pm.IPackageManager
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
|
@ -56,6 +57,15 @@ fun newActivityTaskManager(): IActivityTaskManager? {
|
||||||
return service.let(::ShizukuBinderWrapper).let(IActivityTaskManager.Stub::asInterface)
|
return service.let(::ShizukuBinderWrapper).let(IActivityTaskManager.Stub::asInterface)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun newPackageManager(): IPackageManager? {
|
||||||
|
val service = SystemServiceHelper.getSystemService("package")
|
||||||
|
if (service == null) {
|
||||||
|
LogUtils.d("shizuku 无法获取 package")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return service.let(::ShizukuBinderWrapper).let(IPackageManager.Stub::asInterface)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -1: invalid fc
|
* -1: invalid fc
|
||||||
* 1: (int) -> List<Task>
|
* 1: (int) -> List<Task>
|
||||||
|
|
|
@ -62,10 +62,8 @@ import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
import com.ramcosta.composedestinations.navigation.navigate
|
import com.ramcosta.composedestinations.navigation.navigate
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import li.songe.gkd.MainActivity
|
import li.songe.gkd.MainActivity
|
||||||
import li.songe.gkd.app
|
|
||||||
import li.songe.gkd.appScope
|
import li.songe.gkd.appScope
|
||||||
import li.songe.gkd.debug.FloatingService
|
import li.songe.gkd.debug.FloatingService
|
||||||
import li.songe.gkd.debug.HttpService
|
import li.songe.gkd.debug.HttpService
|
||||||
|
@ -89,12 +87,10 @@ import li.songe.gkd.ui.style.itemPadding
|
||||||
import li.songe.gkd.ui.style.titleItemPadding
|
import li.songe.gkd.ui.style.titleItemPadding
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
import li.songe.gkd.util.appInfoCacheFlow
|
|
||||||
import li.songe.gkd.util.buildLogFile
|
import li.songe.gkd.util.buildLogFile
|
||||||
import li.songe.gkd.util.json
|
import li.songe.gkd.util.json
|
||||||
import li.songe.gkd.util.launchAsFn
|
import li.songe.gkd.util.launchAsFn
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
import li.songe.gkd.util.openApp
|
|
||||||
import li.songe.gkd.util.openUri
|
import li.songe.gkd.util.openUri
|
||||||
import li.songe.gkd.util.privacyStoreFlow
|
import li.songe.gkd.util.privacyStoreFlow
|
||||||
import li.songe.gkd.util.saveFileToDownloads
|
import li.songe.gkd.util.saveFileToDownloads
|
||||||
|
@ -114,7 +110,6 @@ fun AdvancedPage() {
|
||||||
val store by storeFlow.collectAsState()
|
val store by storeFlow.collectAsState()
|
||||||
val snapshotCount by vm.snapshotCountFlow.collectAsState()
|
val snapshotCount by vm.snapshotCountFlow.collectAsState()
|
||||||
|
|
||||||
ShizukuErrorDialog(vm.shizukuErrorFlow)
|
|
||||||
vm.uploadOptions.ShowDialog()
|
vm.uploadOptions.ShowDialog()
|
||||||
|
|
||||||
var showEditPortDlg by remember {
|
var showEditPortDlg by remember {
|
||||||
|
@ -321,7 +316,7 @@ fun AdvancedPage() {
|
||||||
Shizuku.requestPermission(Activity.RESULT_OK)
|
Shizuku.requestPermission(Activity.RESULT_OK)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LogUtils.d("Shizuku授权错误", e.message)
|
LogUtils.d("Shizuku授权错误", e.message)
|
||||||
vm.shizukuErrorFlow.value = true
|
context.mainVm.shizukuErrorFlow.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ShizukuFragment(false)
|
ShizukuFragment(false)
|
||||||
|
@ -666,48 +661,3 @@ private fun ShizukuFragment(enabled: Boolean = true) {
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ShizukuErrorDialog(stateFlow: MutableStateFlow<Boolean>) {
|
|
||||||
val state = stateFlow.collectAsState()
|
|
||||||
if (state.value) {
|
|
||||||
val appId = "moe.shizuku.privileged.api"
|
|
||||||
val appInfoCache = appInfoCacheFlow.collectAsState()
|
|
||||||
val installed = appInfoCache.value.contains(appId)
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { stateFlow.value = false },
|
|
||||||
title = { Text(text = "授权错误") },
|
|
||||||
text = {
|
|
||||||
Text(
|
|
||||||
text = if (installed) {
|
|
||||||
"Shizuku 授权失败, 请检查是否运行"
|
|
||||||
} else {
|
|
||||||
"Shizuku 未安装, 请先下载后安装"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
if (installed) {
|
|
||||||
TextButton(onClick = {
|
|
||||||
stateFlow.value = false
|
|
||||||
app.openApp(appId)
|
|
||||||
}) {
|
|
||||||
Text(text = "打开 Shizuku")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TextButton(onClick = {
|
|
||||||
stateFlow.value = false
|
|
||||||
app.openUri("https://shizuku.rikka.app/")
|
|
||||||
}) {
|
|
||||||
Text(text = "去下载")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = { stateFlow.value = false }) {
|
|
||||||
Text(text = "我知道了")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package li.songe.gkd.ui
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import li.songe.gkd.db.DbSet
|
import li.songe.gkd.db.DbSet
|
||||||
|
@ -12,7 +11,5 @@ class AdvancedVm : ViewModel() {
|
||||||
val snapshotCountFlow =
|
val snapshotCountFlow =
|
||||||
DbSet.snapshotDao.count().stateIn(viewModelScope, SharingStarted.Eagerly, 0)
|
DbSet.snapshotDao.count().stateIn(viewModelScope, SharingStarted.Eagerly, 0)
|
||||||
|
|
||||||
val shizukuErrorFlow = MutableStateFlow(false)
|
|
||||||
|
|
||||||
val uploadOptions = UploadOptions(viewModelScope)
|
val uploadOptions = UploadOptions(viewModelScope)
|
||||||
}
|
}
|
314
app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt
Normal file
314
app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
package li.songe.gkd.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.blankj.utilcode.util.ClipboardUtils
|
||||||
|
import com.blankj.utilcode.util.LogUtils
|
||||||
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
|
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import li.songe.gkd.META
|
||||||
|
import li.songe.gkd.MainActivity
|
||||||
|
import li.songe.gkd.permission.shizukuOkState
|
||||||
|
import li.songe.gkd.permission.writeSecureSettingsState
|
||||||
|
import li.songe.gkd.service.fixRestartService
|
||||||
|
import li.songe.gkd.shizuku.newPackageManager
|
||||||
|
import li.songe.gkd.util.LocalNavController
|
||||||
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
|
import li.songe.gkd.util.launchAsFn
|
||||||
|
import li.songe.gkd.util.openA11ySettings
|
||||||
|
import li.songe.gkd.util.throttle
|
||||||
|
import li.songe.gkd.util.toast
|
||||||
|
import rikka.shizuku.Shizuku
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
@RootNavGraph
|
||||||
|
@Destination(style = ProfileTransitions::class)
|
||||||
|
@Composable
|
||||||
|
fun AuthA11yPage() {
|
||||||
|
val context = LocalContext.current as MainActivity
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
|
||||||
|
val vm = viewModel<AuthA11yVm>()
|
||||||
|
val showCopyDlg by vm.showCopyDlgFlow.collectAsState()
|
||||||
|
val writeSecureSettings by writeSecureSettingsState.stateFlow.collectAsState()
|
||||||
|
var mode by remember { mutableIntStateOf(-1) }
|
||||||
|
|
||||||
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
|
Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = {
|
||||||
|
TopAppBar(scrollBehavior = scrollBehavior, navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, title = {
|
||||||
|
Text(text = "授权说明")
|
||||||
|
}, actions = {})
|
||||||
|
}) { contentPadding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(contentPadding)
|
||||||
|
) {
|
||||||
|
if (writeSecureSettings) {
|
||||||
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
|
Text(
|
||||||
|
text = "授权成功,请关闭此页面",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = "选择一个授权模式",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp, 0.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = if (mode == 0) {
|
||||||
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
} else {
|
||||||
|
Color.Unspecified
|
||||||
|
}
|
||||||
|
),
|
||||||
|
onClick = { mode = 0 }
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = mode == 0,
|
||||||
|
onClick = { mode = 0 }
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "普通授权(简单)",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp, 0.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "1. 授予[无障碍权限]"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp, 0.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "2. 无障碍服务关闭后需重新授权"
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp, 0.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
TextButton(onClick = throttle { openA11ySettings() }) {
|
||||||
|
Text(
|
||||||
|
text = "手动授权",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp, 0.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = if (mode == 1) {
|
||||||
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
} else {
|
||||||
|
Color.Unspecified
|
||||||
|
}
|
||||||
|
),
|
||||||
|
onClick = { mode = 1 }
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = mode == 1,
|
||||||
|
onClick = { mode = 1 }
|
||||||
|
)
|
||||||
|
Text(text = "高级授权(推荐)")
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp, 0.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "1. 授予[写入安全设置权限]"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp, 0.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "2. 授权永久有效, 可自动重启无障碍服务"
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp, 0.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = "3. 搭配通知栏快捷图标可实现无感重启, 无限保活"
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp, 0.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
TextButton(onClick = throttle(fn = vm.viewModelScope.launchAsFn(Dispatchers.IO) {
|
||||||
|
context.grantPermissionByShizuku()
|
||||||
|
})) {
|
||||||
|
Text(
|
||||||
|
text = "Shizuku授权",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TextButton(onClick = throttle(fn = vm.viewModelScope.launchAsFn(Dispatchers.IO) {
|
||||||
|
grantPermissionByRoot()
|
||||||
|
})) {
|
||||||
|
Text(
|
||||||
|
text = "ROOT授权",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TextButton(onClick = {
|
||||||
|
vm.showCopyDlgFlow.value = true
|
||||||
|
}) {
|
||||||
|
Text(
|
||||||
|
text = "手动授权",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showCopyDlg) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { vm.showCopyDlgFlow.value = false },
|
||||||
|
title = { Text(text = "手动授权") },
|
||||||
|
text = {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Text(text = "1. 有一台安装了 adb 的电脑\n\n2.手机开启调试模式后连接电脑授权调试\n\n3. 在电脑 cmd/pwsh 中运行如下命令")
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = commandText,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.extraSmall)
|
||||||
|
.background(MaterialTheme.colorScheme.secondaryContainer)
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
vm.showCopyDlgFlow.value = false
|
||||||
|
ClipboardUtils.copyText(commandText)
|
||||||
|
toast("复制成功")
|
||||||
|
}) {
|
||||||
|
Text(text = "复制并关闭")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { vm.showCopyDlgFlow.value = false }) {
|
||||||
|
Text(text = "关闭")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val commandText by lazy { "adb pm grant ${META.appId} android.permission.WRITE_SECURE_SETTINGS" }
|
||||||
|
|
||||||
|
private suspend fun MainActivity.grantPermissionByShizuku() {
|
||||||
|
if (shizukuOkState.stateFlow.value) {
|
||||||
|
try {
|
||||||
|
val service = newPackageManager()
|
||||||
|
if (service != null) {
|
||||||
|
service.grantRuntimePermission(
|
||||||
|
META.appId,
|
||||||
|
"android.permission.WRITE_SECURE_SETTINGS",
|
||||||
|
0, // maybe others
|
||||||
|
)
|
||||||
|
delay(500)
|
||||||
|
if (writeSecureSettingsState.updateAndGet()) {
|
||||||
|
toast("授权成功")
|
||||||
|
fixRestartService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast("授权失败:${e.message}")
|
||||||
|
LogUtils.d(e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Shizuku.requestPermission(Activity.RESULT_OK)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.d("Shizuku授权错误", e.message)
|
||||||
|
mainVm.shizukuErrorFlow.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun grantPermissionByRoot() {
|
||||||
|
var p: Process? = null
|
||||||
|
try {
|
||||||
|
p = Runtime.getRuntime().exec("su")
|
||||||
|
val o = DataOutputStream(p.outputStream)
|
||||||
|
o.writeBytes("pm grant ${META.appId} android.permission.WRITE_SECURE_SETTINGS\nexit\n")
|
||||||
|
o.flush()
|
||||||
|
o.close()
|
||||||
|
p.waitFor()
|
||||||
|
if (p.exitValue() == 0) {
|
||||||
|
toast("授权成功")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast("授权失败:${e.message}")
|
||||||
|
LogUtils.d(e)
|
||||||
|
} finally {
|
||||||
|
p?.destroy()
|
||||||
|
}
|
||||||
|
}
|
24
app/src/main/kotlin/li/songe/gkd/ui/AuthA11yVm.kt
Normal file
24
app/src/main/kotlin/li/songe/gkd/ui/AuthA11yVm.kt
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package li.songe.gkd.ui
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import li.songe.gkd.permission.writeSecureSettingsState
|
||||||
|
|
||||||
|
class AuthA11yVm : ViewModel() {
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
if (writeSecureSettingsState.updateAndGet()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val showCopyDlgFlow = MutableStateFlow(false)
|
||||||
|
}
|
|
@ -39,6 +39,7 @@ import li.songe.gkd.ui.component.AuthCard
|
||||||
import li.songe.gkd.ui.component.SettingItem
|
import li.songe.gkd.ui.component.SettingItem
|
||||||
import li.songe.gkd.ui.component.TextSwitch
|
import li.songe.gkd.ui.component.TextSwitch
|
||||||
import li.songe.gkd.ui.destinations.ActivityLogPageDestination
|
import li.songe.gkd.ui.destinations.ActivityLogPageDestination
|
||||||
|
import li.songe.gkd.ui.destinations.AuthA11yPageDestination
|
||||||
import li.songe.gkd.ui.destinations.ClickLogPageDestination
|
import li.songe.gkd.ui.destinations.ClickLogPageDestination
|
||||||
import li.songe.gkd.ui.destinations.SlowGroupPageDestination
|
import li.songe.gkd.ui.destinations.SlowGroupPageDestination
|
||||||
import li.songe.gkd.ui.style.EmptyHeight
|
import li.songe.gkd.ui.style.EmptyHeight
|
||||||
|
@ -46,7 +47,6 @@ import li.songe.gkd.ui.style.itemPadding
|
||||||
import li.songe.gkd.util.HOME_PAGE_URL
|
import li.songe.gkd.util.HOME_PAGE_URL
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
import li.songe.gkd.util.launchAsFn
|
import li.songe.gkd.util.launchAsFn
|
||||||
import li.songe.gkd.util.openA11ySettings
|
|
||||||
import li.songe.gkd.util.openUri
|
import li.songe.gkd.util.openUri
|
||||||
import li.songe.gkd.util.ruleSummaryFlow
|
import li.songe.gkd.util.ruleSummaryFlow
|
||||||
import li.songe.gkd.util.storeFlow
|
import li.songe.gkd.util.storeFlow
|
||||||
|
@ -110,8 +110,7 @@ fun useControlPage(): ScaffoldExt {
|
||||||
title = "无障碍授权",
|
title = "无障碍授权",
|
||||||
desc = if (a11yBroken) "服务故障,请重新授权" else "授权使无障碍服务运行",
|
desc = if (a11yBroken) "服务故障,请重新授权" else "授权使无障碍服务运行",
|
||||||
onAuthClick = {
|
onAuthClick = {
|
||||||
openA11ySettings()
|
navController.navigate(AuthA11yPageDestination)
|
||||||
// TODO context.mainVm.showA11yAuthDlgFlow.value = true
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ public interface IPackageManager extends IInterface {
|
||||||
|
|
||||||
PackageInfo getPackageInfo(String packageName, long flags, int userId);
|
PackageInfo getPackageInfo(String packageName, long flags, int userId);
|
||||||
|
|
||||||
|
void grantRuntimePermission(String packageName, String permName, int user);
|
||||||
|
|
||||||
abstract class Stub {
|
abstract class Stub {
|
||||||
|
|
||||||
public static IPackageManager asInterface(IBinder obj) {
|
public static IPackageManager asInterface(IBinder obj) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user