diff --git a/Guide.md b/Guide.md index 9717482..6fbf55e 100644 --- a/Guide.md +++ b/Guide.md @@ -410,6 +410,14 @@ MTE: Memory Tagging Extension(内存标记拓展)[安卓开发者:MTE](htt - 延迟30天 - 默认(由用户决定) +### 安装系统更新 + +需要的权限:Device owner或由组织拥有的工作资料 + +可能会出现`DeadSystemException` + +能否安装成功完全取决于系统 + ### 恢复出厂设置 **谨慎使用** diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt index afe0798..4e0a6cd 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt @@ -2,7 +2,30 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager -import android.app.admin.DevicePolicyManager.* +import android.app.admin.DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY +import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback +import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK +import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS +import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME +import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD +import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE +import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS +import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW +import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO +import android.app.admin.DevicePolicyManager.MTE_DISABLED +import android.app.admin.DevicePolicyManager.MTE_ENABLED +import android.app.admin.DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY +import android.app.admin.DevicePolicyManager.NEARBY_STREAMING_DISABLED +import android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED +import android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY +import android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY +import android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY +import android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT +import android.app.admin.DevicePolicyManager.PERMISSION_POLICY_PROMPT +import android.app.admin.DevicePolicyManager.WIPE_EUICC +import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE +import android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA +import android.app.admin.DevicePolicyManager.WIPE_SILENTLY import android.app.admin.SystemUpdateInfo import android.app.admin.SystemUpdatePolicy import android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC @@ -22,16 +45,33 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ScrollState import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState 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.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext @@ -45,9 +85,19 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.bintianqi.owndroid.* import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.ui.* +import com.bintianqi.owndroid.Receiver +import com.bintianqi.owndroid.fileUriFlow +import com.bintianqi.owndroid.getFile +import com.bintianqi.owndroid.toText +import com.bintianqi.owndroid.ui.Animations +import com.bintianqi.owndroid.ui.CheckBoxItem +import com.bintianqi.owndroid.ui.Information +import com.bintianqi.owndroid.ui.RadioButtonItem +import com.bintianqi.owndroid.ui.SubPageItem +import com.bintianqi.owndroid.ui.SwitchItem +import com.bintianqi.owndroid.ui.TopBar +import com.bintianqi.owndroid.uriToStream import java.util.Date @Composable @@ -88,6 +138,7 @@ fun SystemManage(navCtrl:NavHostController) { composable(route = "CaCert") { CaCert() } composable(route = "SecurityLogs") { SecurityLogs() } composable(route = "SystemUpdatePolicy") { SysUpdatePolicy() } + composable(route = "InstallSystemUpdate") { InstallSystemUpdate() } composable(route = "WipeData") { WipeData() } } } @@ -97,6 +148,7 @@ fun SystemManage(navCtrl:NavHostController) { private fun Home(navCtrl: NavHostController, scrollState: ScrollState) { val context = LocalContext.current val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val receiver = ComponentName(context, Receiver::class.java) Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) { Text( text = stringResource(R.string.system_manage), @@ -135,6 +187,13 @@ private fun Home(navCtrl: NavHostController, scrollState: ScrollState) { if(VERSION.SDK_INT >= 23 && isDeviceOwner(dpm)) { SubPageItem(R.string.system_update_policy, "", R.drawable.system_update_fill0) { navCtrl.navigate("SystemUpdatePolicy") } } + if( + VERSION.SDK_INT >= 29 && + (isDeviceOwner(dpm) || + (VERSION.SDK_INT >= 30 && isProfileOwner(dpm) && dpm.isManagedProfile(receiver) && dpm.isOrganizationOwnedDeviceWithManagedProfile)) + ) { + SubPageItem(R.string.install_system_update, "", R.drawable.system_update_fill0) { navCtrl.navigate("InstallSystemUpdate") } + } SubPageItem(R.string.wipe_data, "", R.drawable.warning_fill0) { navCtrl.navigate("WipeData") } Spacer(Modifier.padding(vertical = 30.dp)) LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") } @@ -939,3 +998,65 @@ private fun SysUpdatePolicy() { Spacer(Modifier.padding(vertical = 30.dp)) } } + +@SuppressLint("NewApi") +@Composable +fun InstallSystemUpdate() { + val context = LocalContext.current + val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val receiver = ComponentName(context,Receiver::class.java) + val callback = object: InstallSystemUpdateCallback() { + override fun onInstallUpdateError(errorCode: Int, errorMessage: String) { + super.onInstallUpdateError(errorCode, errorMessage) + val errDetail = when(errorCode) { + UPDATE_ERROR_BATTERY_LOW -> R.string.battery_low + UPDATE_ERROR_UPDATE_FILE_INVALID -> R.string.update_file_invalid + UPDATE_ERROR_INCORRECT_OS_VERSION -> R.string.incorrect_os_ver + UPDATE_ERROR_FILE_NOT_FOUND -> R.string.file_not_exist + else -> R.string.unknown + } + val errMsg = context.getString(R.string.install_system_update_failed) + context.getString(errDetail) + Toast.makeText(context, errMsg, Toast.LENGTH_SHORT).show() + } + } + val uri by fileUriFlow.collectAsState() + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.install_system_update), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.setType("application/zip") + intent.addCategory(Intent.CATEGORY_OPENABLE) + getFile.launch(intent) + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.select_ota_package)) + } + AnimatedVisibility(uri != Uri.parse("")) { + Button( + onClick = { + try { + dpm.installSystemUpdate(receiver, uri, { it.run() }, callback) + Toast.makeText(context, R.string.start_install_system_update, Toast.LENGTH_SHORT).show() + }catch(e: Exception) { + Toast.makeText( + context, + context.getString(R.string.install_system_update_failed) + e.cause.toString(), + Toast.LENGTH_SHORT + ).show() + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.install_system_update)) + } + } + Spacer(Modifier.padding(vertical = 10.dp)) + Information { + Text(stringResource(R.string.auto_reboot_after_install_succeed)) + } + } +} diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 5e47dd6..9f3d4cd 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -47,6 +47,7 @@ IO异常 当前状态: 开始 + 未知错误 点击以激活 @@ -171,6 +172,14 @@ 结束时间 请输入一天中的分钟(0~1440) 系统更新接收时间: %1$s + 安装系统更新 + 选择OTA包... + 开始安装系统更新 + 安装系统更新失败: + 电量低 + OTA包无效 + 系统版本错误 + 安装系统更新成功后,你的设备会自动重启。 网络 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6662f77..eb9b1bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ IO Exception Current status:  Start + Unknown error Click to activate @@ -182,6 +183,15 @@ Please enter minutes in a day(0~1440) Update received time: %1$s + Install system update + Select OTA package... + Start installing system update + Install system update failed: + Battery is low + Update file is invalid + Incorrect OS version + Your device will reboot automatically when system update install succeed. + Network Min WiFi security level