The application roles feature allows an IT admin to grant special privileges to
a managed application on an Android-powered device. By assigning a specific
role, an app can be exempted from power and background restrictions, suspension,
hibernation (on Android 14+) and have user controls (i.e. includes user actions
like force-stopping and clearing app data) disabled (on Android 11+),
allowing it to perform its critical function without interruption. Additionally,
the app can be notified of its assigned roles, which allows it to bootstrap
itself without user intervention.

> [!NOTE]
> **Note:** The application roles feature replaces the [`extensionConfig`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies#ExtensionConfig) field in `ApplicationPolicy`.

For a list of available application roles and the special privileges granted to
each role, see [`RoleType`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies#roletype).

## Prerequisites

The device is managed by an AMAPI based EMM (EMMs using a custom DPC are not
supported).

## Prepare your app for using the feature

Integrating with the AMAPI SDK is only required if the app wants to be notified
of its assigned roles which allows it to bootstrap itself (i.e. auto launch
without user interaction).

### Integrate with the AMAPI SDK in your app

You can find more information about AMAPI SDK and how to add it to your app in
the [AMAPI SDK integration guide](https://developers.google.com/android/management/sdk-integration).

### Add the required metadata to the app's manifest

Android Device Policy (ADP) needs to know the [`ComponentName`](https://developer.android.com/reference/android/content/ComponentName) of your class
which implements [`NotificationReceiverService`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/notification/NotificationReceiverService) to notify your app of its
assigned roles. You must tag your service in `AndroidManifest.xml` suitably so
that it can be automatically discovered by ADP.

- Your app must have exactly one service which is [`enabled`](https://developer.android.com/guide/topics/manifest/service-element#enabled) and has `meta-data` with `android:name` equal to `com.google.android.managementapi.notification.NotificationReceiverService.SERVICE_APP_ROLES`
- This service must have `android:exported` set to `true`
- The `android:value` of the `meta-data` must be set to an empty string

    <service
     android:name=".MyNotificationReceiverService"
     android:exported="true">
        <meta-data android:name="com.google.android.managementapi.notification.NotificationReceiverService.SERVICE_APP_ROLES" android:value="" />
    </service>

> [!NOTE]
> **Note:** The `NotificationReceiverService` base class automatically handles security protections. It ensures that only the Android Device Policy (ADP) app can securely communicate with this service. You don't need to declare a custom `BIND` permission or manually verify the calling package.

If you are testing the [`COMPANION_APP`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies#RoleType.ENUM_VALUES.COMPANION_APP) role, you should also add the following
`meta-data` to your service so that Android Device Policy can send local
command status updates to your app:

    <meta-data android:name="com.google.android.managementapi.notification.NotificationReceiverService.SERVICE_COMMAND_STATUS" android:value="" />

### Create a service extending NotificationReceiverService (or update the existing)

Create or update your existing [`NotificationReceiverService`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/notification/NotificationReceiverService) and implement an
[`AppRolesListener`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/approles/AppRolesListener) to listen for roles assigned to your app. Only
`getAppRolesListener()` is required for listening for roles assigned to your
app. If your app is assigned a `COMPANION_APP` role you should also implement
`getCommandListener()`:

    import android.util.Log
    import com.google.android.managementapi.approles.AppRolesListener
    import com.google.android.managementapi.approles.model.AppRolesSetRequest
    import com.google.android.managementapi.approles.model.AppRolesSetResponse
    import com.google.android.managementapi.commands.CommandListener
    import com.google.android.managementapi.commands.model.Command
    import com.google.android.managementapi.notification.NotificationReceiverService

    class MyNotificationReceiverService : NotificationReceiverService() {

      // If your app wants to listen for roles assigned
      override fun getAppRolesListener(): AppRolesListener =
        object : AppRolesListener {
          override fun onAppRolesSet(request: AppRolesSetRequest): AppRolesSetResponse {
            val roleTypes = request.roles.map { role -> role.roleType }
            Log.i(TAG, "onAppRolesSet: $roleTypes")

            return AppRolesSetResponse.getDefaultInstance()
          }
        }

     // If your app wants to listen for local command status updates
     // Only relevant for COMPANION_APP role
     override fun getCommandListener(): CommandListener {
        return object : CommandListener {
          override fun onCommandStatusChanged(command: Command) {
             Log.i(TAG, "onCommandStatusChanged")
          }
        }
      }

      private companion object {
        const val TAG = "MyNotificationReceiverService"
      }
    }

> [!NOTE]
> **Note:** Your app will be notified of its assigned roles only when there is a change in its roles or their extra capabilities.

Your app can be notified multiple times if its roles change multiple times. If
all roles are removed, your app will still be notified with an empty list of
roles. This notification will get your app out of stopped state and along with
the exemptions granted for your app, your app can bootstrap itself without any
user interaction. Thanks to the app roles notification and exemptions, your app
can listen for [`ACTION_BOOT_COMPLETED`](https://developer.android.com/reference/android/content/Intent#ACTION_BOOT_COMPLETED) broadcasts. If your app depends on
its managed configurations to bootstrap itself, see
[Set up managed configurations](https://developer.android.com/work/managed-configurations) on how to read and listen
for changes.

Starting with [AMAPI SDK 1.9.0](https://developers.google.com/android/management/sdk-release-notes#version-1.9.0), each role in the
[`AppRolesSetRequest`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/approles/model/AppRolesSetRequest) also includes the [`Role.ExtraCapabilities`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/model/Role.ExtraCapabilities) that the IT
admin granted in the policy. For example, a companion app can check whether
it's allowed to issue a [local wipe command](https://developers.google.com/android/management/sdk-local-commands#wipe_device):

    import com.google.android.managementapi.approles.model.AppRolesSetRequest
    import com.google.android.managementapi.approles.model.AppRolesSetResponse
    import com.google.android.managementapi.common.model.Role
    ...

      override fun onAppRolesSet(request: AppRolesSetRequest): AppRolesSetResponse {
        val canWipeLocally =
          request.roles.any { role ->
            role.roleType == Role.RoleType.COMPANION_APP &&
              role.extraCapabilities.localDeviceWipeCommand ==
                Role.ExtraCapabilities.LocalDeviceWipeCommand.LOCAL_DEVICE_WIPE_COMMAND_ALLOWED
          }
        Log.i(TAG, "onAppRolesSet: canWipeLocally=$canWipeLocally")

        return AppRolesSetResponse.getDefaultInstance()
      }

Only the IT admin can grant extra capabilities. If you set `extraCapabilities`
on a role that you pass to [`EnvironmentClient`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient), the call fails with an
[`InvalidArgumentException`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/exceptions/InvalidArgumentException).

## Provision the device with app roles policies

App developers can test assigning app roles to their application using and EMM
or following the [Android Management API quickstart](https://developers.google.com/android/management/quickstart). The
AMAPI Colab notebook lets you enroll an enterprise, create a policy,and
provision a device.

### Set the policy for your app with app roles

Set up a `policy` with the app roles that your app is intended to have using the
[`ApplicationPolicy.roles`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies#ApplicationPolicy.FIELDS.roles).

The following example shows how to configure the role for MTD apps:

    {
      "applications": [

        {
          "packageName": "com.example.mtd",
          "installType": "FORCE_INSTALLED",
          "roles": [
            { "roleType": "MOBILE_THREAT_DEFENSE_ENDPOINT_DETECTION_RESPONSE" }
          ]
        }
      ]
    }

> [!IMPORTANT]
> **Key Point:** An app can have multiple roles but any role can be assigned only to a single app. See [`RoleType`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies#roletype) for a list of all the available application roles.

Only the [`COMPANION_APP`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies#RoleType.ENUM_VALUES.COMPANION_APP) role supports
[`extraCapabilities`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies#Role.FIELDS.extra_capabilities). To let the companion app
[wipe the device or work profile locally](https://developers.google.com/android/management/sdk-local-commands#wipe_device), set
`extraCapabilities.localDeviceWipeCommand` to
`LOCAL_DEVICE_WIPE_COMMAND_ALLOWED` for that role.

Before assigning the roles as specified in the policy the system will check that
the app's signing key certificate fingerprint on the device matches the one from
the Play Store.
If the fingerprint is different, the roles are not going to be assigned to the
app and [`NonComplianceReason.APP_SIGNING_CERT_MISMATCH`](https://developers.google.com/android/management/reference/rest/v1/NonComplianceReason)
non-compliance will be reported to the EMM.

> [!NOTE]
> **Note:** It is possible to test different builds of the application, signed with a different certificate, by providing additional signing key certificate fingerprints (base64 encoded SHA-256 hash value of the signing key certificate of the app) to compare against as follows:

    {
      "applications": [

        {
          "packageName": "com.example.mtd",
          "installType": "FORCE_INSTALLED",
          "signingKeyCerts": [
             { "signingKeyCertFingerprintSha256": "base64-encoded-sha256" }
           ],
          "roles": [
            { "roleType": "MOBILE_THREAT_DEFENSE_ENDPOINT_DETECTION_RESPONSE" }
          ]
        }
      ]
    }

If your app has managed configuration, the IT admin can set up an initial
configuration for the relevant [`restriction`](https://developer.android.com/work/managed-configurations#define-configuration)s in the application policy:

    {
      "applications": [

        {
          "packageName": "com.example.mtd",
          "installType": "FORCE_INSTALLED",
          "roles": [
            { "roleType": "MOBILE_THREAT_DEFENSE_ENDPOINT_DETECTION_RESPONSE" }
          ],
          "managedConfiguration": {
            "<key>": "<value>"
          }
        }
      ]
    }

### Verify Battery Exemption Status

Apps with the Mobile Threat Defense (MTD) role are automatically exempt from
standard background power restrictions. However, this exemption is implemented
by the App Standby Bucket rather than the system's Power Allowlist.

> [!IMPORTANT]
> **Important:** If you need to verify any MTD-related exemptions don't use [`PowerManager.isIgnoringBatteryOptimizations()`](https://developer.android.com/reference/android/os/PowerManager#isIgnoringBatteryOptimizations(java.lang.String)) because it will return as `false`. This API checks the Power Allowlist, which will incorrectly indicate that the app is restricted.

To verify if the app exemption is active:

    import android.app.usage.UsageStatsManager
    import android.content.Context

    // UsageStatsManager.STANDBY_BUCKET_EXEMPTED is annotated as a @SystemApi. We can redefine it for clarity.
    // Define this at the top level of your file, or inside a companion object
    private const val STANDBY_BUCKET_EXEMPTED = 5

    private fun isAppExemptedFromAppStandbyBucket(): Boolean =
        (getSystemService(Context.USAGE_STATS_SERVICE) as? UsageStatsManager)
            ?.appStandbyBucket == STANDBY_BUCKET_EXEMPTED