如何在不删除现有账户的前提下将Android应用设为Device Owner?
问题描述
我正在开发一款用于阻止用户将手机恢复出厂设置或重置系统的Android应用。目前应用可激活为设备管理员,但会提示权限不足,且点击“Block Wipe Data”按钮时应用会崩溃。我尝试过多种方式将应用从Profile Owner转为Device Owner均未成功,使用adb命令:
adb shell dpm set-device-owner com.foo.deviceowner/.DeviceAdminRcvr
可成功将应用设为Device Owner,使应用正常阻止恢复出厂设置,但该操作会删除设备上的所有现有账户。请问是否存在无需删除现有账户的方法,将应用设为Device Owner,使其在已投入使用的设备上正常工作?
相关代码
MainActivity.kt
package com.example.test2 import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.Bundle import android.os.UserManager import androidx.appcompat.app.AppCompatActivity import com.example.test2.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var componentName: ComponentName private val disallowFactoryReset = "no_factory_reset" private lateinit var binding: ActivityMainBinding @SuppressLint("WrongConstant") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) devicePolicyManager = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager componentName = ComponentName(this, MyDeviceAdminReceiver::class.java) // 设置按钮文本 binding.activateButton.setText(R.string.activate_button_text) binding.deactivateButton.setText(R.string.deactivate_button_text) binding.blockWipeDataButton.setText(R.string.block_wipe_data_button_text) binding.unblockWipeDataButton.setText(R.string.unblock_wipe_data_button_text) binding.activateButton.setOnClickListener { if (!devicePolicyManager.isAdminActive(componentName)) { val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, componentName) startActivity(intent) } } binding.deactivateButton.setOnClickListener { if (devicePolicyManager.isAdminActive(componentName)) { devicePolicyManager.removeActiveAdmin(componentName) } } binding.blockWipeDataButton.setOnClickListener { // 阻止恢复出厂设置 devicePolicyManager.addUserRestriction(componentName, UserManager.DISALLOW_FACTORY_RESET) } binding.unblockWipeDataButton.setOnClickListener { if (devicePolicyManager.isAdminActive(componentName)) { // 解除恢复出厂设置阻止 devicePolicyManager.clearUserRestriction(componentName, disallowFactoryReset) } } } }
MyDeviceAdminReceiver.kt
package com.example.test2 import android.app.admin.DeviceAdminReceiver import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context import android.content.Intent import android.widget.Toast class MyDeviceAdminReceiver : DeviceAdminReceiver() { override fun onEnabled(context: Context, intent: Intent) { super.onEnabled(context, intent) showToast(context, "Device Admin Enabled") setAsProfileOwner(context) } override fun onDisabled(context: Context, intent: Intent) { super.onDisabled(context, intent) showToast(context, "Device Admin Disabled") } private fun showToast(context: Context, msg: String) { Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() } private fun setAsProfileOwner(context: Context) { val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) intent.putExtra( DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, ComponentName(context, MyDeviceAdminReceiver::class.java) ) context.startActivity(intent) } }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.test2"> <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <application android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Test2"> <receiver android:name=".MyDeviceAdminReceiver" android:label="@string/device_admin" android:description="@string/device_admin_description" android:permission="android.permission.BIND_DEVICE_ADMIN" android:exported="true"> <meta-data android:name="android.app.device_admin" android:resource="@xml/device_admin_receiver" /> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> </receiver> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
解决方案与说明
核心结论
不存在无需删除现有账户即可将应用设置为Device Owner的方法。Android系统的设计逻辑中,Device Owner是为全新设备或已恢复出厂设置的设备准备的企业管理角色,设置时会清除设备上的所有用户数据和账户,确保设备处于可控的初始状态。已投入使用的设备(存在现有用户账户)无法绕过这个限制直接设置Device Owner。
替代方案
使用Profile Owner(工作资料)
Profile Owner可以在不清除设备数据的情况下设置,但它仅能管理工作资料内的应用和行为,无法全局阻止设备的恢复出厂设置操作,因此无法满足你的核心需求。引导用户重置设备后设置
如果必须使用Device Owner权限,只能引导用户先备份设备数据,恢复出厂设置后,通过企业注册流程或adb命令设置应用为Device Owner。此时设备处于初始状态,设置后再恢复用户数据即可,不会丢失新添加的账户。修复当前应用崩溃问题
当前应用点击“Block Wipe Data”崩溃的原因是:普通设备管理员(Device Admin)没有权限调用addUserRestriction设置UserManager.DISALLOW_FACTORY_RESET,只有Device Owner或Profile Owner才有该权限。可以在代码中增加权限判断,避免崩溃:binding.blockWipeDataButton.setOnClickListener { if (devicePolicyManager.isDeviceOwnerApp(packageName)) { devicePolicyManager.addUserRestriction(componentName, UserManager.DISALLOW_FACTORY_RESET) } else { Toast.makeText(this, "需要设备所有者权限", Toast.LENGTH_SHORT).show() } }另外,
unblockWipeDataButton的代码中使用了自定义的disallowFactoryReset字符串,应该替换为UserManager.DISALLOW_FACTORY_RESET,否则可能无法正确解除限制。
内容的提问来源于stack exchange,提问作者tomczaxo

