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.

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 realated posts.

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

dpm remove-active-admin

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

  1. 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)
  2. In the Bugjaeger app switch to the Packages tab
  3. 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 Install package button
  4. After installing your app, switch to the Commands tab
  5. Click on the Add custom command button at the top. A dialog where you can enter your custom shell script should appear.
  6. Enter the shell command and name and click on SAVE. Make sure you write the command without adb shell prepended because it is assumed implicitly Save custom command
  7. Once the command is saved, click on the Run button Execute but next to its line item

Next Post

Add a comment

Comments

Yes, it is possible. You can use ADB directly (adb install -r -t updated-app.apk) or through Android Studio. The updated version should be signed with the same certificate and have the same or higher versionCode. If you meant the app should update itself programmatically, then you can read more about this in my other post - https://sisik.eu/blog/android/dev-admin/update-app. When updating programmatically from you app, you might need to additionally consider the changes in Android 7 and higher...to activate device owner from ADB, you need to set the 'testOnly' flag in AndroidManifest.xml. But the updated versions should NOT have this flag set, or programmatic update from your device owner app might not work.
Written on Sun, 22 Jul 2018 19:06:10 by Roman
is it possible to update the device owner app itself while running ? I cant manually uninstall an app which is a device owner.
Written on Sun, 22 Jul 2018 18:07:38 by Sarin