Lock task mode allows you to lock your app to the screen, so that the user won't be able escape from the app by pressing buttons like back and home.

This feature can be especially useful when your implementing single purpose kiosk-like devices. The term Kiosk mode can also be used to describe this feature.

The official docs differentiate between lock task mode and screen pinning. Screen pinning can be activated from device settings and it usually still allows the user to exit the app. With lock task mode, the user won't be able to exit the app.

In previous Android versions, lock task mode was started directly from your app, by calling the Activity.startLockTask() method.

Since Android 9 Pie it is now possible to enable lock task mode in other installed apps and turn them into a locked-down kiosk. This means that you can lock down any other installed app without even having access to its source code.

In this post I would like to show how to start other installed apps in lock task mode programmatically.

This post should serve as quick reference for myself, so that I can quickly look up the procedure in case the docs change or links move around. I think the explanation in official docs is pretty good, so I highly recommend to check it out too.

The sample code is available at github.

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.

Enabling Device Owner

You app will need become a device owner (or profile owner) to use the functionality in the next sections. Otherwise you'd get a SecurityException.

I've already shown how to make your app a device owner in previous blogposts. I don't want to repeat too much stuff, but I still want this post to be complete. So here I'll just summarize the steps from my other blogpost that shows a minimal example of device owner app.

First step is to extend the DeviceAdminReceiver class

class DevAdminReceiver: DeviceAdminReceiver() {
    override fun onEnabled(context: Context?, intent: Intent?) {
        super.onEnabled(context, intent)
        Log.d(TAG, "Device Owner Enabled")
    }
}

Next you should create a device_admin.xml file inside of your res/xml folder

<?xml version="1.0" encoding="utf-8"?>
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-policies>
    </uses-policies>
</device-admin>

Then declare a BroadcastReceiver component inside of you AndroidManifest.xml file

<application
        android:testOnly="true"

        ...>
    ...
    <receiver
        android:name="eu.sisik.kioskbrowser.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>

Note that I also added the testOnly flag to the tag. If you are enabling device owner via ADB, this flag is necessary since Android 7.0.

Once you've built and installed the app to your target device, you can make it a devices owner with the following ADB command

adb shell dpm set-device-owner my.package.name/.DevAdminReceiver

Alternatively, you can use my Bugjaeger app to activate the device owner app. Just connect both devices with an USB OTG cable and execute the same command as before from Bugjaeger's interface.

Allow Other Package to Use Lock Task - Whitelist App

Before an app can use lock task mode, it needs to be whitelisted for this purpose.

You can use the code bellow to accomplish this

val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val cn = ComponentName(this, DevAdminReceiver::class.java)
dpm.setLockTaskPackages(cn, arrayOf(packageNameToAllowLockTask))

I've called the DevicePolicyManager.setLockTaskPackages() method, giving it an array of package names that should be allowed to start lock task. In my example I'm only passing one package.

Calling this from an app that isn't device owner (profile owner) will throw a SecurityException.

You can use my Power Apk App to quickly find the package name of a particular app.

Start App In Lock Task Mode

Once the target app is whitelisted, you can do the following to start it in lock task mode

val options = ActivityOptions.makeBasic()
options.setLockTaskEnabled(true)

val i = packageManager.getLaunchIntentForPackage(forPackageName)
startActivity(i, options.toBundle())

Stop Lock Task Mode

Once another app is started in lock task mode, it might make testing of your app more difficult, because it'll block any other app (including yours) from starting again and going foreground.

A simple method to get rid of the target app running in lock task mode is to kill it via ADB

adb shell force-stop your.locktask.packagename

You can accomplish the same thing with code. The method setLockTaskPackages() can also be used to exclude the given package from using lock task by simply omitting it when from the list of allowed packages.

In my case I just passed an empty list, which means no packages will be allowed to start in lock task mode

dpm.setLockTaskPackages(cn, arrayOf())

This should force the app to immediately unpin/unlock from the screen.

Previous Post

Add a comment