Skip to content

Commit

Permalink
Configure Preferential network service
Browse files Browse the repository at this point in the history
Transfer ownership: get ComponentName by unflatten string
Bump app version to v6.1(33)
Update AGP to 8.7.2
  • Loading branch information
BinTianqi committed Nov 2, 2024
1 parent 5e8ea06 commit e6fa6f1
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 43 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
194 changes: 184 additions & 10 deletions app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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() }
}
}
Expand Down Expand Up @@ -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") }
}
Expand All @@ -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
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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<PreferentialNetworkServiceConfig>() }
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() {
Expand All @@ -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,
Expand Down
30 changes: 10 additions & 20 deletions app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() }
}
}
}
Expand Down Expand Up @@ -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()
}
},
Expand Down
Loading

0 comments on commit e6fa6f1

Please sign in to comment.