Dedicated devices cookbook

This cookbook helps developers and system integrators enhance their dedicated device solution. Follow our how-to recipes to find solutions for dedicated-device behaviors. This cookbook works best for developers that already have a dedicated device app—if you’re just getting started, read Dedicated devices overview.

Custom Home apps

These recipes are useful if you’re developing an app to replace the Android Home screen and Launcher.

Be the home app

You can set your app as the device’s home app so that it’s launched automatically when the device starts up. You can also enable the Home button that brings your allowlisted app to the foreground in lock task mode.

All home apps handle the CATEGORY_HOME intent category—this is how the system recognizes a home app. To become the default home app, set one of your app’s activities as the preferred Home intent handler, by calling DevicePolicyManager.addPersistentPreferredActivity() as shown in the following example:

Kotlin

// Create an intent filter to specify the Home category.
val filter = IntentFilter(Intent.ACTION_MAIN)
filter.addCategory(Intent.CATEGORY_HOME)
filter.addCategory(Intent.CATEGORY_DEFAULT)

// Set the activity as the preferred option for the device.
val activity = ComponentName(context, KioskModeActivity::class.java)
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager
dpm.addPersistentPreferredActivity(adminName, filter, activity)

Java

// Create an intent filter to specify the Home category.
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);

// Set the activity as the preferred option for the device.
ComponentName activity = new ComponentName(context, KioskModeActivity.class);
DevicePolicyManager dpm =
    (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.addPersistentPreferredActivity(adminName, filter, activity);

You still need to declare the intent filter in your app manifest file as shown in the following XML snippet:

<activity
        android:name=".KioskModeActivity"
        android:label="@string/kiosk_mode"
        android:launchMode="singleInstance"
        android:excludeFromRecents="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.HOME"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

Typically you don’t want your launcher app to appear in the Overview screen. However, you don’t need to add excludeFromRecents to the activity declaration because Android’s Launcher hides the initially launched activity when the system is running in lock task mode.

Show separate tasks

FLAG_ACTIVITY_NEW_TASK can be a useful flag for launcher-type apps because each new task appears as a separate item in the Overview screen. To learn more about tasks in the Overview screen, read Recents Screen.

Public kiosks

These recipes are great for unattended devices in public spaces but can also help many dedicated device users focus on their tasks.

Lock down the device

To help make sure that devices are used for their intended purpose, you can add the user restrictions listed in table 1.

Table 1. User restrictions for kiosk devices
User restriction Description
DISALLOW_FACTORY_RESET Prevents a device user resetting the device to its factory defaults. Admins of fully managed devices and the primary user can set this restriction.
DISALLOW_SAFE_BOOT Prevents a device user starting the device in safe mode where the system won’t automatically launch your app. Admins of fully managed devices and the primary user can set this restriction.
DISALLOW_MOUNT_PHYSICAL_MEDIA Prevents the device user from mounting any storage volumes they might attach to the device. Admins of fully managed devices and the primary user can set this restriction.
DISALLOW_ADJUST_VOLUME Mutes the device and prevents the device user from changing the sound volume and vibration settings. Check that your kiosk doesn’t need audio for media playback or accessibility features. Admins of fully managed devices, the primary user, secondary users, and work profiles can set this restriction.
DISALLOW_ADD_USER Prevents the device user adding new users, such as secondary users or restricted users. The system automatically adds this user restriction to fully managed devices but it might have been cleared. Admins of fully managed devices and the primary user can set this restriction.

The following snippet shows how you can set the restrictions:

Kotlin

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
arrayOf(
        UserManager.DISALLOW_FACTORY_RESET,
        UserManager.DISALLOW_SAFE_BOOT,
        UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
        UserManager.DISALLOW_ADJUST_VOLUME,
        UserManager.DISALLOW_ADD_USER).forEach { dpm.addUserRestriction(adminName, it) }

Java

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
String[] restrictions = {
    UserManager.DISALLOW_FACTORY_RESET,
    UserManager.DISALLOW_SAFE_BOOT,
    UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
    UserManager.DISALLOW_ADJUST_VOLUME,
    UserManager.DISALLOW_ADD_USER};

for (String restriction: restrictions) dpm.addUserRestriction(adminName, restriction);

You might want to remove these restrictions when your app is in an admin mode so that an IT admin could still use these features for device maintenance. To clear the restriction, call DevicePolicyManager.clearUserRestriction().

Suppress error dialogs

In some environments, such as retail demonstrations or public information displays, you might not want to show error dialogs to users. In Android 9.0 (API level 28) or higher, you can suppress system error dialogs for crashed or unresponsive apps by adding the DISALLOW_SYSTEM_ERROR_DIALOGS user restriction. The system restarts unresponsive apps as if the device user closed the app from the dialog. The following example shows how you can do this:

Kotlin

override fun onEnabled(context: Context, intent: Intent) {
    val dpm = getManager(context)
    val adminName = getWho(context)

    dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS)
}

