mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 19:15:58 +00:00
Fix CA certs manager and User restrictions manager
Add authentication to app installer Change version to 6.4 (36)
This commit is contained in:
@@ -10,12 +10,13 @@ import android.content.pm.PackageInstaller
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -49,6 +50,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -63,7 +65,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.URLDecoder
|
||||
|
||||
class AppInstallerActivity:ComponentActivity() {
|
||||
class AppInstallerActivity:FragmentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -82,7 +84,7 @@ class AppInstallerActivity:ComponentActivity() {
|
||||
installing, sessionMode, { vm.sessionMode.value = it },
|
||||
packages, { uri -> vm.packages.update { it.minus(uri) } },
|
||||
{ uris -> vm.packages.update { it.plus(uris) } },
|
||||
vm::startInstallationProcess, writtenPackages, writingPackage,
|
||||
{ vm.startInstallationProcess(this) }, writtenPackages, writingPackage,
|
||||
result, { vm.result.value = null }
|
||||
)
|
||||
}
|
||||
@@ -237,7 +239,19 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
|
||||
|
||||
val writtenPackages = MutableStateFlow(setOf<Uri>())
|
||||
val writingPackage = MutableStateFlow<Uri?>(null)
|
||||
fun startInstallationProcess() {
|
||||
fun startInstallationProcess(activity: FragmentActivity) {
|
||||
startAuth(activity, object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
startInstall()
|
||||
}
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
Toast.makeText(activity, R.string.failed_to_authenticate, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
private fun startInstall() {
|
||||
if(installing.value) return
|
||||
installing.value = true
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Bundle
|
||||
@@ -53,6 +54,7 @@ import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
@@ -119,7 +121,7 @@ import com.bintianqi.owndroid.dpm.UpdateNetwork
|
||||
import com.bintianqi.owndroid.dpm.UserOperation
|
||||
import com.bintianqi.owndroid.dpm.UserOptions
|
||||
import com.bintianqi.owndroid.dpm.UserRestriction
|
||||
import com.bintianqi.owndroid.dpm.UserRestrictionItem
|
||||
import com.bintianqi.owndroid.dpm.UserRestrictionScreen
|
||||
import com.bintianqi.owndroid.dpm.UserSessionMessage
|
||||
import com.bintianqi.owndroid.dpm.Users
|
||||
import com.bintianqi.owndroid.dpm.Wifi
|
||||
@@ -137,7 +139,6 @@ import com.bintianqi.owndroid.dpm.isDeviceOwner
|
||||
import com.bintianqi.owndroid.dpm.isProfileOwner
|
||||
import com.bintianqi.owndroid.dpm.setDefaultAffiliationID
|
||||
import com.bintianqi.owndroid.ui.Animations
|
||||
import com.bintianqi.owndroid.ui.MyScaffold
|
||||
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
||||
import com.rosan.dhizuku.api.Dhizuku
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -196,6 +197,17 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
||||
LaunchedEffect(backToHome) {
|
||||
if(backToHome) { navCtrl.navigateUp(); backToHomeStateFlow.value = false }
|
||||
}
|
||||
val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle()
|
||||
fun onUserRestrictionsChange(id: String, status: Boolean) {
|
||||
try {
|
||||
if(status) dpm.addUserRestriction(receiver, id)
|
||||
else dpm.clearUserRestriction(receiver, id)
|
||||
@SuppressLint("NewApi")
|
||||
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
|
||||
} catch(_: Exception) {
|
||||
context.showOperationResultToast(false)
|
||||
}
|
||||
}
|
||||
@Suppress("NewApi") NavHost(
|
||||
navController = navCtrl,
|
||||
startDestination = "HomePage",
|
||||
@@ -268,24 +280,36 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
|
||||
|
||||
composable(route = "Applications") { ApplicationManage(navCtrl, vm) }
|
||||
|
||||
composable(route = "UserRestriction") { UserRestriction(navCtrl) }
|
||||
composable(route = "UserRestriction") { UserRestriction(navCtrl, vm) }
|
||||
composable(route = "UR-Internet") {
|
||||
MyScaffold(R.string.network_and_internet, 0.dp, navCtrl) { RestrictionData.internet.forEach { UserRestrictionItem(it, vm) } }
|
||||
UserRestrictionScreen(R.string.network_and_internet, RestrictionData.internet, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Connectivity") {
|
||||
MyScaffold(R.string.connectivity, 0.dp, navCtrl) { RestrictionData.connectivity.forEach { UserRestrictionItem(it, vm) } }
|
||||
UserRestrictionScreen(R.string.connectivity, RestrictionData.connectivity, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Applications") {
|
||||
MyScaffold(R.string.applications, 0.dp, navCtrl) { RestrictionData.applications.forEach { UserRestrictionItem(it, vm) } }
|
||||
UserRestrictionScreen(R.string.applications, RestrictionData.applications, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Users") {
|
||||
MyScaffold(R.string.users, 0.dp, navCtrl) { RestrictionData.users.forEach { UserRestrictionItem(it, vm) } }
|
||||
UserRestrictionScreen(R.string.users, RestrictionData.users, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Media") {
|
||||
MyScaffold(R.string.media, 0.dp, navCtrl) { RestrictionData.media.forEach { UserRestrictionItem(it, vm) } }
|
||||
UserRestrictionScreen(R.string.media, RestrictionData.media, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
}
|
||||
composable(route = "UR-Other") {
|
||||
MyScaffold(R.string.other, 0.dp, navCtrl) { RestrictionData.other.forEach { UserRestrictionItem(it, vm) } }
|
||||
UserRestrictionScreen(R.string.other, RestrictionData.other, userRestrictions, ::onUserRestrictionsChange) {
|
||||
navCtrl.navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
composable(route = "Users") { Users(navCtrl) }
|
||||
|
||||
@@ -137,7 +137,6 @@ import com.bintianqi.owndroid.ui.NavIcon
|
||||
import com.bintianqi.owndroid.ui.RadioButtonItem
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import com.bintianqi.owndroid.uriToStream
|
||||
import com.bintianqi.owndroid.yesOrNo
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -1227,63 +1226,24 @@ fun CACert(navCtrl: NavHostController) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
var exist by remember { mutableStateOf(false) }
|
||||
var fileUri by remember { mutableStateOf<Uri?>(null) }
|
||||
var caCertByteArray by remember { mutableStateOf(ByteArray(100000)) }
|
||||
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
result.data?.data?.let { uri ->
|
||||
uriToStream(context, uri) {
|
||||
val array = it.readBytes()
|
||||
caCertByteArray = if(array.size < 10000) {
|
||||
array
|
||||
} else {
|
||||
byteArrayOf()
|
||||
}
|
||||
}
|
||||
var dialog by remember { mutableStateOf(false) }
|
||||
var caCertByteArray = remember { byteArrayOf() }
|
||||
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
uri ?: return@rememberLauncherForActivityResult
|
||||
uriToStream(context, uri) {
|
||||
caCertByteArray = it.readBytes()
|
||||
}
|
||||
dialog = true
|
||||
}
|
||||
MyScaffold(R.string.ca_cert, 8.dp, navCtrl) {
|
||||
Text(
|
||||
text = if(fileUri == null) { stringResource(R.string.please_select_ca_cert) }
|
||||
else { stringResource(R.string.cert_installed, stringResource(exist.yesOrNo)) },
|
||||
modifier = Modifier.animateContentSize()
|
||||
)
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
val caCertIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
caCertIntent.setType("*/*")
|
||||
caCertIntent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
getFileLauncher.launch(caCertIntent)
|
||||
getFileLauncher.launch("*/*")
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.select_ca_cert))
|
||||
}
|
||||
AnimatedVisibility(fileUri != null) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Button(
|
||||
onClick = {
|
||||
context.showOperationResultToast(dpm.installCaCert(receiver, caCertByteArray))
|
||||
exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.49F)
|
||||
) {
|
||||
Text(stringResource(R.string.install))
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
dpm.uninstallCaCert(receiver, caCertByteArray)
|
||||
exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
|
||||
context.showOperationResultToast(true)
|
||||
},
|
||||
enabled = exist,
|
||||
modifier = Modifier.fillMaxWidth(0.96F)
|
||||
) {
|
||||
Text(stringResource(R.string.uninstall))
|
||||
}
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
dpm.uninstallAllUserCaCerts(receiver)
|
||||
@@ -1293,6 +1253,28 @@ fun CACert(navCtrl: NavHostController) {
|
||||
) {
|
||||
Text(stringResource(R.string.uninstall_all_user_ca_cert))
|
||||
}
|
||||
if(dialog) {
|
||||
val exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
|
||||
AlertDialog(
|
||||
confirmButton = {
|
||||
TextButton({
|
||||
if(exist) {
|
||||
dpm.uninstallCaCert(receiver, caCertByteArray)
|
||||
} else {
|
||||
val result = dpm.installCaCert(receiver, caCertByteArray)
|
||||
context.showOperationResultToast(result)
|
||||
}
|
||||
dialog = false
|
||||
}) {
|
||||
Text(stringResource(if(exist) R.string.uninstall else R.string.install))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton({ dialog = false }) { Text(stringResource(R.string.cancel)) }
|
||||
},
|
||||
onDismissRequest = { dialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
package com.bintianqi.owndroid.dpm
|
||||
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.UserManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.R
|
||||
@@ -31,15 +29,19 @@ data class Restriction(
|
||||
val requiresApi: Int = 0
|
||||
)
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun UserRestriction(navCtrl:NavHostController) {
|
||||
fun UserRestriction(navCtrl:NavHostController, vm: MyViewModel) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
LaunchedEffect(Unit) {
|
||||
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
|
||||
}
|
||||
MyScaffold(R.string.user_restriction, 0.dp, navCtrl) {
|
||||
Text(text = stringResource(R.string.switch_to_disable_feature), modifier = Modifier.padding(start = 16.dp))
|
||||
if(context.isProfileOwner) { Text(text = stringResource(R.string.profile_owner_is_restricted), modifier = Modifier.padding(start = 16.dp)) }
|
||||
if(context.isProfileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver))) {
|
||||
if(context.isProfileOwner && dpm.isManagedProfile(receiver)) {
|
||||
Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 16.dp))
|
||||
}
|
||||
Spacer(Modifier.padding(vertical = 2.dp))
|
||||
@@ -54,30 +56,19 @@ fun UserRestriction(navCtrl:NavHostController) {
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun UserRestrictionItem(restriction: Restriction, vm: MyViewModel) {
|
||||
val context = LocalContext.current
|
||||
val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle()
|
||||
Box(modifier = Modifier.padding(start = 22.dp, end = 16.dp)) {
|
||||
SwitchItem(
|
||||
restriction.name, restriction.id, restriction.icon,
|
||||
userRestrictions.getBoolean(restriction.id),
|
||||
{
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
try {
|
||||
if(it) {
|
||||
dpm.addUserRestriction(receiver, restriction.id)
|
||||
} else {
|
||||
dpm.clearUserRestriction(receiver, restriction.id)
|
||||
}
|
||||
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
|
||||
} catch(_: SecurityException) {
|
||||
if(context.isProfileOwner) {
|
||||
Toast.makeText(context, R.string.require_device_owner, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}, padding = false
|
||||
)
|
||||
fun UserRestrictionScreen(
|
||||
title: Int, items: List<Restriction>, restrictions: Bundle,
|
||||
onRestrictionChange: (String, Boolean) -> Unit, onNavigateUp: () -> Unit
|
||||
) {
|
||||
MyScaffold(title, 0.dp, onNavigateUp, false) {
|
||||
items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction ->
|
||||
SwitchItem(
|
||||
restriction.name, restriction.id, restriction.icon,
|
||||
restrictions.getBoolean(restriction.id), { onRestrictionChange(restriction.id, it) }, padding = true
|
||||
)
|
||||
/*Box(modifier = Modifier.padding(start = 22.dp, end = 16.dp)) {
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user