Manage system updates

This developer’s guide explains how your device policy controller (DPC) can manage Android system updates on behalf of the device user.

Introduction

Android devices can receive and install over-the-air (OTA) updates to the system and application software. Android notifies the device user that a system update is available and the device user can install the update immediately or later.

Using your DPC, an IT admin can manage system updates for the device user. DPCs can own a fully managed device (called a device owner) or can own a work profile (called a profile owner). Table 1 shows how device owners can manage system updates, while profile owners can only report information about system updates.

Table 1: Tasks available to DPCs depend on the owner mode

Task Device owner Profile owner
Check for pending system updates
Receive callbacks when new system updates become available
Set a local update policy to control when Android installs system updates
Freeze the OS version over critical periods

Check for pending updates

A pending update is a system update for a device that hasn’t yet been installed. Your DPC can help IT admins check which devices have pending system updates—and perhaps ask the device users to install critical updates promptly.

Device owners and profile owners running in Android 8.0 (API level 26) or higher can check if a device has a pending system update. Call DevicePolicyManager.getPendingSystemUpdate() which returns null if the device is up to date. If a system update is pending, the method returns information about the update.

Find out more about a pending update

After calling getPendingSystemUpdate() you can inspect the returned SystemUpdateInfo value to find out more about the pending update. The following example shows how you might find out when a pending update was first available to the device:

Kotlin

val firstAvailable =
        dpm.getPendingSystemUpdate(adminName)?.receivedTime
firstAvailable?.let {
    Log.i(TAG, "Update first available: ${Date(firstAvailable)}")
}

Java

SystemUpdateInfo updateInfo = dpm.getPendingSystemUpdate(adminName);
if (updateInfo != null) {
  Long firstAvailable = updateInfo.getReceivedTime();
  Log.i(TAG, "Update first available: " + new Date(firstAvailable));
}

System callbacks

When an update becomes available, the Android system notifies device owners about the new update. In Android 8.0 or higher, the system notifies profile owners too.

In your DeviceAdminReceiver subclass, override the onSystemUpdatePending() callback. You don’t need to register or advertise for your DPC to receive the callback. The system might call this method more than once for a single update so check the update's status before responding. Call getPendingSystemUpdate() to find out more about the system update in the callback. The following example shows how you can do this:

Kotlin

/**
 * Called when a new update is available.
 */
override fun onSystemUpdatePending(context: Context?, intent: Intent?,
                                   receivedTime: Long) {

    // System update information is supported in API level 26 or higher.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        return
    }

    val updateInfo = getManager(context)
            .getPendingSystemUpdate(getWho(context))
            ?: return
    if (updateInfo.securityPatchState ==
            SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE) {
        // Perhaps install because this is a security patch.
        // ...
    }
}

Java

/**
 * Called when a new update is available.
 */
public void onSystemUpdatePending (Context context, Intent intent,
                                   long receivedTime) {

  // System update information is supported in API level 26 or higher.
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
    return;
  }
  SystemUpdateInfo updateInfo = getManager(context)
      .getPendingSystemUpdate(getWho(context));
  if (updateInfo == null) {
    return;
  }
  if (updateInfo.getSecurityPatchState() ==
      SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE) {
    // Perhaps install because this is a security patch.
    // ...
  }
}

When a system has more than one DPC, for example work profiles on fully managed devices, the device owner and profile owner both receive the callback.

Update policies

A device owner can control when updates are installed by setting a local system update policy for a device. The system update policy can be one of three types:

Automatic
Installs system updates as soon as they become available (without user interaction). Setting this policy type immediately installs any pending updates that might be postponed or waiting for a maintenance window.
Windowed
Installs system updates during a daily maintenance window (without user interaction). Set the start and end of the daily maintenance window, as minutes of the day, when creating a new windowed policy.
Postponed
Postpones the installation of system updates for 30 days. After the 30-day period has ended, the system prompts the device user to install the update.

Postponement periods

The system limits each update to one 30-day postponement. The period begins when the system first postpones the update and setting new postponement policies won’t extend the period.

Besides postponement, Android might not be able to install an update for other reasons such as no connectivity, insufficient disk space, or low battery.

The system resets the 30-day postponement timer if a different update becomes available during the period—giving IT admins a chance to try the combined system updates. Once 30 days have passed without a new update, the system prompts the user to install all the pending updates. Later, when a new system update becomes available, the 30-day period begins again.

How to set a policy

You can set update policies in Android 8.0 (API level 26) or higher. To specify when the device should install system updates, create an instance of SystemUpdatePolicy using one of the three types outlined above. To set a policy, your device owner calls the DevicePolicyManager method setSystemUpdatePolicy(). The following code sample shows how you might do this. To see a windowed-policy example, look at the SystemUpdatePolicy documentation.

Kotlin

// Create the system update policy to postpone installation for 30 days.
val policy = SystemUpdatePolicy.createPostponeInstallPolicy()

// Get a DevicePolicyManager instance to set the policy on the device.
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager
val adminName = getComponentName(context)

// Set the policy.
dpm.setSystemUpdatePolicy(adminName, policy)

Java

// Create the system update policy to postpone installation for 30 days.
SystemUpdatePolicy policy = SystemUpdatePolicy.createPostponeInstallPolicy();

// Get a DevicePolicyManager instance to set the policy on the device.
DevicePolicyManager dpm = (DevicePolicyManager) context
    .getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName adminName = getComponentName(context);

// Set the policy.
dpm.setSystemUpdatePolicy(adminName, policy);

Policy instances can’t be changed once you create them. To change when a device installs updates, you can create and set a new policy. To remove a policy from a device, call setSystemUpdatePolicy() passing null as the policy argument. After your DPC removes a policy, the device user sees notifications for any available system updates.

Apps can call getSystemUpdatePolicy() to get the current policy for the device. If this method returns null, this means a policy isn’t currently set.

Freeze periods

To freeze the OS version over critical periods, such as holidays or other busy times, device owners can suspend system updates for up to 90 days. When a device is within a freeze period it behaves as follows:

  • The device does not receive any notifications about pending system updates.
  • System updates to the OS are not installed.
  • Device users cannot manually check for system updates in Settings.

The system enforces a mandatory 60-day buffer period following any defined freeze periods to prevent freezing the device indefinitely. Remember, freezing system updates can prevent devices from receiving critical updates.

Figure 1. Two freeze periods set for a device
Calendar showing two freeze periods in a year with 60-day buffers.

You set freeze periods on an update policy. You can't set freeze periods without setting a policy. When the device is outside any freeze periods you set, the normal policy behavior (automatic, windowed, or postponed) applies.

How to set a freeze period

You can set freeze periods in Android 9 (API level 28) or higher. A device owner sets a freeze period on a system update policy before setting the policy for the device. The steps are:

  1. Create a new (or get the current) system update policy.
  2. Set the freeze periods on the policy by calling setFreezePeriods().
  3. Set the policy and freeze periods for the device by calling setSystemUpdatePolicy().

Because the freeze period repeats annually, the start and end dates of the period are represented by month and day values. The start day must begin at least 60 days after the end of any previous freeze period. The following example shows how you might set two freeze periods for an existing system update policy:

Kotlin

// Get the existing policy from the DevicePolicyController instance.
val policy = dpm.systemUpdatePolicy ?: return

try {
    // Set the two annual freeze periods on the policy for our retail
    // point-of-sale devices.
    val summerSale = FreezePeriod(
            MonthDay.of(6, 1),
            MonthDay.of(7, 31)) // Jun 1 - Jul 31 inclusive
    val winterSale = FreezePeriod(
            MonthDay.of(11, 20),
            MonthDay.of(1, 12)) // Nov 20 - Jan 12 inclusive
    policy.freezePeriods = Arrays.asList(summerSale, winterSale)

    // Set the policy again to activate the freeze periods.
    dpm.setSystemUpdatePolicy(adminName, policy)

} catch (e: SystemUpdatePolicy.ValidationFailedException) {
    // There must be previous periods recorded on the device because
    // summerSale and winterSale don’t overlap and are separated by more
    // than 60 days. Report the overlap ...
}

Java

// Get the existing policy from the DevicePolicyController instance.
SystemUpdatePolicy policy = dpm.getSystemUpdatePolicy();

try {
  // Set the two annual freeze periods on the policy for our
  // retail point-of-sale devices.
  FreezePeriod summerSale = new FreezePeriod(
      MonthDay.of(6, 1),
      MonthDay.of(7, 31)); // Jun 1 - Jul 31 inclusive
  FreezePeriod winterSale = new FreezePeriod(
      MonthDay.of(11, 20),
      MonthDay.of(1, 12)); // Nov 20 - Jan 12 inclusive
  policy.setFreezePeriods(Arrays.asList(summerSale, winterSale));

  // Don’t forget to set the policy again to activate the freeze periods.
  dpm.setSystemUpdatePolicy(adminName, policy);

} catch (SystemUpdatePolicy.ValidationFailedException e) {
  // There must be previous periods recorded on the device because summerSale
  // and winterSale don’t overlap and are separated by more than 60 days.
  // Report the overlap ...
}

Both the start day and the end day are inclusive. If the start day is greater than the end day (such as winterSale in the previous example), the freeze period extends into the following year.

When setting freeze periods on a system update policy, Android tests for these requirements:

  • No freeze period is longer than 90 days.
  • The interval between freeze periods is at least 60 days.
  • Freeze periods do not overlap.
  • There are no duplicate freeze periods.

When setting the system update policy for a device, Android repeats these tests and includes any current or past freeze periods for the device.

Android throws a SystemUpdatePolicy.ValidationFailedException when any of these tests fails.

To get a list of freeze periods previously set on a system update policy object, all installed apps can call SystemUpdatePolicy.getFreezePeriods(). The following example calls this method to log a device’s freeze periods:

Kotlin

// Log any freeze periods that might be set on a system update policy.
dpm.systemUpdatePolicy?.freezePeriods?.forEach {
    Log.i(TAG, "Freeze period: $it")
}

Java

// Log any freeze periods that might be set on a system update policy.
SystemUpdatePolicy currentPolicy = dpm.getSystemUpdatePolicy();
if (currentPolicy != null) { // A policy might not be set.
  for (FreezePeriod freezePeriod : currentPolicy.getFreezePeriods()) {
    Log.i(TAG, "Freeze period: " + freezePeriod.toString());
  }
}

Leap years

Android uses the ISO 8601 calendar (also called the Gregorian calendar) to calculate freeze periods and it ignores leap years. This means that February 29th is not recognized as a valid date and is treated as if it were February 28th. Therefore February 29th isn’t counted when computing the duration of a freeze period.

Development and testing

While you’re developing and testing your DPC’s system update feature, you might need to create many freeze periods. Because Android checks for a 60-day interval between past freeze periods, you might not be able to set a new freeze period without first clearing the record of past periods. To clear the device’s freeze period record, run the following command in the Android Debug Bridge (adb) shell:

adb shell dpm clear-freeze-period-record

You can confirm that a device is in a freeze period by checking that the user interface for system updates is disabled.

Additional resources

To learn more about system updates, read the Android Open Source Project’s OTA Updates documentation.