Android Device Owner - Minimal App
Android 5.0 introduced a managed device mode. This feature allows a device owner to perform some privileged tasks often related to device administration, such as lock/hide apps, configure user accounts, wipe/reboot device, or remote updates without user interaction. All this can be done without root access. Device-admin-related api also allows you to create single-purpose devices("kiosk" app). This feature was especially interesting to me because I could use it for one of the projects I'm working on.
In this short post I would like to show a minimal implementation of an app that can use the managed device mode. I'll try to show only the code that is relevant for using managed device mode. That means that I'll skip the parts that might be necessary to compile and run the code. It should still be fairly trivial to plug it into your own app. At the end I'll show how to enable a device owner on a specific device.
Once you have a working app with device owner enabled, you can start to play with various methods that require device owner. Looking at the DevicePolicyManager and UserManager classes should give some additional information about what is possible.
If you you're an Android enthusiast that likes to learn more about Android internals, I highly recommend to check out my Bugjaeger app. It allows you to connect 2 Android devices through USB OTG and perform many of the tasks that are normally only accessible from a developer machine via ADB directly from your Android phone/tablet.
This post should show you the basics necessary to create a device owner app. After you finish, I also recommend you to check out my other related posts
- Android Kiosk Browser With Device Owner - Complete Example
- How to Update Android App Silently Without User Interaction
- Using Device Owner for Tasks That Would Normally Require Root on Android
Create DeviceAdminReceiver
Device Admin Receiver class contains multiple methods that you can override, but for this example I decided to use only the onEnabled()
method so that I can at least tell when Device Owner has been enabled
...
import android.app.admin.DeviceAdminReceiver
class DevAdminReceiver: DeviceAdminReceiver() {
override fun onEnabled(context: Context?, intent: Intent?) {
super.onEnabled(context, intent)
Log.d(TAG, "Device Owner Enabled")
}
}
Add DeviceAdminReceiver Declaration to AndroidManifest.xml
This should be somewhere inside of your <application>
tags.
<application
...>
...
<receiver
android:name="eu.sisik.devowner.DevAdminReceiver"
android:label="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN" >
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/device_admin" />
</receiver>
...
</application>
Create XML Resource for Device Admin
Place the device_admin.xml
file specified in the previous step (resource="@xml/device_admin"
) into the xml
folder inside of your resource folder (res
). Create the xml folder if it doesn't exist.
<?xml version="1.0" encoding="utf-8"?>
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
<uses-policies>
</uses-policies>
</device-admin>
Enable Device Owner via ADB
There are multiple methods for setting the device owner, but I've found the method that uses ADB
to be the simplest.
It appears that google considers this method to be used mostly for testing purposes. However, in comparison to the other methods, this method works on all Android versions that support device-owner-related api, it doesn't require NFC, and you don't need to have a google account and pay for additional services.
Official documentation often uses the word "Provisioning" when describing the process for enabling device owner. So this is what you should type in the search box when you want to find more information.
The previous steps should already be sufficient to activate the device owner. The dpm
shell command can be used to enable device owner for your app.
adb shell dpm set-device-owner eu.sisik.devowner/.DevAdminReceiver
eu.sisik.devowner
is the package that I used for this app. The package name is followed by your class that extends DeviceAdminReceiver. You should make sure you precede the name with a dot (and sub-package names, if used).
This command will create a device_owner.xml
file somewhere in /data/system/
and fill it with the provided package.
Setting Owner Directly From App (Root)
On stackoverflow I've found a suggestion to execute the previous dpm
command directly from your app. This will probably require root (at least it did not work on any non-rooted device that I've tested)
Runtime.getRuntime().exec("dpm set-device-owner eu.sisik.devowner/.DevAdminReceiver")
Setting Device Owner on Nougat and Higher
I did not have time to check this out properly, but it seems that configuring device owner has slightly changed in Nougat. Since Android 7 you are required to set the testOnly
attribute in AndroidManifest.xml to true. Without it the dpm
command will output an error(the error won't give much clue about what is wrong). Newer Android Studio versions are setting this flag automatically for debug builds that are built via Run
button
Removing Device Owner
Besides the obvious way of wiping the device from fastboot or recovery, you also have some other options depending on the Android version of your target device.
You can use the removeActiveAdmin()
of DevicePolicyManager class
val cn = ComponentName(packageName, packageName + ".AdminReceiver")
val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
dpm.removeActiveAdmin(cn)
This method can only be called from the package that is already set as a device owner. Otherwise you get a SecurityException
.
Since Android 7 the dpm
command offers one additional parameter that can be used to remove device owner - remove-active-admin
adb shell dpm remove-active-admin eu.sisik.devowner/.DevAdminReceiver
This will however only work when the device owner app has the android:testOnly
flag set to true in AndroidManifest.xml
.
Bugjaeger - Flashing and Configuring Your Kiosk App in the Field
Your target device might be already in the field and you would like to perform maintenance, or just want to flash and set up your new kiosk/dev-admin app. Depending on the location, it might be more comfortable to install and configure your app without carrying a laptop (and without searching for a flat surface where you could position your laptop). You can use the Bugjaeger app for exactly this kind of situations. The Bugjaeger app will allow you to perform these tasks directly from your phone.
To install your app and enable device admin mode, just follow these steps
- Connect your phone to the target Kiosk device through USB OTG cable. You have to enable developer options on the target devices and confirm the authorization request (same as on your regular development machine)
- In the Bugjaeger app switch to the
Packages
tab - Click on the Add package button. This should display a file picker dialog where you can select
.apk
file with your Kiosk app that you want to install - After installing your app, switch to the
Commands
tab - Click on the Add custom command button at the top. A dialog where you can enter your custom shell script should appear.
- Enter the shell command and name and click on
SAVE
. Make sure you write the command withoutadb shell
prepended because it is assumed implicitly - Once the command is saved, click on the Run button
next to its line item