Confirm changing user icon in dialog

Disable API on deactivate
Fix changing theme
Fix auth in app installer
This commit is contained in:
BinTianqi
2025-02-15 19:14:38 +08:00
parent 44d2ab7e2e
commit 7995bfbdfe
11 changed files with 131 additions and 196 deletions

View File

@@ -241,7 +241,8 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
val writtenPackages = MutableStateFlow(setOf<Uri>()) val writtenPackages = MutableStateFlow(setOf<Uri>())
val writingPackage = MutableStateFlow<Uri?>(null) val writingPackage = MutableStateFlow<Uri?>(null)
fun startInstallationProcess(activity: FragmentActivity) { fun startInstallationProcess(activity: FragmentActivity) {
startAuth(activity, object : BiometricPrompt.AuthenticationCallback() { val sp = SharedPrefs(getApplication<Application>())
if(sp.auth) startAuth(activity, object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result) super.onAuthenticationSucceeded(result)
startInstall() startInstall()
@@ -251,6 +252,7 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
Toast.makeText(activity, R.string.failed_to_authenticate, Toast.LENGTH_SHORT).show() Toast.makeText(activity, R.string.failed_to_authenticate, Toast.LENGTH_SHORT).show()
} }
}) })
else startInstall()
} }
private fun startInstall() { private fun startInstall() {
if(installing.value) return if(installing.value) return

View File

@@ -76,8 +76,6 @@ import com.bintianqi.owndroid.dpm.ChangeTime
import com.bintianqi.owndroid.dpm.ChangeTimeScreen import com.bintianqi.owndroid.dpm.ChangeTimeScreen
import com.bintianqi.owndroid.dpm.ChangeTimeZone import com.bintianqi.owndroid.dpm.ChangeTimeZone
import com.bintianqi.owndroid.dpm.ChangeTimeZoneScreen import com.bintianqi.owndroid.dpm.ChangeTimeZoneScreen
import com.bintianqi.owndroid.dpm.ChangeUserIcon
import com.bintianqi.owndroid.dpm.ChangeUserIconScreen
import com.bintianqi.owndroid.dpm.ChangeUsername import com.bintianqi.owndroid.dpm.ChangeUsername
import com.bintianqi.owndroid.dpm.ChangeUsernameScreen import com.bintianqi.owndroid.dpm.ChangeUsernameScreen
import com.bintianqi.owndroid.dpm.ContentProtectionPolicy import com.bintianqi.owndroid.dpm.ContentProtectionPolicy
@@ -255,31 +253,19 @@ class MainActivity : FragmentActivity() {
@ExperimentalMaterial3Api @ExperimentalMaterial3Api
@Composable @Composable
fun Home(activity: FragmentActivity, vm: MyViewModel) { fun Home(activity: FragmentActivity, vm: MyViewModel) {
val navCtrl = rememberNavController() val navController = rememberNavController()
val context = LocalContext.current val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver() val receiver = context.getReceiver()
val sp = SharedPrefs(context)
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
val backToHome by backToHomeStateFlow.collectAsState() val backToHome by backToHomeStateFlow.collectAsState()
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(backToHome) { LaunchedEffect(backToHome) {
if(backToHome) { navCtrl.navigateUp(); backToHomeStateFlow.value = false } if(backToHome) { navController.navigateUp(); backToHomeStateFlow.value = false }
} }
val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle() val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle()
fun onUserRestrictionsChange(id: String, status: Boolean) { fun navigateUp() { navController.navigateUp() }
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)
}
}
fun navigateUp() { navCtrl.navigateUp() }
@Suppress("NewApi") NavHost( @Suppress("NewApi") NavHost(
navController = navCtrl, navController = navController,
startDestination = Home, startDestination = Home,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -291,15 +277,15 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
popEnterTransition = Animations.navHostPopEnterTransition, popEnterTransition = Animations.navHostPopEnterTransition,
popExitTransition = Animations.navHostPopExitTransition popExitTransition = Animations.navHostPopExitTransition
) { ) {
composable<Home> { HomeScreen { navCtrl.navigate(it) } } composable<Home> { HomeScreen { navController.navigate(it) } }
composable<Permissions> { composable<Permissions> {
PermissionsScreen(::navigateUp, { navCtrl.navigate(it) }) { PermissionsScreen(::navigateUp, { navController.navigate(it) }) {
val dest = navCtrl.graph.findNode(ShizukuScreen)!!.id val dest = navController.graph.findNode(ShizukuScreen)!!.id
navCtrl.navigate(dest, it) navController.navigate(dest, it)
} }
} }
composable<ShizukuScreen> { ShizukuScreen(it.arguments!!, ::navigateUp) { navCtrl.navigate(it) } } composable<ShizukuScreen> { ShizukuScreen(it.arguments!!, ::navigateUp) { navController.navigate(it) } }
composable<Accounts>(mapOf(serializableNavTypePair<List<Accounts.Account>>())) { AccountsScreen(it.toRoute(), ::navigateUp) } composable<Accounts>(mapOf(serializableNavTypePair<List<Accounts.Account>>())) { AccountsScreen(it.toRoute(), ::navigateUp) }
composable<DeviceAdmin> { DeviceAdminScreen(::navigateUp) } composable<DeviceAdmin> { DeviceAdminScreen(::navigateUp) }
composable<ProfileOwner> { ProfileOwnerScreen(::navigateUp) } composable<ProfileOwner> { ProfileOwnerScreen(::navigateUp) }
@@ -310,7 +296,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<SupportMessage> { SupportMessageScreen(::navigateUp) } composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
composable<TransferOwnership> { TransferOwnershipScreen(::navigateUp) } composable<TransferOwnership> { TransferOwnershipScreen(::navigateUp) }
composable<SystemManager> { SystemManagerScreen(::navigateUp) { navCtrl.navigate(it) } } composable<SystemManager> { SystemManagerScreen(::navigateUp) { navController.navigate(it) } }
composable<SystemOptions> { SystemOptionsScreen(::navigateUp) } composable<SystemOptions> { SystemOptionsScreen(::navigateUp) }
composable<Keyguard> { KeyguardScreen(::navigateUp) } composable<Keyguard> { KeyguardScreen(::navigateUp) }
composable<HardwareMonitor> { HardwareMonitorScreen(::navigateUp) } composable<HardwareMonitor> { HardwareMonitorScreen(::navigateUp) }
@@ -330,20 +316,20 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<FrpPolicy> { FrpPolicyScreen(::navigateUp) } composable<FrpPolicy> { FrpPolicyScreen(::navigateUp) }
composable<WipeData> { WipeDataScreen(::navigateUp) } composable<WipeData> { WipeDataScreen(::navigateUp) }
composable<Network> { NetworkScreen(::navigateUp) { navCtrl.navigate(it) } } composable<Network> { NetworkScreen(::navigateUp) { navController.navigate(it) } }
composable<WiFi> { composable<WiFi> {
WifiScreen(::navigateUp, { navCtrl.navigate(it) }) { WifiScreen(::navigateUp, { navController.navigate(it) }) {
val dest = navCtrl.graph.findNode(AddNetwork)!!.id val dest = navController.graph.findNode(AddNetwork)!!.id
navCtrl.navigate(dest, it) navController.navigate(dest, it)
} }
} }
composable<NetworkOptions> { NetworkOptionsScreen(::navigateUp) } composable<NetworkOptions> { NetworkOptionsScreen(::navigateUp) }
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) } composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) } composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) } composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp) { navCtrl.navigate(it) } } composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp) { navController.navigate(it) } }
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) { composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
NetworkStatsViewerScreen(it.toRoute()) { navCtrl.navigateUp() } NetworkStatsViewerScreen(it.toRoute()) { navController.navigateUp() }
} }
composable<PrivateDns> { PrivateDnsScreen(::navigateUp) } composable<PrivateDns> { PrivateDnsScreen(::navigateUp) }
composable<AlwaysOnVpnPackage> { AlwaysOnVpnPackageScreen(::navigateUp) } composable<AlwaysOnVpnPackage> { AlwaysOnVpnPackageScreen(::navigateUp) }
@@ -353,7 +339,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<PreferentialNetworkService> { PreferentialNetworkServiceScreen(::navigateUp) } composable<PreferentialNetworkService> { PreferentialNetworkServiceScreen(::navigateUp) }
composable<OverrideApn> { OverrideApnScreen(::navigateUp) } composable<OverrideApn> { OverrideApnScreen(::navigateUp) }
composable<WorkProfile> { WorkProfileScreen(::navigateUp) { navCtrl.navigate(it) } } composable<WorkProfile> { WorkProfileScreen(::navigateUp) { navController.navigate(it) } }
composable<OrganizationOwnedProfile> { OrganizationOwnedProfileScreen(::navigateUp) } composable<OrganizationOwnedProfile> { OrganizationOwnedProfileScreen(::navigateUp) }
composable<CreateWorkProfile> { CreateWorkProfileScreen(::navigateUp) } composable<CreateWorkProfile> { CreateWorkProfileScreen(::navigateUp) }
composable<SuspendPersonalApp> { SuspendPersonalAppScreen(::navigateUp) } composable<SuspendPersonalApp> { SuspendPersonalAppScreen(::navigateUp) }
@@ -364,27 +350,36 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<UserRestriction> { composable<UserRestriction> {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
vm.userRestrictions.value = dpm.getUserRestrictions(receiver) vm.userRestrictions.value = context.getDPM().getUserRestrictions(receiver)
} }
UserRestrictionScreen(::navigateUp) { title, items -> UserRestrictionScreen(::navigateUp) { title, items ->
navCtrl.navigate(UserRestrictionOptions(title, items)) navController.navigate(UserRestrictionOptions(title, items))
} }
} }
composable<UserRestrictionOptions>(mapOf(serializableNavTypePair<List<Restriction>>())) { composable<UserRestrictionOptions>(mapOf(serializableNavTypePair<List<Restriction>>())) {
UserRestrictionOptionsScreen(it.toRoute(), userRestrictions, ::onUserRestrictionsChange, ::navigateUp) UserRestrictionOptionsScreen(it.toRoute(), userRestrictions, ::navigateUp) { id, status ->
try {
val dpm = context.getDPM()
if(status) dpm.addUserRestriction(receiver, id)
else dpm.clearUserRestriction(receiver, id)
@SuppressLint("NewApi")
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
} catch(_: Exception) {
context.showOperationResultToast(false)
}
}
} }
composable<Users> { UsersScreen(::navigateUp) { navCtrl.navigate(it) } } composable<Users> { UsersScreen(::navigateUp) { navController.navigate(it) } }
composable<UserInfo> { UserInfoScreen(::navigateUp) } composable<UserInfo> { UserInfoScreen(::navigateUp) }
composable<UsersOptions> { UsersOptionsScreen(::navigateUp) } composable<UsersOptions> { UsersOptionsScreen(::navigateUp) }
composable<UserOperation> { UserOperationScreen(::navigateUp) } composable<UserOperation> { UserOperationScreen(::navigateUp) }
composable<CreateUser> { CreateUserScreen(::navigateUp) } composable<CreateUser> { CreateUserScreen(::navigateUp) }
composable<ChangeUsername> { ChangeUsernameScreen(::navigateUp) } composable<ChangeUsername> { ChangeUsernameScreen(::navigateUp) }
composable<ChangeUserIcon> { ChangeUserIconScreen(::navigateUp) }
composable<UserSessionMessage> { UserSessionMessageScreen(::navigateUp) } composable<UserSessionMessage> { UserSessionMessageScreen(::navigateUp) }
composable<AffiliationId> { AffiliationIdScreen(::navigateUp) } composable<AffiliationId> { AffiliationIdScreen(::navigateUp) }
composable<Password> { PasswordScreen(::navigateUp) { navCtrl.navigate(it) } } composable<Password> { PasswordScreen(::navigateUp) { navController.navigate(it) } }
composable<PasswordInfo> { PasswordInfoScreen(::navigateUp) } composable<PasswordInfo> { PasswordInfoScreen(::navigateUp) }
composable<ResetPasswordToken> { ResetPasswordTokenScreen(::navigateUp) } composable<ResetPasswordToken> { ResetPasswordTokenScreen(::navigateUp) }
composable<ResetPassword> { ResetPasswordScreen(::navigateUp) } composable<ResetPassword> { ResetPasswordScreen(::navigateUp) }
@@ -392,7 +387,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<KeyguardDisabledFeatures> { KeyguardDisabledFeaturesScreen(::navigateUp) } composable<KeyguardDisabledFeatures> { KeyguardDisabledFeaturesScreen(::navigateUp) }
composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) } composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) }
composable<Settings> { SettingsScreen(::navigateUp) { navCtrl.navigate(it) } } composable<Settings> { SettingsScreen(::navigateUp) { navController.navigate(it) } }
composable<SettingsOptions> { SettingsOptionsScreen(::navigateUp) } composable<SettingsOptions> { SettingsOptionsScreen(::navigateUp) }
composable<Appearance> { composable<Appearance> {
val theme by vm.theme.collectAsStateWithLifecycle() val theme by vm.theme.collectAsStateWithLifecycle()
@@ -409,11 +404,12 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
} }
DisposableEffect(lifecycleOwner) { DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
val sp = SharedPrefs(context)
if( if(
(event == Lifecycle.Event.ON_RESUME && sp.auth && sp.lockInBackground) || (event == Lifecycle.Event.ON_RESUME && sp.auth && sp.lockInBackground) ||
(event == Lifecycle.Event.ON_CREATE && sp.auth) (event == Lifecycle.Event.ON_CREATE && sp.auth)
) { ) {
navCtrl.navigate(Authenticate) { launchSingleTop = true } navController.navigate(Authenticate) { launchSingleTop = true }
} }
} }
lifecycleOwner.lifecycle.addObserver(observer) lifecycleOwner.lifecycle.addObserver(observer)
@@ -422,6 +418,8 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
} }
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
val dpm = context.getDPM()
val sp = SharedPrefs(context)
val profileNotActivated = !sp.managedProfileActivated && context.isProfileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver)) val profileNotActivated = !sp.managedProfileActivated && context.isProfileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver))
if(profileNotActivated) { if(profileNotActivated) {
dpm.setProfileEnabled(receiver) dpm.setProfileEnabled(receiver)

View File

@@ -11,7 +11,6 @@ import android.os.PersistableBundle
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.bintianqi.owndroid.dpm.handleNetworkLogs import com.bintianqi.owndroid.dpm.handleNetworkLogs
import com.bintianqi.owndroid.dpm.isDeviceAdmin
import com.bintianqi.owndroid.dpm.isDeviceOwner import com.bintianqi.owndroid.dpm.isDeviceOwner
import com.bintianqi.owndroid.dpm.isProfileOwner import com.bintianqi.owndroid.dpm.isProfileOwner
import com.bintianqi.owndroid.dpm.processSecurityLogs import com.bintianqi.owndroid.dpm.processSecurityLogs
@@ -29,11 +28,12 @@ class Receiver : DeviceAdminReceiver() {
dpm.setLockTaskPackages(receiver, arrayOf()) dpm.setLockTaskPackages(receiver, arrayOf())
dpm.setLockTaskPackages(receiver, packages) dpm.setLockTaskPackages(receiver, packages)
} }
if(!context.isDeviceOwner && !context.isProfileOwner) SharedPrefs(context).isApiEnabled = false
} }
override fun onEnabled(context: Context, intent: Intent) { override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent) super.onEnabled(context, intent)
if(context.isDeviceAdmin || context.isProfileOwner || context.isDeviceOwner){ if(context.isProfileOwner || context.isDeviceOwner){
Toast.makeText(context, context.getString(R.string.onEnabled), Toast.LENGTH_SHORT).show() Toast.makeText(context, context.getString(R.string.onEnabled), Toast.LENGTH_SHORT).show()
} }
} }

View File

@@ -69,8 +69,9 @@ fun SettingsOptionsScreen(onNavigateUp: () -> Unit) {
@Serializable object Appearance @Serializable object Appearance
@Composable @Composable
fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) { fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) {
var darkThemeMenu by remember { mutableStateOf(false) } var darkThemeMenu by remember { mutableStateOf(false) }
var theme by remember { mutableStateOf(currentTheme) }
val darkThemeTextID = when(theme.darkTheme) { val darkThemeTextID = when(theme.darkTheme) {
1 -> R.string.on 1 -> R.string.on
0 -> R.string.off 0 -> R.string.off
@@ -78,7 +79,11 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChan
} }
MyScaffold(R.string.appearance, 0.dp, onNavigateUp) { MyScaffold(R.string.appearance, 0.dp, onNavigateUp) {
if(VERSION.SDK_INT >= 31) { if(VERSION.SDK_INT >= 31) {
SwitchItem(R.string.material_you_color, state = theme.materialYou, onCheckedChange = { onThemeChange(theme.copy(materialYou = it)) }) SwitchItem(
R.string.material_you_color,
state = theme.materialYou,
onCheckedChange = { theme = theme.copy(materialYou = it) }
)
} }
Box { Box {
FunctionItem(R.string.dark_theme, stringResource(darkThemeTextID)) { darkThemeMenu = true } FunctionItem(R.string.dark_theme, stringResource(darkThemeTextID)) { darkThemeMenu = true }
@@ -89,28 +94,33 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChan
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.follow_system)) }, text = { Text(stringResource(R.string.follow_system)) },
onClick = { onClick = {
onThemeChange(theme.copy(darkTheme = -1)) theme = theme.copy(darkTheme = -1)
darkThemeMenu = false darkThemeMenu = false
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.on)) }, text = { Text(stringResource(R.string.on)) },
onClick = { onClick = {
onThemeChange(theme.copy(darkTheme = 1)) theme = theme.copy(darkTheme = 1)
darkThemeMenu = false darkThemeMenu = false
} }
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.off)) }, text = { Text(stringResource(R.string.off)) },
onClick = { onClick = {
onThemeChange(theme.copy(darkTheme = 0)) theme = theme.copy(darkTheme = 0)
darkThemeMenu = false darkThemeMenu = false
} }
) )
} }
} }
AnimatedVisibility(theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())) { AnimatedVisibility(theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())) {
SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { onThemeChange(theme.copy(blackTheme = it)) }) SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { theme = theme.copy(blackTheme = it) })
}
AnimatedVisibility(theme != currentTheme, Modifier.fillMaxWidth().padding(8.dp)) {
Button({onThemeChange(theme)}) {
Text(stringResource(R.string.apply))
}
} }
} }
} }

View File

@@ -1598,16 +1598,14 @@ fun NetworkLoggingScreen(onNavigateUp: () -> Unit) {
val logFile = context.filesDir.resolve("NetworkLogs.json") val logFile = context.filesDir.resolve("NetworkLogs.json")
var fileSize by remember { mutableLongStateOf(0) } var fileSize by remember { mutableLongStateOf(0) }
LaunchedEffect(Unit) { fileSize = logFile.length() } LaunchedEffect(Unit) { fileSize = logFile.length() }
val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
result.data?.data?.let { uri -> if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".encodeToByteArray()) outStream.write("[".encodeToByteArray())
logFile.inputStream().use { it.copyTo(outStream) } logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".encodeToByteArray()) outStream.write("]".encodeToByteArray())
context.showOperationResultToast(true) context.showOperationResultToast(true)
} }
} }
}
MyScaffold(R.string.network_logging, 8.dp, onNavigateUp) { MyScaffold(R.string.network_logging, 8.dp, onNavigateUp) {
SwitchItem( SwitchItem(
R.string.enable, R.string.enable,
@@ -1619,11 +1617,7 @@ fun NetworkLoggingScreen(onNavigateUp: () -> Unit) {
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button( Button(
onClick = { onClick = {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) exportNetworkLogsLauncher.launch("NetworkLogs.json")
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json")
exportNetworkLogsLauncher.launch(intent)
}, },
enabled = fileSize > 0, enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F) modifier = Modifier.fillMaxWidth(0.49F)

View File

@@ -1299,23 +1299,19 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) {
var fileSize by remember { mutableLongStateOf(0) } var fileSize by remember { mutableLongStateOf(0) }
LaunchedEffect(Unit) { fileSize = logFile.length() } LaunchedEffect(Unit) { fileSize = logFile.length() }
var preRebootSecurityLogs by remember { mutableStateOf(byteArrayOf()) } var preRebootSecurityLogs by remember { mutableStateOf(byteArrayOf()) }
val exportPreRebootSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> val exportPreRebootSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
result.data?.data?.let { uri -> if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
preRebootSecurityLogs.inputStream().copyTo(outStream) preRebootSecurityLogs.inputStream().copyTo(outStream)
} }
} }
} val exportSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
val exportSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
result.data?.data?.let { uri ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".toByteArray()) outStream.write("[".toByteArray())
logFile.inputStream().use { it.copyTo(outStream) } logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".toByteArray()) outStream.write("]".toByteArray())
context.showOperationResultToast(true) context.showOperationResultToast(true)
} }
} }
}
MyScaffold(R.string.security_logging, 8.dp, onNavigateUp) { MyScaffold(R.string.security_logging, 8.dp, onNavigateUp) {
SwitchItem( SwitchItem(
R.string.enable, R.string.enable,
@@ -1326,11 +1322,7 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) {
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button( Button(
onClick = { onClick = {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) exportSecurityLogs.launch("SecurityLogs.json")
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "SecurityLogs.json")
exportSecurityLogs.launch(intent)
}, },
enabled = fileSize > 0, enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F) modifier = Modifier.fillMaxWidth(0.49F)
@@ -1362,11 +1354,7 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) {
processSecurityLogs(logs, outputStream) processSecurityLogs(logs, outputStream)
outputStream.write("]".encodeToByteArray()) outputStream.write("]".encodeToByteArray())
preRebootSecurityLogs = outputStream.toByteArray() preRebootSecurityLogs = outputStream.toByteArray()
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) exportPreRebootSecurityLogs.launch("PreRebootSecurityLogs.json")
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "PreRebootSecurityLogs.json")
exportPreRebootSecurityLogs.launch(intent)
} }
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@@ -1742,16 +1730,11 @@ fun InstallSystemUpdateScreen(onNavigateUp: () -> Unit) {
} }
} }
var uri by remember { mutableStateOf<Uri?>(null) } var uri by remember { mutableStateOf<Uri?>(null) }
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri = it }
uri = it.data?.data
}
MyScaffold(R.string.install_system_update, 8.dp, onNavigateUp) { MyScaffold(R.string.install_system_update, 8.dp, onNavigateUp) {
Button( Button(
onClick = { onClick = {
val intent = Intent(Intent.ACTION_GET_CONTENT) getFileLauncher.launch("application/zip")
intent.setType("application/zip")
intent.addCategory(Intent.CATEGORY_OPENABLE)
getFileLauncher.launch(intent)
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {

View File

@@ -73,7 +73,7 @@ data class UserRestrictionOptions(
@Composable @Composable
fun UserRestrictionOptionsScreen( fun UserRestrictionOptionsScreen(
data: UserRestrictionOptions, restrictions: Bundle, data: UserRestrictionOptions, restrictions: Bundle,
onRestrictionChange: (String, Boolean) -> Unit, onNavigateUp: () -> Unit onNavigateUp: () -> Unit, onRestrictionChange: (String, Boolean) -> Unit
) { ) {
MyScaffold(data.title, 0.dp, onNavigateUp, false) { MyScaffold(data.title, 0.dp, onNavigateUp, false) {
data.items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction -> data.items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction ->

View File

@@ -2,7 +2,6 @@ package com.bintianqi.owndroid.dpm
import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Binder import android.os.Binder
@@ -10,13 +9,11 @@ import android.os.Build.VERSION
import android.os.Process import android.os.Process
import android.os.UserHandle import android.os.UserHandle
import android.os.UserManager import android.os.UserManager
import android.provider.MediaStore
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -36,7 +33,6 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -56,7 +52,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
@@ -115,7 +110,16 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) } FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) }
} }
if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) { if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { onNavigate(ChangeUserIcon) } var changeUserIconDialog by remember { mutableStateOf(false) }
var bitmap: Bitmap? by remember { mutableStateOf(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
if(it != null) uriToStream(context, it) { stream ->
bitmap = BitmapFactory.decodeStream(stream)
if(bitmap != null) changeUserIconDialog = true
}
}
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { launcher.launch("image/*") }
if(changeUserIconDialog == true) ChangeUserIconDialog(bitmap!!) { changeUserIconDialog = false }
} }
if(VERSION.SDK_INT >= 28 && deviceOwner) { if(VERSION.SDK_INT >= 28 && deviceOwner) {
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) } FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) }
@@ -568,56 +572,36 @@ fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
} }
} }
@Serializable object ChangeUserIcon
@RequiresApi(23) @RequiresApi(23)
@Composable @Composable
fun ChangeUserIconScreen(onNavigateUp: () -> Unit) { private fun ChangeUserIconDialog(bitmap: Bitmap, onClose: () -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val dpm = context.getDPM() AlertDialog(
val receiver = context.getReceiver() title = { Text(stringResource(R.string.change_user_icon)) },
var getContent by remember { mutableStateOf(false) } text = {
var bitmap by remember { mutableStateOf<Bitmap?>(null) } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
it.data?.data?.let {
uriToStream(context, it) { stream ->
bitmap = BitmapFactory.decodeStream(stream)
}
}
}
MyScaffold(R.string.change_user_icon, 8.dp, onNavigateUp) {
CheckBoxItem(R.string.file_picker_instead_gallery, getContent) { getContent = it }
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
val intent = Intent(if(getContent) Intent.ACTION_GET_CONTENT else Intent.ACTION_PICK)
if(getContent) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
getFileLauncher.launch(intent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.select_picture))
}
AnimatedVisibility(visible = bitmap != null, modifier = Modifier.align(Alignment.CenterHorizontally)) {
Card(modifier = Modifier.padding(top = 8.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10.dp)) {
Image( Image(
bitmap = bitmap!!.asImageBitmap(), contentDescription = "User icon", bitmap = bitmap.asImageBitmap(), contentDescription = null,
modifier = Modifier.padding(end = 12.dp).size(80.dp).clip(RoundedCornerShape(50)) modifier = Modifier.size(80.dp).clip(RoundedCornerShape(50))
) )
Button( }
onClick = { },
dpm.setUserIcon(receiver, bitmap) confirmButton = {
TextButton({
context.getDPM().setUserIcon(context.getReceiver(), bitmap)
context.showOperationResultToast(true) context.showOperationResultToast(true)
onClose()
}) {
Text(stringResource(R.string.confirm))
} }
) { },
Text(stringResource(R.string.apply)) dismissButton = {
} TextButton(onClose) {
} Text(stringResource(R.string.cancel))
}
}
} }
},
onDismissRequest = onClose
)
} }
@StringRes @StringRes

