Back to case studies
Charging Point

Client

i4B own product

R&D team

i4b - EV Lab

Kiosk mode for android devices with Flutter

Charging Station

Technology

flutter logo and kinds of screens flutter logo

You've got questions?

Contact us

Why to use Android kiosk mode?

Android kiosk mode allows you to limit the functionality of the device and block access to device settings and configuration.

It allows you to block the ability to exit the application and receive notifications from Android and applications.

Kiosks are generally found in retail stores, airports, hospitals, restaurants, car parks and of course – ev chargers – all the places when you like the self-service with some touch screen device. Usually, the users don’t even realize, that there is a “normal android” system behind.

When writing this kind of an application, you may have to choose one of two approaches how to control the access to systems internal settings – screen pinning or locking task mode.

We are writing about our solution below.

Screen pinning vs Lock Task Mode

Screen pinning

The screen pinning is available from version 5.0 (API level 21) or higher, it allows you to pin the application and prevents you from leaving it without unpinning. After enabling this option, you will get asked for a confirmation if you want to pin the screen.

After pinned, the status bar disappears, and the navigation buttons become useless. Depending on how you have set your Screen Pinning settings, you also will not receive any notifications from your device.

To exit pinned mode, press the ‘back’ and ‘overview’ buttons together, and you will exit the app to the lock screen. Once you unlock your device, your app will no longer be pinned.

Lock Task Mode

Also from the same version as Screen pinning a lock task mode is available. In this mode, the device does not display notifications, the user has no access to other applications and does not have access to the home screen.

However, to get this effect it is not enough to call the lock task mode itself.

As you can read on the Android documentation:

Only apps that have been allowlisted by a device policy controller (DPC) can run when the system is in lock task mode. Apps are allowlisted because the person using the device can't always leave lock task mode.

How you combine the app allowlisted for lock task mode and the allowlisting DPC will depend on the problem you want to solve. Here are some examples:

  • A single app package that combines a kiosk (to present content) and a mini DPC (to allowlist itself for lock task mode).
  • A DPC that’s part of an enterprise mobility management solution, launching the customer’s mobile apps in lock task mode.

To make kiosk mode work properly, there are a few things you need to do.

First of all, app needs to have a ‘DeviceAdminReceiver’ setup.

<receiver
  android:name=".MyDeviceAdminReceiver"
  android:label="kioskexample"
  android:permission="android.permission.BIND_DEVICE_ADMIN"
  android:exported="true" >
  <meta-data
    android:name="android.app.device_admin"
    android:resource="@xml/policies" />
  <intent-filter>
    <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
    <action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/>
    <action android:name="android.intent.action.BOOT_COMPLETED"/>
  </intent-filter>
</receiver>

We also need to create a file in which we declare the security policies used in metadata. The metadata provides additional information specific to the device administrator, as parsed by the DeviceAdminInfo class.

<?xml version="1.0" encoding="utf-8"?>
<device-admin>
  <uses-policies>
    <!-- define policy here -->
  </uses-policies>
</device-admin>

Next, you need to create a class in the Android folder that implements the DeviceAdminReceiver

import android.app.admin.DeviceAdminReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.util.Log

class MyDeviceAdminReceiver : DeviceAdminReceiver() {
  companion object {
    fun getComponentName(context: Context): ComponentName {
      return ComponentName(context.applicationContext, MyDeviceAdminReceiver::class.java)
    }

    private val TAG = MyDeviceAdminReceiver::class.java.simpleName
  }

  override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) {
    super.onLockTaskModeEntering(context, intent, pkg)
    Log.d(TAG, "onLockTaskModeEntering")
  }

  override fun onLockTaskModeExiting(context: Context, intent: Intent) {
    super.onLockTaskModeExiting(context, intent)
    Log.d(TAG, "onLockTaskModeExiting")
  }
}

Next, you need to modify the MainActivity class in the Android folder.

Only apps allowlisted by DevicePolicyManager#setLockTaskPackages(ComponentName, String[]) can be launched while LockTask mode is active.

import io.flutter.embedding.android.FlutterActivity
import androidx.annotation.NonNull
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.content.ComponentName
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.app.admin.DevicePolicyManager

class MainActivity: FlutterActivity() {
  private val kioskModeChannel = "kioskModeLocked"
  private lateinit var mAdminComponentName: ComponentName
  private lateinit var mDevicePolicyManager: DevicePolicyManager

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, kioskModeChannel).setMethodCallHandler {
        call, result ->
        if (call.method == "startKioskMode") {
          try {
            manageKioskMode(true)
          } catch (e: Exception) {}
        } else if (call.method == "stopKioskMode") {
          try {
            manageKioskMode(false)
          } catch (e: Exception) {}
        } else {
          result.notImplemented()
        }
      }
  }

  private fun manageKioskMode(enable: Boolean) {
    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
        mDevicePolicyManager = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
        mAdminComponentName = MyDeviceAdminReceiver.getComponentName(this)
        mDevicePolicyManager.setLockTaskPackages(mAdminComponentName, arrayOf(packageName))
        if(enable) {
          this.startLockTask()
        } else {
          this.stopLockTask()
        }
      return
    }
  }
}

Flutter also needs methods to start kiosk mode

import 'package:flutter/services.dart';

class KioskMode {
  static const platform = MethodChannel('kioskModeLocked');

  static startKioskMode() async {
    await platform.invokeMethod('startKioskMode');
  }

  static stopKioskMode() async {
    await platform.invokeMethod('stopKioskMode');
  }
}

After installing the application on the device, go to the console and execute the command to set device-owner

adb shell dpm set-device-owner com.example.kioskexample/.MyDeviceAdminReceiver

If the command was successful, it should display a message like:

Success: Device owner set to package ComponentInfo{com.example.kioskexample/com.example.kioskexample.MyDeviceAdminReceiver}
Active admin set to component {com.example.kioskexample/com.example.kioskexample.MyDeviceAdminReceiver}

It is worth adding that after configuring the Device Owner application, it cannot be unset with the dpm command. You’ll need to programmatically use the DevicePolicyManager.clearDeviceOwnerApp() method or factory reset your device.

When we put it all together, we get an application in which we can turn on and off the kiosk mode, as you can see in the screenshots below.

Before starting kiosk mode:

What is accessible: Notification panel and full Navigation bar

After starting kiosk mode:

What is accessible: just Back arrow

Easier solution?

However, for the lazy ones, there is a possible shortcut to the problem that the lock task mode does not work correctly.

All you need to do is set it in AndroidManifest:

<activity
  android:name=".MainActivity"
  android:lockTaskMode="always">
  <!-- ... -->
</activity>

The problem is only with exiting the app as we can’t disable kiosk mode in the app. There remains an option to close the application, this can be done by the method:

SystemChannels.platform.invokeMethod('SystemNavigator.pop');

We hope you find this article useful. As always, when developing anything for Android devices, there are lots of exceptions depending on system version, Google services and particular device. Feel free to contact us if you need some support or want to share your experience.

Get in touch and let’s talk.

daniel ceo
Write to us on:

    Your message was successfully sent.

    Thank you for contacting us. We will get back to you as soon as possible.