diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 22b23ec..7a9cc92 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,8 +24,8 @@ android { applicationId = "com.bintianqi.owndroid" minSdk = 21 targetSdk = 34 - versionCode = 32 - versionName = "6.0" + versionCode = 33 + versionName = "6.1" multiDexEnabled = false } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt index 3702ed1..e4099c8 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -12,6 +12,7 @@ import android.app.admin.DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192 import android.app.admin.DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP import android.app.admin.DevicePolicyManager.WIFI_SECURITY_OPEN import android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL +import android.app.admin.PreferentialNetworkServiceConfig import android.app.admin.WifiSsidPolicy import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST @@ -59,6 +60,10 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material.icons.filled.Add import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Icon @@ -111,6 +116,7 @@ import com.bintianqi.owndroid.ui.SubPageItem import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.TopBar import com.bintianqi.owndroid.writeClipBoard +import kotlin.math.max @Composable fun Network(navCtrl: NavHostController) { @@ -147,6 +153,7 @@ fun Network(navCtrl: NavHostController) { composable(route = "RecommendedGlobalProxy") { RecommendedGlobalProxy() } composable(route = "NetworkLog") { NetworkLog() } composable(route = "WifiAuthKeypair") { WifiAuthKeypair() } + composable(route = "PreferentialNetworkService") { PreferentialNetworkService() } composable(route = "APN") { APN() } } } @@ -217,6 +224,9 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDia if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) { SubPageItem(R.string.wifi_auth_keypair, "", R.drawable.key_fill0) { navCtrl.navigate("WifiAuthKeypair") } } + if(VERSION.SDK_INT >= 33 && (deviceOwner || profileOwner)) { + SubPageItem(R.string.preferential_network_service, "", R.drawable.globe_fill0) { navCtrl.navigate("PreferentialNetworkService") } + } if(VERSION.SDK_INT >= 28 && deviceOwner) { SubPageItem(R.string.override_apn_settings, "", R.drawable.cell_tower_fill0) { navCtrl.navigate("APN") } } @@ -232,12 +242,6 @@ private fun Switches() { val deviceOwner = context.isDeviceOwner Column(modifier = Modifier.fillMaxSize().padding(start = 20.dp, end = 16.dp)) { Spacer(Modifier.padding(vertical = 5.dp)) - if(VERSION.SDK_INT >= 33 && deviceOwner) { - SwitchItem( - R.string.preferential_network_service, "", R.drawable.globe_fill0, - { dpm.isPreferentialNetworkServiceEnabled }, { dpm.isPreferentialNetworkServiceEnabled = it }, padding = false - ) - } if(VERSION.SDK_INT>=30 && (deviceOwner || dpm.isOrgProfile(receiver))) { SwitchItem(R.string.lockdown_admin_configured_network, "", R.drawable.wifi_password_fill0, { dpm.hasLockdownAdminConfiguredNetworks(receiver) }, { dpm.setConfiguredNetworksLockdownState(receiver,it) }, padding = false @@ -453,9 +457,11 @@ private fun PrivateDNS() { try { result = dpm.setGlobalPrivateDnsModeSpecifiedHost(receiver,inputHost) Toast.makeText(context, operationResult[result], Toast.LENGTH_SHORT).show() - } catch(e:IllegalArgumentException) { + } catch(e: IllegalArgumentException) { + e.printStackTrace() Toast.makeText(context, R.string.invalid_hostname, Toast.LENGTH_SHORT).show() - } catch(e:SecurityException) { + } catch(e: SecurityException) { + e.printStackTrace() Toast.makeText(context, R.string.security_exception, Toast.LENGTH_SHORT).show() } finally { status = dnsStatus[dpm.getGlobalPrivateDnsMode(receiver)] @@ -492,9 +498,11 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController) { Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() true } catch(e: UnsupportedOperationException) { + e.printStackTrace() Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show() false } catch(e: NameNotFoundException) { + e.printStackTrace() Toast.makeText(context, R.string.not_installed, Toast.LENGTH_SHORT).show() false } @@ -609,6 +617,7 @@ private fun RecommendedGlobalProxy() { try { port = proxyPort.toInt() } catch(e: NumberFormatException) { + e.printStackTrace() Toast.makeText(context, R.string.invalid_config, Toast.LENGTH_SHORT).show() return@Button } @@ -702,7 +711,12 @@ private fun WifiAuthKeypair() { modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.padding(vertical = 5.dp)) - val isExist = try{ dpm.isKeyPairGrantedToWifiAuth(keyPair) }catch(e:java.lang.IllegalArgumentException) { false } + val isExist = try { + dpm.isKeyPairGrantedToWifiAuth(keyPair) + } catch(e: java.lang.IllegalArgumentException) { + e.printStackTrace() + false + } Text(stringResource(R.string.already_exist)+":$isExist") Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { @@ -728,6 +742,166 @@ private fun WifiAuthKeypair() { } } +@SuppressLint("NewApi") +@Composable +fun PreferentialNetworkService() { + val focusMgr = LocalFocusManager.current + val context = LocalContext.current + val dpm = context.getDPM() + var masterEnabled by remember { mutableStateOf(false) } + val configs = remember { mutableStateListOf() } + var index by remember { mutableIntStateOf(-1) } + var enabled by remember { mutableStateOf(false) } + var networkId by remember { mutableStateOf("") } + var allowFallback by remember { mutableStateOf(false) } + var blockNonMatching by remember { mutableStateOf(false) } + var excludedUids by remember { mutableStateOf("") } + var includedUids by remember { mutableStateOf("") } + fun refresh() { + val config = configs.getOrNull(index) + enabled = config?.isEnabled == true + networkId = config?.networkId?.toString() ?: "" + allowFallback = config?.isFallbackToDefaultConnectionAllowed == true + if(VERSION.SDK_INT >= 34) blockNonMatching = config?.shouldBlockNonMatchingNetworks() == true + includedUids = config?.includedUids?.joinToString("\n") ?: "" + excludedUids = config?.excludedUids?.joinToString("\n") ?: "" + } + fun saveCurrentConfig() { + val builder = PreferentialNetworkServiceConfig.Builder() + builder.setEnabled(enabled) + builder.setNetworkId(networkId.toInt()) + builder.setFallbackToDefaultConnectionAllowed(allowFallback) + if(VERSION.SDK_INT >= 34) builder.setShouldBlockNonMatchingNetworks(blockNonMatching) + builder.setIncludedUids(includedUids.lines().dropWhile { it == "" }.map { it.toInt() }.toIntArray()) + builder.setExcludedUids(excludedUids.lines().dropWhile { it == "" }.map { it.toInt() }.toIntArray()) + if(index < configs.size) configs[index] = builder.build() else configs += builder.build() + } + fun initialize() { + masterEnabled = dpm.isPreferentialNetworkServiceEnabled + configs.addAll(dpm.preferentialNetworkServiceConfigs) + index = max(0, configs.size - 1) + refresh() + } + LaunchedEffect(Unit) { initialize() } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.preferential_network_service), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + SwitchItem( + title = R.string.enabled, desc = "", icon = null, + state = masterEnabled, onCheckedChange = { masterEnabled = it }, padding = false + ) + Row( + horizontalArrangement = Arrangement.SpaceAround, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) + ) { + IconButton( + onClick = { + try { + saveCurrentConfig() + index -= 1 + refresh() + } catch(e: Exception) { + e.printStackTrace() + Toast.makeText(context, R.string.failed_to_save_current_config, Toast.LENGTH_SHORT).show() + } + }, + enabled = index > 0 + ) { + Icon(imageVector = Icons.AutoMirrored.Default.KeyboardArrowLeft, contentDescription = stringResource(R.string.previous)) + } + Text("${index + 1} / ${configs.size}") + IconButton( + onClick = { + try { + saveCurrentConfig() + index += 1 + refresh() + } catch(e: Exception) { + e.printStackTrace() + Toast.makeText(context, R.string.failed_to_save_current_config, Toast.LENGTH_SHORT).show() + } + } + ) { + Icon( + imageVector = if(index + 1 >= configs.size) Icons.Default.Add else Icons.AutoMirrored.Default.KeyboardArrowRight, + contentDescription = stringResource(R.string.previous) + ) + } + } + Row { + Button( + onClick = { + try { + saveCurrentConfig() + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + } catch(e: Exception) { + e.printStackTrace() + Toast.makeText(context, R.string.failed_to_save_current_config, Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.save_current_config)) + } + Button( + onClick = { + if(index < configs.size) configs.removeAt(index) + if(index > 0) index -= 1 + refresh() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.delete_current_config)) + } + } + SwitchItem( + title = R.string.enabled, desc = "", icon = null, + state = enabled, onCheckedChange = { enabled = it }, padding = false + ) + OutlinedTextField( + value = networkId, onValueChange = { networkId = it }, + label = { Text(stringResource(R.string.network_id)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions { focusMgr.clearFocus() }, + modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp) + ) + SwitchItem( + title = R.string.allow_fallback_to_default_connection, desc = "", icon = null, + state = allowFallback, onCheckedChange = { allowFallback = it }, padding = false + ) + if(VERSION.SDK_INT >= 34) SwitchItem( + title = R.string.block_non_matching_networks, desc = "", icon = null, + state = blockNonMatching, onCheckedChange = { blockNonMatching = it }, padding = false + ) + OutlinedTextField( + value = includedUids, onValueChange = { includedUids = it }, minLines = 2, + label = { Text(stringResource(R.string.included_uids)) }, + supportingText = { Text(stringResource(R.string.one_uid_per_line)) }, + modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp) + ) + OutlinedTextField( + value = excludedUids, onValueChange = { excludedUids = it }, minLines = 2, + label = { Text(stringResource(R.string.excluded_uids)) }, + supportingText = { Text(stringResource(R.string.one_uid_per_line)) }, + modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp) + ) + Button( + onClick = { + dpm.isPreferentialNetworkServiceEnabled = masterEnabled + dpm.preferentialNetworkServiceConfigs = configs + initialize() + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth().padding(top = 12.dp) + ) { + Text(stringResource(R.string.apply)) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + @SuppressLint("NewApi") @Composable private fun APN() { @@ -747,7 +921,7 @@ private fun APN() { Spacer(Modifier.padding(vertical = 5.dp)) SwitchItem(R.string.enable, "", null, { dpm.isOverrideApnEnabled(receiver) }, { dpm.setOverrideApnsEnabled(receiver,it) }, padding = false) Text(text = stringResource(R.string.total_apn_amount, setting.size)) - if(setting.size>0) { + if(setting.isNotEmpty()) { Text(text = stringResource(R.string.select_a_apn_or_create, setting.size)) TextField( value = inputNum, diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt index e924005..490c6b5 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -22,8 +22,6 @@ import androidx.compose.material3.MaterialTheme.typography import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource @@ -78,7 +76,7 @@ fun DpmPermissions(navCtrl:NavHostController) { composable(route = "DisableAccountManagement") { DisableAccountManagement() } composable(route = "LockScreenInfo") { LockScreenInfo() } composable(route = "SupportMsg") { SupportMsg() } - composable(route = "TransformOwnership") { TransformOwnership() } + composable(route = "TransformOwnership") { TransferOwnership() } } } } @@ -640,40 +638,32 @@ private fun DisableAccountManagement() { @SuppressLint("NewApi") @Composable -private fun TransformOwnership() { +private fun TransferOwnership() { val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current - val focusRequester = FocusRequester() + var component by remember { mutableStateOf("") } Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { - var pkg by remember { mutableStateOf("") } - var cls by remember { mutableStateOf("") } Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.transfer_ownership), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.transfer_ownership_desc)) Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( - value = pkg, onValueChange = { pkg = it }, label = { Text(stringResource(R.string.target_package_name)) }, + value = component, onValueChange = { component = it }, label = { Text(stringResource(R.string.target_component_name)) }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - keyboardActions = KeyboardActions(onNext = { focusRequester.requestFocus() }) - ) - Spacer(Modifier.padding(vertical = 2.dp)) - OutlinedTextField( - value = cls, onValueChange = {cls = it }, label = { Text(stringResource(R.string.target_class_name)) }, - modifier = Modifier.focusRequester(focusRequester).fillMaxWidth(), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }) + keyboardActions = KeyboardActions(onNext = { focusMgr.clearFocus() }) ) Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { + val dpm = context.getDPM() + val receiver = context.getReceiver() try { - dpm.transferOwnership(receiver, ComponentName(pkg, cls),null) + dpm.transferOwnership(receiver, ComponentName.unflattenFromString(component)!!, null) Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - } catch(_:IllegalArgumentException) { + } catch(e: Exception) { + e.printStackTrace() Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() } }, diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index d97e1b5..11ae189 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -57,6 +57,8 @@ Delete Yes No + Previous + Next Etkinleştirmek İçin Tıklayın @@ -86,8 +88,7 @@ Hesap Türleri Sahipliği Devret Cihaz sahibi veya profil sahibi ayrıcalığını başka bir uygulamaya devredin. - Hedef Paket Adı - Hedef Sınıf Adı + Target component name Ekran Kilidi Bilgisi Destek Mesajı Kısa Mesaj @@ -219,7 +220,6 @@ Wi-Fi MAC adresi Minimum Wi-Fi güvenlik seviyesi Açık - Tercihli ağ hizmeti Yönetici tarafından yapılandırılmış ağı kilitle WiFi SSID politikası SSID listesi: @@ -246,6 +246,16 @@ Export logs WiFi anahtar çifti Anahtar çifti + Tercihli ağ hizmeti + Network ID + Allow fallback to default connection + Block non matching networks + Included UIDs + Excluded UIDs + One UID per line + Failed to save current config + Save current config + Delete current config APN ayarlarını geçersiz kıl APN ayarlarının toplamı: %1$s Düzenlemek istediğiniz APN ayarını seçin (1~%1$s) veya yeni bir APN ayarı oluşturmak için 0 girin. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d3a460b..cc7e330 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -54,6 +54,8 @@ 删除 + 上一个 + 下一个 点击以激活 @@ -81,8 +83,7 @@ 账号类型 转移所有权 把Device owner或Profile owner权限转移到另一个应用 - 目标包名 - 目标类名 + 目标组件名 锁屏提示信息 提供支持的消息 提供支持的短消息 @@ -214,7 +215,6 @@ Wi-Fi Mac地址 最低WiFi安全等级 开放 - 优先网络服务 锁定由管理员配置的网络 WiFi SSID策略 SSID列表: @@ -241,6 +241,16 @@ 导出日志 WiFi密钥对 密钥对 + 首选网络服务 + 网络ID + 允许回落到默认连接 + 阻止不匹配的网络 + 包含的UID + 排除的UIDs + 一行一个UID + 保存当前配置失败 + 保存当前配置 + 删除当前配置 APN设置 一共有%1$s个APN设置 选择一个你要修改的APN设置(1~%1$s)或者输入0以新建APN设置 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 69b990a..050c4c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -57,6 +57,8 @@ Delete Yes No + Previous + Next Click to activate @@ -87,8 +89,7 @@ Account type Transfer Ownership Transfer device owner or profile owner privilege to another app. - Target package name - Target class name + Target component name Lockscreen info Support Message Short message @@ -223,7 +224,6 @@ Wi-Fi Mac address Min Wi-Fi security level Open - Preferential network service Lockdown admin configured network WiFi SSID policy SSID list: @@ -250,6 +250,16 @@ Export logs WiFi keypair Keypair + Preferential network service + Network ID + Allow fallback to default connection + Block non matching networks + Included UIDs + Excluded UIDs + One UID per line + Failed to save current config + Save current config + Delete current config APN settings APN settings amount: %1$s Select an APN setting you want to edit (1~%1$s) or enter 0 to create a new APN setting. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e424ea9..4940528 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.5.0" +agp = "8.7.2" kotlin = "2.0.0" androidx-activity-compose = "1.9.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 93fa1c1..c2efe93 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Jan 12 20:22:20 CST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists