Optimize AppLockDialog

Add reboot API, close #119
Update README files
Update dependencies, bump version number
This commit is contained in:
BinTianqi
2025-05-10 10:41:25 +08:00
parent 2ce92999a4
commit fdcb7c179f
12 changed files with 71 additions and 59 deletions

View File

@@ -16,6 +16,7 @@ Use Android Device owner privilege to manage your device.
- System - System
- Options: disable camera, disable screenshot, master volume mute, disable USB signal... - Options: disable camera, disable screenshot, master volume mute, disable USB signal...
- Lock task mode
- Manage CA certificates - Manage CA certificates
- _Wipe data_ - _Wipe data_
- ... - ...
@@ -33,8 +34,9 @@ Use Android Device owner privilege to manage your device.
- Applications - Applications
- Suspend/hide app - Suspend/hide app
- Block app uninstallation - Block app uninstallation
- Install/uninstall app
- Grant/revoke permissions - Grant/revoke permissions
- Clear app storage
- Install/uninstall app
- ... - ...
- User restriction - User restriction
- Network: disable configuring mobile network, disable configuring Wi-Fi, disable SMS, disable outgoing calls... - Network: disable configuring mobile network, disable configuring Wi-Fi, disable SMS, disable outgoing calls...
@@ -57,6 +59,8 @@ Use Android Device owner privilege to manage your device.
## Activate ## Activate
- Shizuku (recommended) - Shizuku (recommended)
- Dhizuku
- Root
- Execute command in adb shell: `dpm set-device-owner com.bintianqi.owndroid/.Receiver` - Execute command in adb shell: `dpm set-device-owner com.bintianqi.owndroid/.Receiver`
## FAQ ## FAQ
@@ -72,7 +76,6 @@ java.lang.IllegalStateException: Not allowed to set the device owner because the
Solutions: Solutions:
- Freeze apps who hold those accounts. - Freeze apps who hold those accounts.
- Delete these accounts. - Delete these accounts.
- Use LSPosed module [HookDPM](https://github.com/BinTianqi/HookDPM).
#### Already several users on the device #### Already several users on the device
@@ -82,7 +85,6 @@ java.lang.IllegalStateException: Not allowed to set the device owner because the
Solutions: Solutions:
- Delete secondary users. - Delete secondary users.
- Use LSPosed module [HookDPM](https://github.com/BinTianqi/HookDPM).
> [!NOTE] > [!NOTE]
> Some systems have features such as app cloning and children space, which are usually users. > Some systems have features such as app cloning and children space, which are usually users.
@@ -116,6 +118,7 @@ Solution: Use OwnDroid testkey version
| ADD_USER_RESTRICTION | `restriction` | | | ADD_USER_RESTRICTION | `restriction` | |
| CLEAR_USER_RESTRICTION | `restriction` | | | CLEAR_USER_RESTRICTION | `restriction` | |
| LOCK | | | | LOCK | | |
| REBOOT | | 7 |
[Available user restrictions](https://developer.android.com/reference/android/os/UserManager#constants_1) [Available user restrictions](https://developer.android.com/reference/android/os/UserManager#constants_1)

View File

@@ -57,6 +57,8 @@ AndroidのDevice owner特権を使用してデバイスを管理します。
## アクティベート ## アクティベート
- Shizuku (推奨) - Shizuku (推奨)
- Dhizuku
- Root
- adbシェルでコマンドを実行: `dpm set-device-owner com.bintianqi.owndroid/.Receiver` - adbシェルでコマンドを実行: `dpm set-device-owner com.bintianqi.owndroid/.Receiver`
## FAQ ## FAQ
@@ -72,7 +74,6 @@ java.lang.IllegalStateException: Not allowed to set the device owner because the
解決策: 解決策:
- これらのアカウントを保持しているアプリを凍結します。 - これらのアカウントを保持しているアプリを凍結します。
- これらのアカウントを削除します。 - これらのアカウントを削除します。
- LSPosedモジュール [HookDPM](https://github.com/BinTianqi/HookDPM) を使用します。
#### デバイスに既に複数のユーザーが存在する場合 #### デバイスに既に複数のユーザーが存在する場合
@@ -82,7 +83,6 @@ java.lang.IllegalStateException: Not allowed to set the device owner because the
解決策: 解決策:
- セカンダリユーザーを削除します。 - セカンダリユーザーを削除します。
- LSPosedモジュール [HookDPM](https://github.com/BinTianqi/HookDPM) を使用します。
> [!NOTE] > [!NOTE]
> 一部のシステムにはアプリのクローンや子供用スペースなどの機能があり、通常はユーザーとして扱われます。 > 一部のシステムにはアプリのクローンや子供用スペースなどの機能があり、通常はユーザーとして扱われます。
@@ -108,7 +108,7 @@ java.lang.IllegalStateException: Unexpected @ProvisioningPreCondition
## API ## API
| ID | Extras | 最小Androidバージョン | | ID | Extras | 最小Androidバージョン |
|------------------------|---------------|:---------------------:| |------------------------|---------------|:--------------:|
| HIDE | `package` | | | HIDE | `package` | |
| UNHIDE | `package` | | | UNHIDE | `package` | |
| SUSPEND | `package` | 7 | | SUSPEND | `package` | 7 |
@@ -116,6 +116,7 @@ java.lang.IllegalStateException: Unexpected @ProvisioningPreCondition
| ADD_USER_RESTRICTION | `restriction` | | | ADD_USER_RESTRICTION | `restriction` | |
| CLEAR_USER_RESTRICTION | `restriction` | | | CLEAR_USER_RESTRICTION | `restriction` | |
| LOCK | | | | LOCK | | |
| REBOOT | | 7 |
[利用可能なユーザー制限](https://developer.android.com/reference/android/os/UserManager#constants_1) [利用可能なユーザー制限](https://developer.android.com/reference/android/os/UserManager#constants_1)

View File

@@ -16,6 +16,7 @@
- 系统 - 系统
- 选项禁用摄像头、禁止截屏、全局静音、禁用USB信号... - 选项禁用摄像头、禁止截屏、全局静音、禁用USB信号...
- 锁定任务模式
- 管理CA证书 - 管理CA证书
- _清除数据_ - _清除数据_
- ... - ...
@@ -33,8 +34,9 @@
- 应用管理 - 应用管理
- 挂起/隐藏应用 - 挂起/隐藏应用
- 阻止应用卸载 - 阻止应用卸载
- 安装/卸载应用
- 授予/撤销权限 - 授予/撤销权限
- 清除应用存储
- 安装/卸载应用
- ... - ...
- 用户限制 - 用户限制
- 网络禁止配置移动网络、禁止配置Wi-Fi、禁用短信、禁止拨出电话... - 网络禁止配置移动网络、禁止配置Wi-Fi、禁用短信、禁止拨出电话...
@@ -57,6 +59,8 @@
## 激活 ## 激活
- Shizuku (推荐) - Shizuku (推荐)
- Dhizuku
- Root
- 在ADB命令行中执行命令: `dpm set-device-owner com.bintianqi.owndroid/.Receiver` - 在ADB命令行中执行命令: `dpm set-device-owner com.bintianqi.owndroid/.Receiver`
## FAQ ## FAQ
@@ -72,7 +76,6 @@ java.lang.IllegalStateException: Not allowed to set the device owner because the
解决办法: 解决办法:
- 冻结持有这些账号的app。 - 冻结持有这些账号的app。
- 删除这些账号。 - 删除这些账号。
- 使用LSPosed模块 [HookDPM](https://github.com/BinTianqi/HookDPM)。
#### 设备上有多个用户 #### 设备上有多个用户
@@ -82,7 +85,6 @@ java.lang.IllegalStateException: Not allowed to set the device owner because the
解决办法: 解决办法:
- 删除次级用户。 - 删除次级用户。
- 使用LSPosed模块[HookDPM](https://github.com/BinTianqi/HookDPM)。
> [!NOTE] > [!NOTE]
> 一些系统有应用克隆、儿童空间等功能,它们通常是用户。 > 一些系统有应用克隆、儿童空间等功能,它们通常是用户。
@@ -116,6 +118,7 @@ java.lang.IllegalStateException: Unexpected @ProvisioningPreCondition
| ADD_USER_RESTRICTION | `restriction` | | | ADD_USER_RESTRICTION | `restriction` | |
| CLEAR_USER_RESTRICTION | `restriction` | | | CLEAR_USER_RESTRICTION | `restriction` | |
| LOCK | | | | LOCK | | |
| REBOOT | | 7 |
[可用的用户限制](https://developer.android.google.cn/reference/android/os/UserManager#constants_1) [可用的用户限制](https://developer.android.google.cn/reference/android/os/UserManager#constants_1)

View File

@@ -24,8 +24,8 @@ android {
applicationId = "com.bintianqi.owndroid" applicationId = "com.bintianqi.owndroid"
minSdk = 21 minSdk = 21
targetSdk = 35 targetSdk = 35
versionCode = 38 versionCode = 39
versionName = "6.6" versionName = "7.0"
multiDexEnabled = false multiDexEnabled = false
} }

View File

@@ -22,14 +22,15 @@ class ApiReceiver: BroadcastReceiver() {
if(!app.isNullOrEmpty()) log += "\npackage: $app" if(!app.isNullOrEmpty()) log += "\npackage: $app"
try { try {
@SuppressWarnings("NewApi") @SuppressWarnings("NewApi")
val ok = when(intent.action) { val ok = when(intent.action?.removePrefix("com.bintianqi.owndroid.action.")) {
"com.bintianqi.owndroid.action.HIDE" -> dpm.setApplicationHidden(receiver, app, true) "HIDE" -> dpm.setApplicationHidden(receiver, app, true)
"com.bintianqi.owndroid.action.UNHIDE" -> dpm.setApplicationHidden(receiver, app, false) "UNHIDE" -> dpm.setApplicationHidden(receiver, app, false)
"com.bintianqi.owndroid.action.SUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), true).isEmpty() "SUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), true).isEmpty()
"com.bintianqi.owndroid.action.UNSUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), false).isEmpty() "UNSUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), false).isEmpty()
"com.bintianqi.owndroid.action.ADD_USER_RESTRICTION" -> { dpm.addUserRestriction(receiver, restriction); true } "ADD_USER_RESTRICTION" -> { dpm.addUserRestriction(receiver, restriction); true }
"com.bintianqi.owndroid.action.CLEAR_USER_RESTRICTION" -> { dpm.clearUserRestriction(receiver, restriction); true } "CLEAR_USER_RESTRICTION" -> { dpm.clearUserRestriction(receiver, restriction); true }
"com.bintianqi.owndroid.action.LOCK" -> { dpm.lockNow(); true } "LOCK" -> { dpm.lockNow(); true }
"REBOOT" -> { dpm.reboot(receiver); true }
else -> { else -> {
log += "\nInvalid action" log += "\nInvalid action"
false false

View File

@@ -63,8 +63,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -80,7 +80,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.net.URLDecoder import java.net.URLDecoder
import androidx.core.net.toUri
class AppInstallerActivity:FragmentActivity() { class AppInstallerActivity:FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -181,7 +180,7 @@ private fun AppInstaller(
ResultDialog(result, onResultDialogClose) ResultDialog(result, onResultDialogClose)
} }
} }
if(appLockDialog) Dialog({ appLockDialog = false }) { if(appLockDialog) {
AppLockDialog({ AppLockDialog({
appLockDialog = false appLockDialog = false
onStartInstall() onStartInstall()

View File

@@ -21,9 +21,16 @@ import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
@@ -32,14 +39,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.serialization.Serializable import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@Serializable object AppLock
@Composable @Composable
fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) { fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismiss, DialogProperties(true, false)) {
val context = LocalContext.current val context = LocalContext.current
val fm = LocalFocusManager.current val fm = LocalFocusManager.current
val fr = FocusRequester()
val sp = SharedPrefs(context) val sp = SharedPrefs(context)
var input by remember { mutableStateOf("") } var input by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) } var isError by remember { mutableStateOf(false) }
@@ -51,12 +58,15 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) {
isError = true isError = true
} }
} }
LaunchedEffect(Unit) {
fr.requestFocus()
}
BackHandler(onBack = onDismiss) BackHandler(onBack = onDismiss)
Card(Modifier.pointerInput(Unit) { detectTapGestures(onTap = { fm.clearFocus() }) }, shape = RoundedCornerShape(16.dp)) { Card(Modifier.pointerInput(Unit) { detectTapGestures(onTap = { fm.clearFocus() }) }, shape = RoundedCornerShape(16.dp)) {
Column(Modifier.padding(12.dp)) { Column(Modifier.padding(12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField( OutlinedTextField(
input, { input = it; isError = false }, Modifier.width(200.dp), input, { input = it; isError = false }, Modifier.width(200.dp).focusRequester(fr),
label = { Text(stringResource(R.string.password)) }, isError = isError, label = { Text(stringResource(R.string.password)) }, isError = isError,
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password, imeAction = if(input.length >= 4) ImeAction.Go else ImeAction.Done keyboardType = KeyboardType.Password, imeAction = if(input.length >= 4) ImeAction.Go else ImeAction.Done

View File

@@ -1,7 +1,6 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
@@ -38,6 +37,9 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -47,7 +49,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@@ -57,7 +58,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.dialog
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute import androidx.navigation.toRoute
import com.bintianqi.owndroid.dpm.AddApnSetting import com.bintianqi.owndroid.dpm.AddApnSetting
@@ -257,9 +257,13 @@ class MainActivity : FragmentActivity() {
val vm by viewModels<MyViewModel>() val vm by viewModels<MyViewModel>()
lifecycleScope.launch { delay(5000); setDefaultAffiliationID(context) } lifecycleScope.launch { delay(5000); setDefaultAffiliationID(context) }
setContent { setContent {
var appLockDialog by rememberSaveable { mutableStateOf(false) }
val theme by vm.theme.collectAsStateWithLifecycle() val theme by vm.theme.collectAsStateWithLifecycle()
OwnDroidTheme(theme) { OwnDroidTheme(theme) {
Home(vm) Home(vm) { appLockDialog = true }
if (appLockDialog) {
AppLockDialog({ appLockDialog = false }) { moveTaskToBack(true) }
}
} }
} }
} }
@@ -282,7 +286,7 @@ class MainActivity : FragmentActivity() {
@ExperimentalMaterial3Api @ExperimentalMaterial3Api
@Composable @Composable
fun Home(vm: MyViewModel) { fun Home(vm: MyViewModel, onLock: () -> Unit) {
val navController = rememberNavController() val navController = rememberNavController()
val context = LocalContext.current val context = LocalContext.current
val receiver = context.getReceiver() val receiver = context.getReceiver()
@@ -477,10 +481,6 @@ fun Home(vm: MyViewModel) {
composable<ApiSettings> { ApiSettings(::navigateUp) } composable<ApiSettings> { ApiSettings(::navigateUp) }
composable<Notifications> { NotificationsScreen(::navigateUp) } composable<Notifications> { NotificationsScreen(::navigateUp) }
composable<About> { AboutScreen(::navigateUp) } composable<About> { AboutScreen(::navigateUp) }
dialog<AppLock>(dialogProperties = DialogProperties(false, false)) {
AppLockDialog(::navigateUp) { (context as? Activity)?.moveTaskToBack(true) }
}
} }
DisposableEffect(lifecycleOwner) { DisposableEffect(lifecycleOwner) {
val sp = SharedPrefs(context) val sp = SharedPrefs(context)
@@ -489,9 +489,7 @@ fun Home(vm: MyViewModel) {
(event == Lifecycle.Event.ON_CREATE && !sp.lockPasswordHash.isNullOrEmpty()) || (event == Lifecycle.Event.ON_CREATE && !sp.lockPasswordHash.isNullOrEmpty()) ||
(event == Lifecycle.Event.ON_RESUME && sp.lockWhenLeaving) (event == Lifecycle.Event.ON_RESUME && sp.lockWhenLeaving)
) { ) {
navController.navigate(AppLock) { onLock()
launchSingleTop = true
}
} }
} }
lifecycleOwner.lifecycle.addObserver(observer) lifecycleOwner.lifecycle.addObserver(observer)

View File

@@ -13,12 +13,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.Dialog import androidx.core.content.edit
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import kotlin.system.exitProcess import kotlin.system.exitProcess
import androidx.core.content.edit
class ManageSpaceActivity: FragmentActivity() { class ManageSpaceActivity: FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -30,9 +29,7 @@ class ManageSpaceActivity: FragmentActivity() {
OwnDroidTheme(theme) { OwnDroidTheme(theme) {
var appLockDialog by remember { mutableStateOf(!SharedPrefs(this).lockPasswordHash.isNullOrEmpty()) } var appLockDialog by remember { mutableStateOf(!SharedPrefs(this).lockPasswordHash.isNullOrEmpty()) }
if(appLockDialog) { if(appLockDialog) {
Dialog(::finish) {
AppLockDialog({ appLockDialog = false }, ::finish) AppLockDialog({ appLockDialog = false }, ::finish)
}
} else { } else {
AlertDialog( AlertDialog(
text = { text = {

View File

@@ -1,9 +1,9 @@
[versions] [versions]
agp = "8.9.1" agp = "8.9.2"
kotlin = "2.1.20" kotlin = "2.1.20"
navigation-compose = "2.8.9" navigation-compose = "2.9.0"
composeBom = "2025.04.00" composeBom = "2025.05.00"
accompanist-drawablepainter = "0.35.0-alpha" accompanist-drawablepainter = "0.35.0-alpha"
accompanist-permissions = "0.37.0" accompanist-permissions = "0.37.0"
shizuku = "13.1.5" shizuku = "13.1.5"

View File

@@ -1,8 +1,8 @@
#Fri Jan 12 20:22:20 CST 2024 #Fri Jan 12 20:22:20 CST 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
#distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip #distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.14-bin.zip
#distributionUrl=https://mirrors.aliyun.com/gradle/gradle-8.13-bin.zip #distributionUrl=https://mirrors.aliyun.com/gradle/distributions/v8.14.0/gradle-8.14-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists