diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e95c56e..07d4c57 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,7 +15,7 @@ android { } } namespace = "com.bintianqi.owndroid" - compileSdk = 35 + compileSdk = 36 lint.checkReleaseBuilds = false lint.disable += "All" @@ -23,7 +23,7 @@ android { defaultConfig { applicationId = "com.bintianqi.owndroid" minSdk = 21 - targetSdk = 35 + targetSdk = 36 versionCode = 39 versionName = "7.0" multiDexEnabled = false diff --git a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt index 8efda6d..91824a9 100644 --- a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt @@ -361,7 +361,10 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999) if(statusExtra == PackageInstaller.STATUS_PENDING_USER_ACTION) { @SuppressWarnings("UnsafeIntentLaunch") - context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?) + context.startActivity( + (intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?) + ?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) } else { result.value = intent writtenPackages.value = setOf() diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index 7dd9f1b..a34e6cc 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -75,6 +75,10 @@ import com.bintianqi.owndroid.dpm.ApplicationDetails import com.bintianqi.owndroid.dpm.ApplicationDetailsScreen import com.bintianqi.owndroid.dpm.ApplicationsFeatures import com.bintianqi.owndroid.dpm.ApplicationsFeaturesScreen +import com.bintianqi.owndroid.dpm.AutoTimePolicy +import com.bintianqi.owndroid.dpm.AutoTimePolicyScreen +import com.bintianqi.owndroid.dpm.AutoTimeZonePolicy +import com.bintianqi.owndroid.dpm.AutoTimeZonePolicyScreen import com.bintianqi.owndroid.dpm.BlockUninstall import com.bintianqi.owndroid.dpm.BlockUninstallScreen import com.bintianqi.owndroid.dpm.CaCert @@ -347,6 +351,8 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) { composable { HardwareMonitorScreen(::navigateUp) } composable { ChangeTimeScreen(::navigateUp) } composable { ChangeTimeZoneScreen(::navigateUp) } + composable { AutoTimePolicyScreen(::navigateUp) } + composable { AutoTimeZonePolicyScreen(::navigateUp) } //composable<> { KeyPairs(::navigateUp) } composable { ContentProtectionPolicyScreen(::navigateUp) } composable { PermissionPolicyScreen(::navigateUp) } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt index 7e86deb..abda12e 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt @@ -5,30 +5,22 @@ import android.annotation.SuppressLint import android.app.admin.ConnectEvent import android.app.admin.DevicePolicyManager import android.app.admin.DnsEvent -import android.app.admin.FactoryResetProtectionPolicy import android.app.admin.IDevicePolicyManager import android.app.admin.SecurityLog -import android.app.admin.SystemUpdatePolicy import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.IPackageInstaller import android.content.pm.PackageInstaller -import android.content.pm.ShortcutInfo -import android.content.pm.ShortcutManager -import android.graphics.drawable.Icon import android.os.Build.VERSION import android.util.Log import androidx.annotation.DrawableRes import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.core.content.pm.ShortcutManagerCompat -import androidx.core.graphics.drawable.toBitmap import com.bintianqi.owndroid.MyAdminComponent import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver import com.bintianqi.owndroid.SharedPrefs -import com.bintianqi.owndroid.ShortcutsReceiverActivity import com.bintianqi.owndroid.createShortcuts import com.bintianqi.owndroid.myPrivilege import com.rosan.dhizuku.api.Dhizuku 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 f7e3632..c8a0ed1 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -107,6 +107,7 @@ import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.rosan.dhizuku.api.Dhizuku import com.rosan.dhizuku.api.DhizukuRequestPermissionListener import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString @@ -125,6 +126,15 @@ fun WorkModesScreen( val privilege by myPrivilege.collectAsStateWithLifecycle() /** 0: none, 1: device owner, 2: circular progress indicator, 3: result, 4: deactivate, 5: command */ var dialog by remember { mutableIntStateOf(0) } + LaunchedEffect(privilege) { + if (!params.canNavigateUp && privilege.device) { + delay(1000) + if (dialog != 3) { + dialog = 0 + onActivate() // Activated by ADB command, return to home screen + } + } + } Scaffold( topBar = { TopAppBar( @@ -195,11 +205,14 @@ fun WorkModesScreen( context.showOperationResultToast(false) } } - Column(Modifier.fillMaxSize().padding(paddingValues)) { + Column(Modifier + .fillMaxSize() + .padding(paddingValues)) { if(!privilege.profile && (VERSION.SDK_INT >= 28 || !privilege.dhizuku)) Row( Modifier - .fillMaxWidth().clickable(!privilege.device || privilege.dhizuku) { dialog = 1 } - .background(if(privilege.device) colorScheme.primaryContainer else Color.Transparent) + .fillMaxWidth() + .clickable(!privilege.device || privilege.dhizuku) { dialog = 1 } + .background(if (privilege.device) colorScheme.primaryContainer else Color.Transparent) .padding(HorizontalPadding, 10.dp), Arrangement.SpaceBetween, Alignment.CenterVertically ) { @@ -217,7 +230,7 @@ fun WorkModesScreen( if(privilege.profile) Row( Modifier .fillMaxWidth() - .background(if(privilege.device) colorScheme.primaryContainer else Color.Transparent) + .background(if (privilege.device) colorScheme.primaryContainer else Color.Transparent) .padding(HorizontalPadding, 10.dp), Arrangement.SpaceBetween, Alignment.CenterVertically ) { @@ -236,7 +249,7 @@ fun WorkModesScreen( dialog = 2 activateDhizukuMode(context, ::handleResult) } - .background(if(privilege.dhizuku) colorScheme.primaryContainer else Color.Transparent) + .background(if (privilege.dhizuku) colorScheme.primaryContainer else Color.Transparent) .padding(HorizontalPadding, 10.dp), Arrangement.SpaceBetween, Alignment.CenterVertically ) { @@ -251,8 +264,9 @@ fun WorkModesScreen( context.getDPM().isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)) ) Row( Modifier - .fillMaxWidth().clickable(!privilege.work) { onNavigate(CreateWorkProfile) } - .background(if(privilege.device) colorScheme.primaryContainer else Color.Transparent) + .fillMaxWidth() + .clickable(!privilege.work) { onNavigate(CreateWorkProfile) } + .background(if (privilege.device) colorScheme.primaryContainer else Color.Transparent) .padding(HorizontalPadding, 10.dp), Arrangement.SpaceBetween, Alignment.CenterVertically ) { @@ -265,11 +279,16 @@ fun WorkModesScreen( ) } if ((privilege.device || privilege.profile) && !privilege.dhizuku) Row( - Modifier.padding(top = 20.dp).fillMaxWidth() - .clickable { onNavigate(DhizukuServerSettings) }.padding(vertical = 4.dp), + Modifier + .padding(top = 20.dp) + .fillMaxWidth() + .clickable { onNavigate(DhizukuServerSettings) } + .padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { - Icon(painterResource(R.drawable.dhizuku_icon), null, Modifier.padding(8.dp).size(28.dp)) + Icon(painterResource(R.drawable.dhizuku_icon), null, Modifier + .padding(8.dp) + .size(28.dp)) Text(stringResource(R.string.dhizuku_server), style = typography.titleLarge) } @@ -290,25 +309,22 @@ fun WorkModesScreen( coroutine.launch { activateUsingShizuku(context, ::handleResult) } - }) { + }, Modifier.padding(end = 8.dp)) { Text(stringResource(R.string.shizuku)) } - Spacer(Modifier.padding(horizontal = 2.dp)) if(!privilege.dhizuku) Button({ dialog = 2 activateUsingRoot(context, ::handleResult) - }) { + }, Modifier.padding(end = 8.dp)) { Text("Root") } - Spacer(Modifier.padding(horizontal = 2.dp)) if(VERSION.SDK_INT >= 28) Button({ dialog = 2 activateUsingDhizuku(context, ::handleResult) - }) { + }, Modifier.padding(end = 8.dp)) { Text(stringResource(R.string.dhizuku)) } - Spacer(Modifier.padding(horizontal = 2.dp)) - Button({ dialog = 5 }) { Text(stringResource(R.string.adb_command)) } + if (!privilege.dhizuku) Button({ dialog = 5 }) { Text(stringResource(R.string.adb_command)) } } }, confirmButton = { @@ -320,7 +336,9 @@ fun WorkModesScreen( if(dialog == 3) AlertDialog( title = { Text(stringResource(if(operationSucceed) R.string.succeeded else R.string.failed)) }, text = { - Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { + Column(Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState())) { Text(resultText) } }, @@ -338,6 +356,14 @@ fun WorkModesScreen( title = { Text(stringResource(R.string.deactivate)) }, text = { Text(stringResource(R.string.info_deactivate)) }, confirmButton = { + var time by remember { mutableIntStateOf(3) } + LaunchedEffect(Unit) { + for (i in (0..2).reversed()) { + delay(1000) + time = i + } + } + val timeText = if (time != 0) " (${time}s)" else "" TextButton( { if(privilege.dhizuku) { @@ -355,8 +381,11 @@ fun WorkModesScreen( handlePrivilegeChange(context) onDeactivate() }, + enabled = time == 0, colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error) - ) { Text(stringResource(R.string.confirm)) } + ) { + Text(stringResource(R.string.confirm) + timeText) + } }, dismissButton = { TextButton({ dialog = 0 }) { Text(stringResource(R.string.cancel)) } @@ -460,7 +489,7 @@ fun activateDhizukuMode(context: Context, callback: (Boolean, Boolean, String?) } } -const val ACTIVATE_DEVICE_OWNER_COMMAND = "dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver" +const val ACTIVATE_DEVICE_OWNER_COMMAND = "dpm set-device-owner com.bintianqi.owndroid/.Receiver" @Serializable object DhizukuServerSettings @@ -501,13 +530,17 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) { } else { val info = pm.getApplicationInfo(name, 0) Row( - Modifier.fillMaxWidth().padding(HorizontalPadding, 8.dp), + Modifier + .fillMaxWidth() + .padding(HorizontalPadding, 8.dp), Arrangement.SpaceBetween, Alignment.CenterVertically ) { Row(verticalAlignment = Alignment.CenterVertically) { Image( rememberDrawablePainter(info.loadIcon(pm)), null, - Modifier.padding(end = 16.dp).size(50.dp) + Modifier + .padding(end = 16.dp) + .size(50.dp) ) Text(info.loadLabel(pm).toString(), style = typography.titleLarge) } @@ -535,7 +568,9 @@ fun LockScreenInfoScreen(onNavigateUp: () -> Unit) { onValueChange = { infoText = it }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions { focusMgr.clearFocus() }, - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp) + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) ) Button( onClick = { @@ -608,7 +643,10 @@ fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdm MyScaffold(R.string.delegated_admins, onNavigateUp, 0.dp) { packages.forEach { (pkg, scopes) -> Row( - Modifier.fillMaxWidth().padding(vertical = 8.dp).padding(start = 14.dp, end = 8.dp), + Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .padding(start = 14.dp, end = 8.dp), Arrangement.SpaceBetween ) { Column { @@ -626,7 +664,9 @@ fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdm if(packages.isEmpty()) Text( stringResource(R.string.none), color = colorScheme.onSurfaceVariant, - modifier = Modifier.align(Alignment.CenterHorizontally).padding(vertical = 4.dp) + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(vertical = 4.dp) ) Row( modifier = Modifier @@ -666,12 +706,17 @@ fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) { keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), keyboardActions = KeyboardActions { fm.clearFocus() }, readOnly = updateMode, - modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = HorizontalPadding) + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp, horizontal = HorizontalPadding) ) DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { scope -> val checked = scope in scopes Row( - Modifier.fillMaxWidth().clickable { if(!checked) scopes += scope else scopes -= scope }.padding(vertical = 4.dp), + Modifier + .fillMaxWidth() + .clickable { if (!checked) scopes += scope else scopes -= scope } + .padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { Checkbox(checked, { if(it) scopes += scope else scopes -= scope }, modifier = Modifier.padding(horizontal = 4.dp)) @@ -686,7 +731,9 @@ fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) { context.getDPM().setDelegatedScopes(context.getReceiver(), input, scopes.map { it.id }) onNavigateUp() }, - modifier = Modifier.fillMaxWidth().padding(HorizontalPadding, vertical = 4.dp), + modifier = Modifier + .fillMaxWidth() + .padding(HorizontalPadding, vertical = 4.dp), enabled = input.isNotBlank() && (!updateMode || scopes.toList() != data.scopes) ) { Text(stringResource(if(updateMode) R.string.update else R.string.add)) @@ -696,7 +743,9 @@ fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) { context.getDPM().setDelegatedScopes(context.getReceiver(), input, emptyList()) onNavigateUp() }, - modifier = Modifier.fillMaxWidth().padding(HorizontalPadding), + modifier = Modifier + .fillMaxWidth() + .padding(HorizontalPadding), colors = ButtonDefaults.buttonColors(colorScheme.error, colorScheme.onError) ) { Text(stringResource(R.string.delete)) @@ -767,7 +816,9 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) { label = { Text(stringResource(R.string.short_support_msg)) }, onValueChange = { shortMsg = it }, minLines = 2, - modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp) + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 2.dp) ) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Button( @@ -798,7 +849,9 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) { label = { Text(stringResource(R.string.long_support_msg)) }, onValueChange = { longMsg = it }, minLines = 3, - modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp) + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 2.dp) ) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Button( diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt index df88114..f3acfb0 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -32,6 +32,7 @@ import android.net.Uri import android.os.Build.VERSION import android.os.HardwarePropertiesManager import android.os.UserManager +import android.widget.Button import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -183,7 +184,11 @@ fun SystemManagerScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { } if(VERSION.SDK_INT >= 28 && (privilege.device || privilege.org)) { FunctionItem(R.string.change_time, icon = R.drawable.schedule_fill0) { onNavigate(ChangeTime) } - FunctionItem(R.string.change_timezone, icon = R.drawable.schedule_fill0) { onNavigate(ChangeTimeZone) } + FunctionItem(R.string.change_timezone, icon = R.drawable.globe_fill0) { onNavigate(ChangeTimeZone) } + } + if (VERSION.SDK_INT >= 36 && (privilege.device || privilege.org)) { + FunctionItem(R.string.auto_time_policy, icon = R.drawable.schedule_fill0) { onNavigate(AutoTimePolicy) } + FunctionItem(R.string.auto_timezone_policy, icon = R.drawable.globe_fill0) { onNavigate(AutoTimeZonePolicy) } } /*if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) FunctionItem(R.string.key_pairs, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("KeyPairs") }*/ @@ -732,6 +737,56 @@ fun ChangeTimeZoneScreen(onNavigateUp: () -> Unit) { ) } +@Serializable object AutoTimePolicy + +@RequiresApi(36) +@Composable +fun AutoTimePolicyScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.auto_time_policy, onNavigateUp, 0.dp) { + val context = LocalContext.current + val dpm = context.getDPM() + var policy by remember { mutableIntStateOf(dpm.autoTimePolicy) } + listOf( + DevicePolicyManager.AUTO_TIME_ENABLED to R.string.enable, + DevicePolicyManager.AUTO_TIME_DISABLED to R.string.disabled, + DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY to R.string.not_controlled_by_policy + ).forEach { + FullWidthRadioButtonItem(it.second, it.first == policy) { + policy = it.first + } + } + Button({ + dpm.autoTimePolicy = policy + policy = dpm.autoTimePolicy + }, Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)) { + Text(stringResource(R.string.apply)) + } +} + +@Serializable object AutoTimeZonePolicy + +@RequiresApi(36) +@Composable +fun AutoTimeZonePolicyScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.auto_timezone_policy, onNavigateUp, 0.dp) { + val context = LocalContext.current + val dpm = context.getDPM() + var policy by remember { mutableIntStateOf(dpm.autoTimeZonePolicy) } + listOf( + DevicePolicyManager.AUTO_TIME_ZONE_ENABLED to R.string.enable, + DevicePolicyManager.AUTO_TIME_ZONE_DISABLED to R.string.disabled, + DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY to R.string.not_controlled_by_policy + ).forEach { + FullWidthRadioButtonItem(it.second, it.first == policy) { + policy = it.first + } + } + Button({ + dpm.autoTimeZonePolicy = policy + policy = dpm.autoTimeZonePolicy + }, Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)) { + Text(stringResource(R.string.apply)) + } +} + /*@RequiresApi(28) @OptIn(ExperimentalLayoutApi::class) @Composable diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c1316b2..d3d33dc 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -162,6 +162,8 @@ 更改时区 时区ID 在设置时区前需要关闭自动时区 + 自动时间策略 + 自动时区策略 内容保护策略 不受策略控制 权限策略 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 83d99c3..2aede37 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -171,6 +171,8 @@ Change timezone Timezone ID Auto timezone should be disabled before set a custom timezone. + Auto time policy + Auto timezone policy