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](https://developer.android.com/work/dpc/dedicated-devices).

## 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](https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode#customize-ui) that brings your allowlisted app to the foreground in lock
task mode.

All home apps handle the [`CATEGORY_HOME`](https://developer.android.com/reference/android/content/Intent#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()`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#addPersistentPreferredActivity(android.content.ComponentName,%20android.content.IntentFilter,%20android.content.ComponentName))
as shown in the following example:  

### Kotlin

```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

```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](https://developer.android.com/guide/components/intents-filters)
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`](https://developer.android.com/guide/topics/manifest/activity-element#exclude) 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`](https://developer.android.com/reference/android/content/Intent#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](https://developer.android.com/guide/components/activities/recents).

## 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.

| User restriction | Description |
|---|---|
| [`DISALLOW_FACTORY_RESET`](https://developer.android.com/reference/android/os/UserManager#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`](https://developer.android.com/reference/android/os/UserManager#DISALLOW_SAFE_BOOT) | Prevents a device user starting the device in [safe mode](https://source.android.com/security/overview/kernel-security#system-partition-and-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`](https://developer.android.com/reference/android/os/UserManager#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`](https://developer.android.com/reference/android/os/UserManager#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`](https://developer.android.com/reference/android/os/UserManager#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. |
[**Table 1**. User restrictions for kiosk devices]

The following snippet shows how you can set the restrictions:  

### Kotlin

```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

```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()`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#clearUserRestriction(android.content.ComponentName,%20java.lang.String)).

### 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`](https://developer.android.com/reference/android/os/UserManager#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

```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

```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](https://developer.android.com/training/scheduling/wakelock) when it's running your app's activity. Add
the [`FLAG_KEEP_SCREEN_ON`](https://developer.android.com/reference/android/view/WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON) layout flag to your app's
window as shown in the following example:  

### Kotlin

```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

```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`](https://developer.android.com/reference/android/os/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](https://developer.android.com/training/monitoring-device-state/battery-monitoring).

You can also set the [`STAY_ON_WHILE_PLUGGED_IN`](https://developer.android.com/reference/android/provider/Settings.Global#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()`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#setGlobalSetting(android.content.ComponentName,%20java.lang.String,%20java.lang.String)) as shown
in the following example:  

### Kotlin

```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

```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](https://developer.android.com/work/dpc/dedicated-devices/cookbook#delegates)) 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()`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#setKeepUninstalledPackages(android.content.ComponentName,%20java.util.List%3Cjava.lang.String%3E)).
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()`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#getKeepUninstalledPackages(android.content.ComponentName)).
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()`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#installExistingPackage(android.content.ComponentName,%20java.lang.String)).
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

```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

```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()`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#setDelegatedScopes(android.content.ComponentName,%20java.lang.String,%20java.util.List%3Cjava.lang.String%3E)) and include
[`DELEGATION_KEEP_UNINSTALLED_PACKAGES`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#DELEGATION_KEEP_UNINSTALLED_PACKAGES)
in the scopes argument. The following example shows how you can make another app
the delegate:  

### Kotlin

```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

```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`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#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`](https://developer.android.com/reference/android/content/pm/PackageInstaller) classes.
| **Caution:** Use the PackageInstaller APIs to install custom or private apps. Always get the permission of the app developer before installing their app. The [Google
| Play Terms of Service](https://play.google.com/about/play-terms.html) doesn't allow you to download and install apps from Google Play using these APIs.

While any app can install APKs, [admins](https://developer.android.com/reference/android/app/admin/DeviceAdminReceiver) 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.

| 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 |
[**Table 2**. Android versions supporting package installation
without user interaction]

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`](https://developer.android.com/reference/android/content/pm/PackageInstaller) to create a session that queues one
or more APKs for installation. In the following example we receive status
feedback in our activity ([singleTop](https://developer.android.com/guide/topics/manifest/activity-element#lmode) mode) but you could use a
service or broadcast receiver:  

### Kotlin

```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

```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`](https://developer.android.com/reference/android/content/pm/PackageInstaller#EXTRA_STATUS) field to get the
[status](https://developer.android.com/reference/android/content/pm/PackageInstaller#STATUS_SUCCESS). Remember, admins don't receive the
[`STATUS_PENDING_USER_ACTION`](https://developer.android.com/reference/android/content/pm/PackageInstaller#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`](https://developer.android.com/reference/android/content/pm/PackageInstaller#uninstall(java.lang.String,%20android.content.IntentSender)).
Admins of fully managed devices, users, and work profiles can uninstall packages
without user interaction running supported Android versions (see
[table 2](https://developer.android.com/work/dpc/dedicated-devices/cookbook#t-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](https://developer.android.com/work/dpc/system-updates).

## Remote config

Android's [managed configurations](https://developer.android.com/work/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](https://developer.android.com/work/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](https://developer.android.com/studio/command-line/adb) (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](https://developers.google.com/android/work/play/emm-api/prov-devices). We recommend [QR-code enrollment](https://developers.google.com/android/work/play/emm-api/prov-devices#qr_code_method) for
dedicated devices.

## Additional resources

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

- [Manage dedicated devices](https://developer.android.com/work/dpc/dedicated-devices)
- [Lock task mode](https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode)
- [Manage multiple users](https://developer.android.com/work/dpc/dedicated-devices/multiple-users)