View File

@@ -14,6 +14,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -67,16 +68,10 @@ fun FunctionItem(
} }
@Composable @Composable
fun NavIcon(operation: () -> Unit) { fun NavIcon(onClick: () -> Unit) {
Icon( IconButton(onClick) {
painter = painterResource(R.drawable.arrow_back_fill0), Icon(Icons.AutoMirrored.Default.ArrowBack, null)
contentDescription = "Back arrow", }
modifier = Modifier
.padding(horizontal = 6.dp)
.clip(RoundedCornerShape(50))
.clickable(onClick = operation)
.padding(5.dp)
)
} }
@Composable @Composable

View File

@@ -6,12 +6,19 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.ThemeSettings import com.bintianqi.owndroid.ThemeSettings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
private val lightScheme = lightColorScheme( private val lightScheme = lightColorScheme(
primary = primaryLight, primary = primaryLight,
@@ -96,28 +103,24 @@ fun OwnDroidTheme(
) { ) {
val darkTheme = theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme()) val darkTheme = theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())
val context = LocalContext.current val context = LocalContext.current
var colorScheme = when { val colorScheme = when {
theme.materialYou && VERSION.SDK_INT >= 31 -> { theme.materialYou && VERSION.SDK_INT >= 31 -> {
if(darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) if(darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} }
darkTheme -> darkScheme darkTheme -> darkScheme
else -> lightScheme else -> lightScheme
} }.let {
if(darkTheme && theme.blackTheme) { if(darkTheme && theme.blackTheme) it.copy(background = Color.Black) else it
colorScheme = colorScheme.copy(background = Color.Black) }.let {
} if(!darkTheme) it.copy(background = it.primary.copy(alpha = 0.05f)) else it
if(!darkTheme) {
colorScheme = colorScheme.copy(background = colorScheme.primary.copy(alpha = 0.05f))
} }
val view = LocalView.current val view = LocalView.current
SideEffect { SideEffect {
val window = (view.context as Activity).window val window = (view.context as Activity).window
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
} }
MaterialTheme( MaterialTheme(
colorScheme = colorScheme, colorScheme = colorScheme,
typography = Typography,
content = content content = content
) )
} }

View File

@@ -1,34 +0,0 @@
package com.bintianqi.owndroid.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)