Java

public void onEnabled(Context context, Intent intent) {
  DevicePolicyManager dpm = getManager(context);
  ComponentName adminName = getWho(context);

  dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS);
}

If an admin of the primary or a secondary user sets this restriction, the system suppresses error dialogs for just that user. If an admin of a fully managed device sets this restriction, the system suppresses dialogs for all users.

Keep the screen on

If you’re building a kiosk, you can stop a device going to sleep when it’s running your app’s activity. Add the FLAG_KEEP_SCREEN_ON layout flag to your app’s window as shown in the following example:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Keep the screen on and bright while this kiosk activity is running.
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // Keep the screen on and bright while this kiosk activity is running.
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

You might want to check that the device is plugged in to an AC, USB, or wireless charger. Register for battery-change broadcasts and use BatteryManager values to discover the charging state. You can even send remote alerts to an IT admin if the device becomes unplugged. For step-by-step instructions, read Monitor the Battery Level and Charging State.

You can also set the STAY_ON_WHILE_PLUGGED_IN global setting to keep the device awake while connected to a power source. Admins of fully managed devices, in Android 6.0 (API level 23) or higher, can call DevicePolicyManager.setGlobalSetting() as shown in the following example:

Kotlin

val pluggedInto = BatteryManager.BATTERY_PLUGGED_AC or
        BatteryManager.BATTERY_PLUGGED_USB or
        BatteryManager.BATTERY_PLUGGED_WIRELESS
dpm.setGlobalSetting(adminName,
        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, pluggedInto.toString())

Java

int pluggedInto = BatteryManager.BATTERY_PLUGGED_AC |
    BatteryManager.BATTERY_PLUGGED_USB |
    BatteryManager.BATTERY_PLUGGED_WIRELESS;
dpm.setGlobalSetting( adminName,
    Settings.Global.STAY_ON_WHILE_PLUGGED_IN, String.valueOf(pluggedInto));

App packages

This section contains recipes to efficiently install apps onto dedicated devices.

Cache app packages

If the users of a shared device all share a common set of apps, it makes sense to avoid downloading apps whenever possible. To streamline user provisioning on shared devices with a fixed set of users, such as devices for shift workers, in Android 9.0 (API level 28) or later, you can cache app packages (APKs) that are needed for multi-user sessions.

Installing a cached APK (that’s already installed on the device) happens in two stages:

  1. The admin component of a fully managed device (or a delegate—see following) sets the list of APKs to keep on the device.
  2. Admin components of affiliated secondary users (or their delegates) can install the cached APK on behalf of the user. Admins of the fully managed device, the primary user, or an affiliated work profile (or their delegates) can also install the cached app if needed.

To set the list of APKs to keep on the device, the admin calls DevicePolicyManager.setKeepUninstalledPackages(). This method doesn’t check that the APK is installed on the device—useful if you want to install an app just before you need it for a user. To get a list of previously-set packages, you can call DevicePolicyManager.getKeepUninstalledPackages(). After you call setKeepUninstalledPackages() with changes, or when a secondary user is deleted, the system deletes any cached APKs that are no longer needed.

To install a cached APK, call DevicePolicyManager.installExistingPackage(). This method can only install an app that the system has already cached—your dedicated device solution (or the user of a device) must first install the app on the device before you can call this method.

The following sample shows how you could use these API calls in the admin of a fully managed device and secondary user:

Kotlin

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
val cachedAppPackageName = "com.example.android.myapp"
dpm.setKeepUninstalledPackages(adminName, listOf(cachedAppPackageName))

// ...

// The admin of a secondary user installs the app.
val success = dpm.installExistingPackage(adminName, cachedAppPackageName)

Java

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
String cachedAppPackageName = "com.example.android.myapp";
List<String> packages = new ArrayList<String>();
packages.add(cachedAppPackageName);
dpm.setKeepUninstalledPackages(adminName, packages);

// ...

// The admin of a secondary user installs the app.
boolean success = dpm.installExistingPackage(adminName, cachedAppPackageName);

Delegate apps

You can delegate another app to manage app caching. You might do this to separate the features of your solution or offer the ability for IT admins to use their own apps. The delegate app gets the same permissions as the admin component. For example, an app delegate of a secondary user’s admin can call installExistingPackage() but can’t call setKeepUninstalledPackages().

To make a delegate call DevicePolicyManager.setDelegatedScopes() and include DELEGATION_KEEP_UNINSTALLED_PACKAGES in the scopes argument. The following example shows how you can make another app the delegate:

Kotlin

var delegatePackageName = "com.example.tools.kept_app_assist"

