This guide explains how to use the Android Management API (AMAPI) SDK in a
custom Device Policy Controller (DPC) to configure
[Unified Update Controls (UUC)](https://developers.google.com/android/management/unified-update-controls) on company-owned devices.

Using the AMAPI SDK, your custom DPC sends a UUC policy directly to the Android
Device Policy (ADP) app on the device. ADP evaluates the policy rules against
the current device state and instructs Google Play and the OEM OTA client on how
to handle pending updates.

Before you begin, read [Unified Update Controls](https://developers.google.com/android/management/unified-update-controls) to learn how
rules, targets, predicates, and outcomes work, and which limitations apply to
all UUC integrations.

> [!NOTE]
> **Note:** If your EMM solution manages devices with the Android Management API instead of a custom DPC, see [Configure the UUC policy](https://developers.google.com/android/management/unified-update-controls#configure-policy) in the Unified Update Controls guide.

## Prerequisites

Before integrating UUC into your custom DPC, verify the following requirements:

- The device is company-owned, configured either as a [fully managed device](https://developers.google.com/android/work/overview#fully-managed-devices) or with a [work profile on a company-owned device](https://developers.google.com/android/work/overview#company-owned-devices-for-work-and-personal-use).
- Your custom DPC is integrated with the [AMAPI SDK](https://developers.google.com/android/management/sdk-integration).
- The device is enrolled using the [Google Play EMM API](https://developers.google.com/android/work/play/emm-api).
- The device meets the [minimum version requirements](https://developers.google.com/android/management/unified-update-controls#prerequisites) for its management mode and each update target you want to control:
  - Fully managed (Device Owner) devices: Android 13 (API level 33) or higher.
  - Company-owned devices with a work profile (COPE): Android 15 (API level 35) or higher.
- On company-owned devices with a work profile (COPE):
  - Your custom DPC must call `EnvironmentClient` and `PolicyClient` from the work profile (where the custom DPC is the profile owner).
  - A managed Google Play account (provisioned through the Google Play EMM API) must be present in the work profile for UUC to enforce policies on Google Play updates (`GOOGLE_SYSTEM_SERVICES`, `SINGLE_APP`, and `GOOGLE_PLAY_SYSTEM_UPDATE`).

## Integrate with the AMAPI SDK in your custom DPC

To configure a UUC policy on a device, your custom DPC must integrate with the
AMAPI SDK. For instructions on adding the library and configuring your app's
manifest, see the [AMAPI SDK integration guide](https://developers.google.com/android/management/sdk-integration).

> [!NOTE]
> **Note:** You can find the latest available version of the library in the [AMAPI SDK release notes](https://developers.google.com/android/management/sdk-release-notes).  
> **We strongly recommend always
> using the latest version of the library to benefit from the available bug fixes
> and improvements.**

## Steps to configure Unified Update Controls

To configure UUC policies from your custom DPC, ensure that the on-device client
environment is ready and up to date before sending policy requests to ADP:

1. [Verify the client environment](https://developers.google.com/android/management/unified-update-controls-sdk#verify-client-environment)
2. [Prepare the client environment](https://developers.google.com/android/management/unified-update-controls-sdk#prepare-client-environment)
3. [Set, retrieve, and clear the update policy](https://developers.google.com/android/management/unified-update-controls-sdk#manage-update-policy)

### Verify the client environment

> [!NOTE]
> **Note:** UUC requires an up-to-date version of Google Play services, the Google Play Store app, and the ADP app on the device. Use [`EnvironmentClient`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient)'s [`getEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient#getEnvironment(com.google.android.managementapi.environment.model.GetEnvironmentRequest)) method to check whether the ADP app is installed and ready for the [`DEVICE_POLICY_CONTROLLER`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/model/Role.RoleType#DEVICE_POLICY_CONTROLLER) role.

The following example shows how to call [`getEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient#getEnvironment(com.google.android.managementapi.environment.model.GetEnvironmentRequest)) (in Kotlin) or
[`getEnvironmentAsync`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient#getEnvironmentAsync(com.google.android.managementapi.environment.model.GetEnvironmentRequest)) (in Java) with
`Role.RoleType.DEVICE_POLICY_CONTROLLER` to inspect the state of the ADP app. If
the environment is `READY` and `UP_TO_DATE`, your custom DPC can proceed to
configure UUC policies using `PolicyClient`. Otherwise, call
[`prepareEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient#prepareEnvironment(com.google.android.managementapi.environment.model.PrepareEnvironmentRequest,android.content.ComponentName)) to install or update the ADP app (see
[Prepare the client environment](https://developers.google.com/android/management/unified-update-controls-sdk#prepare-client-environment)).

> [!IMPORTANT]
> **Key Point:** Calling [`getEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient#getEnvironment(com.google.android.managementapi.environment.model.GetEnvironmentRequest)) or [`prepareEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient#prepareEnvironment(com.google.android.managementapi.environment.model.PrepareEnvironmentRequest,android.content.ComponentName)) without setting a [`Role`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/model/Role) results in an [`InvalidArgumentException`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/exceptions/InvalidArgumentException).

### Kotlin

```kotlin
import android.content.Context
import android.util.Log
import com.google.android.managementapi.common.model.Role
import com.google.android.managementapi.environment.EnvironmentClientFactory
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.INSTALLED
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.NOT_INSTALLED
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE
import com.google.android.managementapi.environment.model.GetEnvironmentRequest

private suspend fun checkEnvironmentForUuc(context: Context): Boolean {
    try {
        val roles = listOf(
            Role.builder()
                .setRoleType(Role.RoleType.DEVICE_POLICY_CONTROLLER)
                .build()
        )
        val request = GetEnvironmentRequest.builder().setRoles(roles).build()
        val environmentClient = EnvironmentClientFactory.create(context)
        val environmentResponse = environmentClient.getEnvironment(request)

        if (environmentResponse.hasAndroidDevicePolicyEnvironment()) {
            val adpEnvironment = environmentResponse.androidDevicePolicyEnvironment

            when (adpEnvironment.state) {
                READY -> {
                    return if (adpEnvironment.version == UP_TO_DATE) {
                        // AMAPI environment state and version are ready.
                        true
                    } else {
                        prepareEnvironment(context, environmentClient)
                    }
                }

                INSTALLED -> {
                    // Call prepareEnvironment to silently update ADP.
                    return prepareEnvironment(context, environmentClient)
                }

                NOT_INSTALLED -> {
                    // Call prepareEnvironment to install ADP.
                    return prepareEnvironment(context, environmentClient)
                }

                else -> {}
            }
        }
    } catch (e: Exception) {
        Log.e(TAG, "Exception checking environment", e)
    }
    return false
}
```

### Java

```java
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.INSTALLED;
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.NOT_INSTALLED;
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY;
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE;

import android.content.Context;
import android.util.Log;
import com.google.android.managementapi.common.model.Role;
import com.google.android.managementapi.environment.EnvironmentClient;
import com.google.android.managementapi.environment.EnvironmentClientFactory;
import com.google.android.managementapi.environment.model.Environment;
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment;
import com.google.android.managementapi.environment.model.GetEnvironmentRequest;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;

private void checkEnvironmentForUuc(Context context) {
    try {
        ImmutableList roles = new ImmutableList.Builder()
                .add(Role.builder()
                        .setRoleType(Role.RoleType.DEVICE_POLICY_CONTROLLER)
                        .build())
                .build();

        EnvironmentClient environmentClient = EnvironmentClientFactory.create(context);
        GetEnvironmentRequest getRequest = GetEnvironmentRequest.builder()
                .setRoles(roles)
                .build();

        ListenableFuture getEnvironmentFuture =
                environmentClient.getEnvironmentAsync(getRequest);
        Futures.addCallback(getEnvironmentFuture, new FutureCallback() {
            @Override
            public void onSuccess(Environment environment) {
                if (!environment.hasAndroidDevicePolicyEnvironment()) {
                    return;
                }
                AndroidDevicePolicyEnvironment adpEnvironment =
                        environment.getAndroidDevicePolicyEnvironment();

                AndroidDevicePolicyEnvironment.State state = adpEnvironment.getState();
                AndroidDevicePolicyEnvironment.Version version = adpEnvironment.getVersion();
                if (state == READY && version == UP_TO_DATE) {
                    // AMAPI environment state and version are ready.
                    return;
                } else if (state == INSTALLED || state == NOT_INSTALLED) {
                    // Call prepareEnvironment to silently install or update ADP.
                    prepareEnvironment(context, environmentClient);
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.e(TAG, "Failed to verify environment", t);
            }
        }, MoreExecutors.directExecutor());
    } catch (Exception e) {
        Log.e(TAG, "Exception verifying environment", e);
    }
}
```

### Prepare the client environment

If the environment on the device isn't ready or up to date, call
[`prepareEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient#prepareEnvironment(com.google.android.managementapi.environment.model.PrepareEnvironmentRequest,android.content.ComponentName)) before using `PolicyClient`.

When called from a custom DPC with `Role.RoleType.DEVICE_POLICY_CONTROLLER` and
your DPC's [`DeviceAdminReceiver`](https://developer.android.com/reference/android/app/admin/DeviceAdminReceiver) component name passed to `setAdmin()`,
`prepareEnvironment` silently installs the ADP app (if not already installed)
and updates the ADP app and Google Play Store without user interaction. For more
information about environment preparation in custom DPC flows, see
[Environment preparation and user enrollment](https://developers.google.com/android/management/environment-preparation-and-user-enrollment).

> [!NOTE]
> **Note:** In addition to calling `prepareEnvironment`, you can also deploy and update the ADP app (`com.google.android.apps.work.clouddpc`) on managed devices using the Google Play EMM API with:
>
> - [`policy.productPolicy[].autoInstallPolicy`](https://developers.google.com/android/work/play/emm-api/v1/devices#policy.productPolicy.autoInstallPolicy) --- controls the installation priority and constraints for the app.
> - [`policy.productPolicy[].autoUpdateMode`](https://developers.google.com/android/work/play/emm-api/v1/devices#policy.productPolicy.autoUpdateMode) --- controls the app update priority (for example, `AUTO_UPDATE_HIGH_PRIORITY`).

> [!IMPORTANT]
> **Key Point:** When requesting `Role.RoleType.DEVICE_POLICY_CONTROLLER`, you must specify your custom DPC's `DeviceAdminReceiver` component using `PrepareEnvironmentRequest.Builder.setAdmin()`. Omitting the admin component results in an [`InvalidArgumentException`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/exceptions/InvalidArgumentException).

### Kotlin

```kotlin
import android.content.ComponentName
import android.content.Context
import android.util.Log
import com.google.android.managementapi.common.model.Role
import com.google.android.managementapi.environment.EnvironmentClient
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE
import com.google.android.managementapi.environment.model.PrepareEnvironmentRequest

private suspend fun prepareEnvironment(
    context: Context,
    environmentClient: EnvironmentClient
): Boolean {
    try {
        val roles = listOf(
            Role.builder()
                .setRoleType(Role.RoleType.DEVICE_POLICY_CONTROLLER)
                .build()
        )
        val request = PrepareEnvironmentRequest.builder()
            .setRoles(roles)
            .setAdmin(
                ComponentName(
                    context,
                    DeviceAdminReceiver::class.java
                )
            )
            .build()

        val response = environmentClient.prepareEnvironment(request, null)

        val environment = response.environment
        val adpEnvironment = environment.androidDevicePolicyEnvironment

        val state = adpEnvironment.state
        val version = adpEnvironment.version
        return (state == READY && version == UP_TO_DATE)
    } catch (e: Exception) {
        Log.e(TAG, "Failed to prepare environment", e)
    }
    return false
}
```

### Java

```java
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY;
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE;

import android.content.ComponentName;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.managementapi.common.model.Role;
import com.google.android.managementapi.environment.EnvironmentClient;
import com.google.android.managementapi.environment.model.Environment;
import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment;
import com.google.android.managementapi.environment.model.PrepareEnvironmentRequest;
import com.google.android.managementapi.environment.model.PrepareEnvironmentResponse;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;

private void prepareEnvironment(Context context, EnvironmentClient environmentClient) {
    try {
        ImmutableList roles = new ImmutableList.Builder()
                .add(Role.builder()
                        .setRoleType(Role.RoleType.DEVICE_POLICY_CONTROLLER)
                        .build())
                .build();

        ComponentName adminComponent = new ComponentName(
                context,
                DeviceAdminReceiver.class
        );

        PrepareEnvironmentRequest request = PrepareEnvironmentRequest.builder()
                .setRoles(roles)
                .setAdmin(adminComponent)
                .build();

        ListenableFuture environmentFuture =
                environmentClient.prepareEnvironmentAsync(request, null);

        Futures.addCallback(environmentFuture, new FutureCallback() {
            @Override
            public void onSuccess(PrepareEnvironmentResponse response) {
                Environment environment = response.getEnvironment();
                AndroidDevicePolicyEnvironment adpEnvironment =
                        environment.getAndroidDevicePolicyEnvironment();

                AndroidDevicePolicyEnvironment.State state = adpEnvironment.getState();
                AndroidDevicePolicyEnvironment.Version version = adpEnvironment.getVersion();
                if (state == READY && version == UP_TO_DATE) {
                    // Environment is prepared; configure UUC policy using PolicyClient.
                } else {
                    Log.w(
                            TAG,
                            "AMAPI environment was not ready: " + state + " - " + version
                    );
                }
            }

            @Override
            public void onFailure(@NonNull Throwable t) {
                Log.e(TAG, "Failed to prepare environment", t);
            }
        }, MoreExecutors.directExecutor());
    } catch (Exception e) {
        Log.e(TAG, "Exception preparing environment", e);
    }
}
```

### Set, retrieve, and clear the update policy

> [!IMPORTANT]
> **Key Point:** Configuring or reading the update policy requires an up-to-date environment. Always verify the environment as described in [Verify the client environment](https://developers.google.com/android/management/unified-update-controls-sdk#verify-client-environment) before calling `PolicyClient`.

After the environment is ready, use `PolicyClient` (created with
`PolicyClientFactory.create(context)`) to apply a `Policy` containing your
`UnifiedUpdatePolicy` rules, inspect the active policy, or clear the UUC policy
from the device.

#### PolicyClient usage recommendation

Creating a `PolicyClient` instance establishes a communication channel with the
ADP app. To optimize performance, we recommend creating a single instance of
`PolicyClient` using `PolicyClientFactory.create()` and caching it for reuse
throughout your application. Avoid creating a new `PolicyClient` instance while
another is active, because doing so can force the existing communication channel
to close, which incurs overhead and can lead to resource leaks.

#### Set the update policy

To apply UUC rules, build a `Policy.UnifiedUpdatePolicy` containing your list of
`Rule` objects, attach it to a `Policy` inside an `UpdatePolicyRequest`, and
call `updatePolicy` (in Kotlin) or `updatePolicyFuture` (in Java):

### Kotlin

```kotlin
import android.util.Log
import com.google.android.managementapi.policy.PolicyClient
import com.google.android.managementapi.policy.model.Policy
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule
import com.google.android.managementapi.policy.model.UpdatePolicyRequest

suspend fun setUucPolicy(
    policyClient: PolicyClient,
    rules: Iterable
): Boolean {
    // Build the UnifiedUpdatePolicy and attach it to the overall Policy.
    val unifiedUpdatePolicy = Policy.UnifiedUpdatePolicy.builder()
        .setRules(rules)
        .build()

    val policy = Policy.builder()
        .setUnifiedUpdatePolicy(unifiedUpdatePolicy)
        .build()

    // Create the UpdatePolicyRequest.
    val updateRequest = UpdatePolicyRequest.builder()
        .setPolicy(policy)
        .build()

    // Apply the policy using PolicyClient within a CoroutineScope.
    return try {
        val updatedPolicy = policyClient.updatePolicy(updateRequest)
        Log.d(TAG, "Policy updated successfully: $updatedPolicy")
        true
    } catch (e: Exception) {
        Log.e(TAG, "Error updating policy: ${e.message}", e)
        false
    }
}
```

### Java

```java
import android.util.Log;
import com.google.android.managementapi.policy.PolicyClient;
import com.google.android.managementapi.policy.model.Policy;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule;
import com.google.android.managementapi.policy.model.UpdatePolicyRequest;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

ListenableFuture setUucPolicy(
        PolicyClient policyClient,
        Iterable rules
) {
    // Build the UnifiedUpdatePolicy and attach it to the overall Policy.
    UnifiedUpdatePolicy unifiedUpdatePolicy = UnifiedUpdatePolicy.builder()
            .setRules(
                    StreamSupport.stream(rules.spliterator(), false)
                            .collect(Collectors.toList())
            )
            .build();

    Policy policy = Policy.builder()
            .setUnifiedUpdatePolicy(unifiedUpdatePolicy)
            .build();

    // Create the UpdatePolicyRequest.
    UpdatePolicyRequest updateRequest = UpdatePolicyRequest.builder()
            .setPolicy(policy)
            .build();

    // Apply the policy using PolicyClient.
    try {
        ListenableFuture updateFuture =
                policyClient.updatePolicyFuture(updateRequest);
        return Futures.transform(updateFuture, updatedPolicy -> {
            Log.d(TAG, "Policy updated successfully: " + updatedPolicy);
            return true;
        }, MoreExecutors.directExecutor());
    } catch (Exception e) {
        Log.e(TAG, "Error updating policy: " + e.getMessage(), e);
        return Futures.immediateFuture(false);
    }
}
```

#### Retrieve the active update policy

To inspect the active UUC policy on the device, build a `GetPolicyRequest` and
call `getPolicy` (in Kotlin) or `getPolicyFuture` (in Java). Use
`hasUnifiedUpdatePolicy()` on the returned `Policy` to check whether a UUC
policy is configured:

### Kotlin

```kotlin
import android.util.Log
import com.google.android.managementapi.policy.PolicyClient
import com.google.android.managementapi.policy.model.GetPolicyRequest
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule

suspend fun getActiveUucPolicy(policyClient: PolicyClient) {
    try {
        val getPolicyRequest = GetPolicyRequest.builder().build()
        val activePolicy = policyClient.getPolicy(getPolicyRequest)
        Log.i(TAG, "Policy version: ${activePolicy.version}")

        if (!activePolicy.hasUnifiedUpdatePolicy()) {
            Log.i(TAG, "No Unified Update Policy set.")
            return
        }

        val activeUucPolicy = activePolicy.unifiedUpdatePolicy
        val rules: List = activeUucPolicy.rules.toList()
        rules.forEachIndexed { index, rule ->
            val outcome = if (rule.hasOutcome()) rule.outcome.toString() else "[no outcome]"
            val predicate = if (rule.hasPredicate()) rule.predicate.toString() else "[no predicate]"
            Log.i(TAG, "Rule [$index] targets: ${rule.targets}")
            Log.i(TAG, "Rule [$index] outcome: $outcome")
            Log.i(TAG, "Rule [$index] predicate: $predicate")
        }
    } catch (e: Exception) {
        Log.e(TAG, "Error retrieving policy: ${e.message}", e)
    }
}
```

### Java

```java
import android.util.Log;
import com.google.android.managementapi.policy.PolicyClient;
import com.google.android.managementapi.policy.model.GetPolicyRequest;
import com.google.android.managementapi.policy.model.Policy;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

void getActiveUucPolicy(PolicyClient policyClient) {
    try {
        GetPolicyRequest getPolicyRequest = GetPolicyRequest.builder().build();
        ListenableFuture getActivePolicy =
                policyClient.getPolicyFuture(getPolicyRequest);

        Futures.addCallback(getActivePolicy, new FutureCallback() {
            @Override
            public void onSuccess(Policy result) {
                if (!result.hasUnifiedUpdatePolicy()) {
                    Log.i(TAG, "No Unified Update Policy set.");
                    return;
                }

                UnifiedUpdatePolicy activeUucPolicy = result.getUnifiedUpdatePolicy();
                List uucRules = StreamSupport.stream(
                                activeUucPolicy.getRules().spliterator(), false)
                        .collect(Collectors.toList());

                for (int i = 0; i < uucRules.size(); i++) {
                    Rule rule = uucRules.get(i);
                    String outcome = rule.hasOutcome()
                            ? rule.getOutcome().toString() : "[no outcome]";
                    String predicate = rule.hasPredicate()
                            ? rule.getPredicate().toString() : "[no predicate]";
                    Log.i(TAG, "Rule [" + i + "] targets: " + rule.getTargets());
                    Log.i(TAG, "Rule [" + i + "] outcome: " + outcome);
                    Log.i(TAG, "Rule [" + i + "] predicate: " + predicate);
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.e(TAG, "Error retrieving policy", t);
            }
        }, MoreExecutors.directExecutor());
    } catch (Exception e) {
        Log.e(TAG, "Exception retrieving policy", e);
    }
}
```

#### Clear the update policy

To remove the `UnifiedUpdatePolicy` from the device without clearing other
fields on `Policy`, retrieve the current `Policy` using `getPolicy`, call
`currentPolicy.toBuilder().clearUnifiedUpdatePolicy().build()`, and apply the
updated `Policy`:

### Kotlin

```kotlin
import android.util.Log
import com.google.android.managementapi.policy.PolicyClient
import com.google.android.managementapi.policy.model.GetPolicyRequest
import com.google.android.managementapi.policy.model.UpdatePolicyRequest

suspend fun clearUucPolicy(policyClient: PolicyClient): Boolean {
    try {
        // 1. Retrieve the current policy.
        val currentPolicy = policyClient.getPolicy(
            GetPolicyRequest.builder().build()
        )
        if (!currentPolicy.hasUnifiedUpdatePolicy()) {
            Log.i(TAG, "Policy does not have a UnifiedUpdatePolicy to clear.")
            return true
        }

        // 2. Convert to a builder and clear the UnifiedUpdatePolicy field.
        val policyWithUucCleared = currentPolicy.toBuilder()
            .clearUnifiedUpdatePolicy()
            .build()

        // 3. Apply the modified Policy object.
        val updateRequest = UpdatePolicyRequest.builder()
            .setPolicy(policyWithUucCleared)
            .build()
        val updatedPolicy = policyClient.updatePolicy(updateRequest)

        return !updatedPolicy.hasUnifiedUpdatePolicy()
    } catch (e: Exception) {
        Log.e(TAG, "Error clearing policy: ${e.message}", e)
        return false
    }
}
```

### Java

```java
import android.util.Log;
import com.google.android.managementapi.policy.PolicyClient;
import com.google.android.managementapi.policy.model.GetPolicyRequest;
import com.google.android.managementapi.policy.model.Policy;
import com.google.android.managementapi.policy.model.UpdatePolicyRequest;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;

ListenableFuture clearUucPolicy(PolicyClient policyClient) {
    try {
        ListenableFuture currentPolicyFuture =
                policyClient.getPolicyFuture(GetPolicyRequest.builder().build());

        return Futures.transformAsync(currentPolicyFuture, currentPolicy -> {
            if (!currentPolicy.hasUnifiedUpdatePolicy()) {
                Log.i(TAG, "Policy does not have a UnifiedUpdatePolicy to clear.");
                return Futures.immediateFuture(true);
            }

            Policy policyWithUucCleared = currentPolicy.toBuilder()
                    .clearUnifiedUpdatePolicy()
                    .build();

            UpdatePolicyRequest updateRequest = UpdatePolicyRequest.builder()
                    .setPolicy(policyWithUucCleared)
                    .build();

            ListenableFuture updateFuture =
                    policyClient.updatePolicyFuture(updateRequest);

            return Futures.transform(
                    updateFuture,
                    updatedPolicy -> !updatedPolicy.hasUnifiedUpdatePolicy(),
                    MoreExecutors.directExecutor()
            );
        }, MoreExecutors.directExecutor());
    } catch (Exception e) {
        Log.e(TAG, "Error clearing policy: " + e.getMessage(), e);
        return Futures.immediateFuture(false);
    }
}
```

## AOSP SystemUpdatePolicy precedence

> [!IMPORTANT]
> **Important:** An AOSP `SystemUpdatePolicy` takes precedence over UUC rules for OEM OTA updates (`OS_UPDATE_WITH_API_LEVEL_CHANGE` and `OS_UPDATE_WITHOUT_API_LEVEL_CHANGE`), matching the `systemUpdate` precedence behavior in the Android Management API. For details, see [Precedence over systemUpdate](https://developers.google.com/android/management/unified-update-controls#system-update-precedence).

In a custom DPC, the Android Compatibility Definition Document ([CDD](https://source.android.com/docs/compatibility/16/android-16-cdd#11_updatable_software))
requires devices to honor the AOSP [`SystemUpdatePolicy`](https://developer.android.com/reference/android/app/admin/SystemUpdatePolicy) set through
[`DevicePolicyManager.setSystemUpdatePolicy`](https://developer.android.com/reference/android/app/admin/DevicePolicyManager#setSystemUpdatePolicy(android.content.ComponentName,%20android.app.admin.SystemUpdatePolicy)).

As a result, OEM OTA update behavior depends on whether an AOSP
`SystemUpdatePolicy` is active on the device:

- Both UUC policy and AOSP `SystemUpdatePolicy` are set: The AOSP `SystemUpdatePolicy` takes precedence for OEM OTA updates, and the UUC targets `OS_UPDATE_WITH_API_LEVEL_CHANGE` and `OS_UPDATE_WITHOUT_API_LEVEL_CHANGE` are ignored. Clear the AOSP `SystemUpdatePolicy` in your custom DPC if you want UUC to manage OEM OTA updates.
- Only UUC policy is set (AOSP `SystemUpdatePolicy` is cleared): The OEM OTA agent applies the UUC policy for OEM OTA updates on supported devices, unless an OEM-specific update management service (such as an enterprise FOTA solution) is active and takes precedence on the device.
- Devices that don't yet support UUC for OEM OTA updates: On devices where OEM OTA support requires Android 17 (API level 37), you can use UUC to control Google Play system updates (`GOOGLE_PLAY_SYSTEM_UPDATE`) and Google system services (`GOOGLE_SYSTEM_SERVICES` and `SINGLE_APP`) while continuing to use AOSP `SystemUpdatePolicy` for OEM OTA updates.

You can check whether an AOSP `SystemUpdatePolicy` is active on the device using
`DevicePolicyManager`:

### Kotlin

```kotlin
import android.app.admin.DevicePolicyManager
import android.content.Context

fun Context.isSystemUpdatePolicyActive(): Boolean {
    val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as? DevicePolicyManager
    return dpm?.systemUpdatePolicy != null
}
```

### Java

```java
import android.app.admin.DevicePolicyManager;
import android.content.Context;

boolean isSystemUpdatePolicyActive(Context context) {
    DevicePolicyManager dpm =
            (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
    return dpm != null && dpm.getSystemUpdatePolicy() != null;
}
```

## Policy configuration examples

The following examples show how to build UUC rules in Kotlin and Java and apply
them using the `setUucPolicy` helper defined in
[Set, retrieve, and clear the update policy](https://developers.google.com/android/management/unified-update-controls-sdk#manage-update-policy). For the
meaning of each field, see [Understand UUC policies](https://developers.google.com/android/management/unified-update-controls#understand-uuc-policies). The same
scenarios are available as JSON in
[Unified Update Controls](https://developers.google.com/android/management/unified-update-controls#policy-examples).

### Block major OS updates during business hours

The following rule blocks major OS updates (`OS_UPDATE_WITH_API_LEVEL_CHANGE`)
from 09:00 to 17:00, Monday through Friday:

### Kotlin

```kotlin
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.BLOCK
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.OS_UPDATE_WITH_API_LEVEL_CHANGE
import java.time.DayOfWeek
import java.time.LocalTime

// 1. Define the target (OS update with API level change).
val target = Target.builder()
    .setTargetType(OS_UPDATE_WITH_API_LEVEL_CHANGE)
    .build()

// 2. Define the predicate (Monday through Friday, 09:00 to 17:00).
val predicate = Predicate.builder()
    .setDaysOfWeek(
        listOf(
            DayOfWeek.MONDAY,
            DayOfWeek.TUESDAY,
            DayOfWeek.WEDNESDAY,
            DayOfWeek.THURSDAY,
            DayOfWeek.FRIDAY
        )
    )
    .setTimeOfDayLowerBound(LocalTime.of(9, 0))
    .setTimeOfDayUpperBound(LocalTime.of(17, 0))
    .build()

// 3. Define the outcome (BLOCK).
val outcome = Outcome.builder()
    .setOutcomeType(BLOCK)
    .build()

// 4. Assemble and apply the rule.
val rule = Rule.builder()
    .setTargets(listOf(target))
    .setPredicate(predicate)
    .setOutcome(outcome)
    .setId("Block Policy")
    .build()

setUucPolicy(policyClient, listOf(rule))
```

### Java

```java
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.BLOCK;
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.OS_UPDATE_WITH_API_LEVEL_CHANGE;

import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.List;

// 1. Define the target (OS update with API level change).
Target target = Target.builder()
        .setTargetType(OS_UPDATE_WITH_API_LEVEL_CHANGE)
        .build();

// 2. Define the predicate (Monday through Friday, 09:00 to 17:00).
Predicate predicate = Predicate.builder()
        .setDaysOfWeek(
                List.of(
                        DayOfWeek.MONDAY,
                        DayOfWeek.TUESDAY,
                        DayOfWeek.WEDNESDAY,
                        DayOfWeek.THURSDAY,
                        DayOfWeek.FRIDAY
                )
        )
        .setTimeOfDayLowerBound(LocalTime.of(9, 0))
        .setTimeOfDayUpperBound(LocalTime.of(17, 0))
        .build();

// 3. Define the outcome (BLOCK).
Outcome outcome = Outcome.builder()
        .setOutcomeType(BLOCK)
        .build();

// 4. Assemble and apply the rule.
Rule rule = Rule.builder()
        .setTargets(List.of(target))
        .setPredicate(predicate)
        .setOutcome(outcome)
        .setId("Block Policy")
        .build();

setUucPolicy(policyClient, List.of(rule));
```

### Force OS updates by update age

The following rule forces major OS updates (`OS_UPDATE_WITH_API_LEVEL_CHANGE`)
with an update age upper bound of 7 days:

### Kotlin

```kotlin
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.FORCE
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.OS_UPDATE_WITH_API_LEVEL_CHANGE
import java.time.Duration

val target = Target.builder()
    .setTargetType(OS_UPDATE_WITH_API_LEVEL_CHANGE)
    .build()

val predicate = Predicate.builder()
    .setUpdateAgeUpperBound(Duration.ofDays(7))
    .build()

val outcome = Outcome.builder()
    .setOutcomeType(FORCE)
    .build()

val rule = Rule.builder()
    .setTargets(listOf(target))
    .setPredicate(predicate)
    .setOutcome(outcome)
    .setId("Force Policy")
    .build()

setUucPolicy(policyClient, listOf(rule))
```

### Java

```java
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.FORCE;
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.OS_UPDATE_WITH_API_LEVEL_CHANGE;

import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target;
import java.time.Duration;
import java.util.List;

Target target = Target.builder()
        .setTargetType(OS_UPDATE_WITH_API_LEVEL_CHANGE)
        .build();

Predicate predicate = Predicate.builder()
        .setUpdateAgeUpperBound(Duration.ofDays(7))
        .build();

Outcome outcome = Outcome.builder()
        .setOutcomeType(FORCE)
        .build();

Rule rule = Rule.builder()
        .setTargets(List.of(target))
        .setPredicate(predicate)
        .setOutcome(outcome)
        .setId("Force Policy")
        .build();

setUucPolicy(policyClient, List.of(rule));
```

### Block updates for a single Google system service app

To control a specific package that is part of Google system services---such as
Android System WebView (`com.google.android.webview`)---set the target type to
`SINGLE_APP` and provide a `SingleApp` object with the package name:

### Kotlin

```kotlin
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.BLOCK
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.SingleApp
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.SINGLE_APP
import java.time.DayOfWeek
import java.time.LocalTime

val target = Target.builder()
    .setTargetType(SINGLE_APP)
    .setSingleApp(
        SingleApp.builder()
            .setPackageName("com.google.android.webview")
            .build()
    )
    .build()

val predicate = Predicate.builder()
    .setDaysOfWeek(
        listOf(
            DayOfWeek.MONDAY,
            DayOfWeek.TUESDAY,
            DayOfWeek.WEDNESDAY,
            DayOfWeek.THURSDAY,
            DayOfWeek.FRIDAY
        )
    )
    .setTimeOfDayLowerBound(LocalTime.of(9, 0))
    .setTimeOfDayUpperBound(LocalTime.of(17, 0))
    .build()

val outcome = Outcome.builder()
    .setOutcomeType(BLOCK)
    .build()

val rule = Rule.builder()
    .setTargets(listOf(target))
    .setPredicate(predicate)
    .setOutcome(outcome)
    .setId("Block Webview Policy")
    .build()

setUucPolicy(policyClient, listOf(rule))
```

### Java

```java
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.BLOCK;
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.SINGLE_APP;

import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.SingleApp;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.List;

Target target = Target.builder()
        .setTargetType(SINGLE_APP)
        .setSingleApp(
                SingleApp.builder()
                        .setPackageName("com.google.android.webview")
                        .build()
        )
        .build();

Predicate predicate = Predicate.builder()
        .setDaysOfWeek(
                List.of(
                        DayOfWeek.MONDAY,
                        DayOfWeek.TUESDAY,
                        DayOfWeek.WEDNESDAY,
                        DayOfWeek.THURSDAY,
                        DayOfWeek.FRIDAY
                )
        )
        .setTimeOfDayLowerBound(LocalTime.of(9, 0))
        .setTimeOfDayUpperBound(LocalTime.of(17, 0))
        .build();

Outcome outcome = Outcome.builder()
        .setOutcomeType(BLOCK)
        .build();

Rule rule = Rule.builder()
        .setTargets(List.of(target))
        .setPredicate(predicate)
        .setOutcome(outcome)
        .setId("Block Webview Policy")
        .build();

setUucPolicy(policyClient, List.of(rule));
```

### Combine multiple rules across all update targets

Because rules are evaluated in order, you can combine multiple rules to block
updates during specific windows (such as business hours or before a target
calendar date) and force updates outside those windows.

The following example applies two sequential rules across all four category
targets (`OS_UPDATE_WITH_API_LEVEL_CHANGE`,
`OS_UPDATE_WITHOUT_API_LEVEL_CHANGE`, `GOOGLE_SYSTEM_SERVICES`, and
`GOOGLE_PLAY_SYSTEM_UPDATE`):

1. Block all updates during business hours (Monday--Friday, 09:00 to 17:00).
2. Outside business hours, force updates that have been available for up to 7 days.

### Kotlin

```kotlin
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.BLOCK
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.FORCE
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.GOOGLE_PLAY_SYSTEM_UPDATE
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.GOOGLE_SYSTEM_SERVICES
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.OS_UPDATE_WITHOUT_API_LEVEL_CHANGE
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.OS_UPDATE_WITH_API_LEVEL_CHANGE
import java.time.DayOfWeek
import java.time.Duration
import java.time.LocalTime

// 1. Define the targets to reuse across both rules.
val allTargets = listOf(
    Target.builder().setTargetType(OS_UPDATE_WITH_API_LEVEL_CHANGE).build(),
    Target.builder().setTargetType(OS_UPDATE_WITHOUT_API_LEVEL_CHANGE).build(),
    Target.builder().setTargetType(GOOGLE_SYSTEM_SERVICES).build(),
    Target.builder().setTargetType(GOOGLE_PLAY_SYSTEM_UPDATE).build()
)

// 2. Rule 1: Block updates during business hours.
val blockPredicate = Predicate.builder()
    .setDaysOfWeek(
        listOf(
            DayOfWeek.MONDAY,
            DayOfWeek.TUESDAY,
            DayOfWeek.WEDNESDAY,
            DayOfWeek.THURSDAY,
            DayOfWeek.FRIDAY
        )
    )
    .setTimeOfDayLowerBound(LocalTime.of(9, 0))
    .setTimeOfDayUpperBound(LocalTime.of(17, 0))
    .build()

val blockOutcome = Outcome.builder()
    .setOutcomeType(BLOCK)
    .build()

val rule1 = Rule.builder()
    .setTargets(allTargets)
    .setPredicate(blockPredicate)
    .setOutcome(blockOutcome)
    .setId("Block Policy - rule1")
    .build()

// 3. Rule 2: Force updates within 7 days of availability.
val forcePredicate = Predicate.builder()
    .setUpdateAgeUpperBound(Duration.ofDays(7))
    .build()

val forceOutcome = Outcome.builder()
    .setOutcomeType(FORCE)
    .build()

val rule2 = Rule.builder()
    .setTargets(allTargets)
    .setPredicate(forcePredicate)
    .setOutcome(forceOutcome)
    .setId("Force Policy - rule2")
    .build()

setUucPolicy(policyClient, listOf(rule1, rule2))
```

### Java

```java
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.BLOCK;
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.FORCE;
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.GOOGLE_PLAY_SYSTEM_UPDATE;
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.GOOGLE_SYSTEM_SERVICES;
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.OS_UPDATE_WITHOUT_API_LEVEL_CHANGE;
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target.TargetType.OS_UPDATE_WITH_API_LEVEL_CHANGE;

import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Target;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalTime;
import java.util.List;

// 1. Define the targets to reuse across both rules.
List allTargets = List.of(
        Target.builder().setTargetType(OS_UPDATE_WITH_API_LEVEL_CHANGE).build(),
        Target.builder().setTargetType(OS_UPDATE_WITHOUT_API_LEVEL_CHANGE).build(),
        Target.builder().setTargetType(GOOGLE_SYSTEM_SERVICES).build(),
        Target.builder().setTargetType(GOOGLE_PLAY_SYSTEM_UPDATE).build()
);

// 2. Rule 1: Block updates during business hours.
Predicate blockPredicate = Predicate.builder()
        .setDaysOfWeek(
                List.of(
                        DayOfWeek.MONDAY,
                        DayOfWeek.TUESDAY,
                        DayOfWeek.WEDNESDAY,
                        DayOfWeek.THURSDAY,
                        DayOfWeek.FRIDAY
                )
        )
        .setTimeOfDayLowerBound(LocalTime.of(9, 0))
        .setTimeOfDayUpperBound(LocalTime.of(17, 0))
        .build();

Outcome blockOutcome = Outcome.builder()
        .setOutcomeType(BLOCK)
        .build();

Rule rule1 = Rule.builder()
        .setTargets(allTargets)
        .setPredicate(blockPredicate)
        .setOutcome(blockOutcome)
        .setId("Block Policy - rule1")
        .build();

// 3. Rule 2: Force updates within 7 days of availability.
Predicate forcePredicate = Predicate.builder()
        .setUpdateAgeUpperBound(Duration.ofDays(7))
        .build();

Outcome forceOutcome = Outcome.builder()
        .setOutcomeType(FORCE)
        .build();

Rule rule2 = Rule.builder()
        .setTargets(allTargets)
        .setPredicate(forcePredicate)
        .setOutcome(forceOutcome)
        .setId("Force Policy - rule2")
        .build();

setUucPolicy(policyClient, List.of(rule1, rule2));
```

### Restrict updates until a calendar date

You can use `setDateLowerBound` and `setDateUpperBound` on `Predicate.Builder`
to restrict or allow updates within a specific calendar date range in the
device's local time zone:

### Kotlin

```kotlin
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.BLOCK
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate
import java.time.LocalDate

val blockUntilDateRule = Rule.builder()
    .setId("block_until_date")
    .setTargets(allTargets)
    .setPredicate(
        Predicate.builder()
            .setDateUpperBound(LocalDate.of(2026, 3, 14))
            .build()
    )
    .setOutcome(Outcome.builder().setOutcomeType(BLOCK).build())
    .build()

setUucPolicy(policyClient, listOf(blockUntilDateRule))
```

### Java

```java
import static com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome.OutcomeType.BLOCK;

import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Outcome;
import com.google.android.managementapi.policy.model.Policy.UnifiedUpdatePolicy.Rule.Predicate;
import java.time.LocalDate;
import java.util.List;

Rule blockUntilDateRule = Rule.builder()
        .setId("block_until_date")
        .setTargets(allTargets)
        .setPredicate(
                Predicate.builder()
                        .setDateUpperBound(LocalDate.of(2026, 3, 14))
                        .build()
        )
        .setOutcome(Outcome.builder().setOutcomeType(BLOCK).build())
        .build();

setUucPolicy(policyClient, List.of(blockUntilDateRule));
```

## Handle errors

If a `PolicyClient` call fails, your custom DPC receives an exception (thrown
directly by the Kotlin `suspend` methods `getPolicy` and `updatePolicy`, or
wrapped as the cause of `ExecutionException` in `getPolicyFuture` and
`updatePolicyFuture`). Common exceptions include:

- `ApiLevelException`: The device runs an Android version lower than Android 13 (API level 33).
- `DeviceNotManagedException`: The device isn't managed as a fully managed device (Device Owner) or a company-owned device with a work profile (COPE).
- `AndroidDevicePolicyAppNotInstalledException`, `AndroidDevicePolicyAppDisabledException`, or `AndroidDevicePolicyAppNotUpdatedException`: The ADP app isn't installed, is disabled, or runs an older version that doesn't support `PolicyClient`. Call [`prepareEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient#prepareEnvironment(com.google.android.managementapi.environment.model.PrepareEnvironmentRequest,android.content.ComponentName)) to install or update the ADP app.
- [`InvalidArgumentException`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/exceptions/InvalidArgumentException): The request is malformed or contains invalid policy rules (for example, omitting a rule `id` or using a duplicate `id`, setting minutes or seconds on a time-of-day bound, specifying an update age duration that isn't a multiple of 24 hours, including duplicate targets, or explicitly setting `OutcomeType.DEFAULT` in a rule).
- [`SecurityException`](https://developer.android.com/reference/java/lang/SecurityException): The calling app isn't authorized to manage the device policy.
- `TimeoutException` (`java.util.concurrent.TimeoutException`), `InternalException`, or another subclass of [`AmapiSdkException`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/exceptions/AmapiSdkException): A transient IPC or internal error occurred while communicating with the ADP app. Retry the call using an exponential backoff strategy.

### Retry strategies

For exceptions caused by a transient condition (such as `TimeoutException` or
`InternalException`), implement a retry strategy with exponential backoff:

1. After the first failure, wait for an initial delay of 5 seconds before retrying.
2. Increase the delay exponentially on each subsequent attempt (10 seconds, 20 seconds, and so on) up to a maximum number of attempts.

> [!NOTE]
> **Note:** Logging `SecurityException` and `AmapiSdkException` messages locally or remotely with tools such as [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics) helps diagnose errors during the rollout of Unified Update Controls.