// Check that the package is installed before delegating.
try {
    context.packageManager.getPackageInfo(delegatePackageName, 0)
    dpm.setDelegatedScopes(
            adminName,
            delegatePackageName,
            listOf(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES))
} catch (e: PackageManager.NameNotFoundException) {
    // The delegate app isn't installed. Send a report to the IT admin ...
}

Java

String delegatePackageName = "com.example.tools.kept_app_assist";

// Check that the package is installed before delegating.
try {
  context.getPackageManager().getPackageInfo(delegatePackageName, 0);
  dpm.setDelegatedScopes(
      adminName,
      delegatePackageName,
      Arrays.asList(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES));
} catch (PackageManager.NameNotFoundException e) {
  // The delegate app isn't installed. Send a report to the IT admin ...
}

If everything goes well, the delegate app receives the ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED broadcast and becomes the delegate. The app can call the methods in this guide as if it were the device owner or profile owner. When calling DevicePolicyManager methods, the delegate passes null for the admin component argument.

Install app packages

Sometimes it’s useful to install a locally-cached custom app onto a dedicated device. For example, dedicated devices are frequently deployed to bandwidth-limited environments or areas without any internet connectivity. Your dedicated device solution should be mindful of your customers’ bandwidth. Your app can start the installation of another app package (APK) using the PackageInstaller classes.

While any app can install APKs, admins on fully managed devices can install (or uninstall) packages without user interaction. The admin might manage the device, an affiliated secondary user, or an affiliated work profile. After finishing the installation, the system posts a notification that all device users see. The notification informs device users that the app was installed (or updated) by their admin.

Table 2. Android versions supporting package installation without user interaction
Android version Admin component for install and uninstall
Android 9.0 (API level 28) or higher Affiliated secondary users and work profiles—both on fully managed devices
Android 6.0 (API level 23) or higher Fully managed devices

How you distribute one or more copies of the APK to dedicated devices will depend on how remote the devices are and possibly by how far apart the devices are from one another. Your solution needs to follow security best practices before installing APKs onto dedicated devices.

You can use PackageInstaller.Session to create a session that queues one or more APKs for installation. In the following example we receive status feedback in our activity (singleTop mode) but you could use a service or broadcast receiver:

Kotlin

// First, create a package installer session.
val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(
        PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
// The I/O streams can't be open when installation begins.
session.openWrite("apk", 0, -1).use { output ->
    getContext().resources.openRawResource(R.raw.app).use { input ->
        input.copyTo(output, 2048)
    }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
val intent = Intent(context, activity.javaClass)
intent.action = "com.android.example.APK_INSTALLATION_ACTION"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val statusReceiver = pendingIntent.intentSender

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver)

Java

// First, create a package installer session.
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
try (
    // These I/O streams can't be open when installation begins.
    OutputStream output = session.openWrite("apk", 0, -1);
    InputStream input = getContext().getResources().openRawResource(R.raw.app);
) {
  byte[] buffer = new byte[2048];
  int n;
  while ((n = input.read(buffer)) >= 0) {
    output.write(buffer, 0, n);
  }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
Intent intent = new Intent(context, getActivity().getClass());
intent.setAction("com.android.example.APK_INSTALLATION_ACTION");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.getIntentSender();

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver);

The session sends status feedback about the installation using intents. Check each intent’s EXTRA_STATUS field to get the status. Remember, admins don’t receive the STATUS_PENDING_USER_ACTION status update because the device user doesn’t need to approve the installation.

To uninstall apps, you can call PackageInstaller.uninstall. Admins of fully managed devices, users, and work profiles can uninstall packages without user interaction running supported Android versions (see table 2).

Freeze system updates

Android devices receive over-the-air (OTA) updates to the system and application software. To freeze the OS version over critical periods, such as holidays or other busy times, dedicated devices can suspend OTA system updates for up to 90 days. To learn more, read Manage system updates.

Remote config

Android’s managed configurations allow IT admins to remotely configure your app. You might want to expose settings such as allowlists, network hosts, or content URLs to make your app more useful to IT admins.

If your app exposes its config, remember to include the settings in your documentation. To learn more about exposing your app’s config and reacting to changes in settings, read Set up managed configurations.

Development setup

While you’re developing your solution for dedicated devices, it’s sometimes useful to set your app as the admin of a fully managed device without a factory reset. To set the admin of a fully managed device, follow these steps:

  1. Build and install your device policy controller (DPC) app on the device.
  2. Check that there are no accounts on the device.
  3. Run the following command in the Android Debug Bridge (adb) shell. You need to replace com.example.dpc/.MyDeviceAdminReceiver in the example with your app’s admin component name:

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

To help customers deploy your solution, you’ll need to look at other enrollment methods. We recommend QR-code enrollment for dedicated devices.

Additional resources

To learn more about dedicated devices, read the following documents: