/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.admin; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.TEXT; import android.annotation.IntDef; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import android.util.Pair; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.MonthDay; import java.time.ZoneId; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * Determines when over-the-air system updates are installed on a device. Only a device policy * controller (DPC) running in device owner mode or in profile owner mode for an organization-owned * device can set an update policy for the device by calling the {@code DevicePolicyManager} method * {@link DevicePolicyManager#setSystemUpdatePolicy setSystemUpdatePolicy()}. An update * policy affects the pending system update (if there is one) and any future updates for the device. * *
If a policy is set on a device, the system doesn't notify the user about updates.
*The example below shows how a DPC might set a maintenance window for system updates:
*
* private final MAINTENANCE_WINDOW_START = 1380; // 11pm
* private final MAINTENANCE_WINDOW_END = 120; // 2am
*
* // ...
*
* // Create the system update policy
* SystemUpdatePolicy policy = SystemUpdatePolicy.createWindowedInstallPolicy(
* MAINTENANCE_WINDOW_START, MAINTENANCE_WINDOW_END);
*
* // Get a DevicePolicyManager instance to set the policy on the device
* DevicePolicyManager dpm =
* (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
* ComponentName adminComponent = getComponentName(context);
* dpm.setSystemUpdatePolicy(adminComponent, policy);
*
*
* Note: * Google Play system updates (also called Mainline updates) are automatically downloaded * but require a device reboot to be installed. Refer to the mainline section in * Manage system * updates for further details.
* * @see DevicePolicyManager#setSystemUpdatePolicy * @see DevicePolicyManager#getSystemUpdatePolicy */ public final class SystemUpdatePolicy implements Parcelable { private static final String TAG = "SystemUpdatePolicy"; /** @hide */ @IntDef(prefix = { "TYPE_" }, value = { TYPE_INSTALL_AUTOMATIC, TYPE_INSTALL_WINDOWED, TYPE_POSTPONE }) @Retention(RetentionPolicy.SOURCE) @interface SystemUpdatePolicyType {} /** * Unknown policy type, used only internally. */ private static final int TYPE_UNKNOWN = -1; /** * Installs system updates (without user interaction) as soon as they become available. Setting * this policy type immediately installs any pending updates that might be postponed or waiting * for a maintenance window. */ public static final int TYPE_INSTALL_AUTOMATIC = 1; /** * Installs system updates (without user interaction) during a daily maintenance window. Set the * start and end of the daily maintenance window, as minutes of the day, when creating a new * {@code TYPE_INSTALL_WINDOWED} policy. See * {@link #createWindowedInstallPolicy createWindowedInstallPolicy()}. * *No connectivity, not enough disk space, or a low battery are typical reasons Android might * not install a system update in the daily maintenance window. After 30 days trying to install * an update in the maintenance window (regardless of policy changes in this period), the system * prompts the device user to install the update. */ public static final int TYPE_INSTALL_WINDOWED = 2; /** * 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. * *
The system limits each update to one 30-day postponement. The period begins when the * system first postpones the update and setting new {@code TYPE_POSTPONE} policies won’t extend * the period. If, after 30 days the update isn't installed (through policy changes), the system * prompts the user to install the update. * *
Note: Device manufacturers or carriers might choose to exempt important
* security updates from a postponement policy. Exempted updates notify the device user when
* they become available.
*/
public static final int TYPE_POSTPONE = 3;
/**
* Incoming system updates (including security updates) should be blocked. This flag is not
* exposed to third-party apps (and any attempt to set it will raise exceptions). This is used
* to represent the current installation option type to the privileged system update clients,
* for example to indicate OTA freeze is currently in place or when system is outside a daily
* maintenance window.
*
* @see InstallationOption
* @hide
*/
@SystemApi
public static final int TYPE_PAUSE = 4;
private static final String KEY_POLICY_TYPE = "policy_type";
private static final String KEY_INSTALL_WINDOW_START = "install_window_start";
private static final String KEY_INSTALL_WINDOW_END = "install_window_end";
private static final String KEY_FREEZE_TAG = "freeze";
private static final String KEY_FREEZE_START = "start";
private static final String KEY_FREEZE_END = "end";
/**
* The upper boundary of the daily maintenance window: 24 * 60 minutes.
*/
private static final int WINDOW_BOUNDARY = 24 * 60;
/**
* The maximum length of a single freeze period: 90 days.
*/
static final int FREEZE_PERIOD_MAX_LENGTH = 90;
/**
* The minimum allowed time between two adjacent freeze period (from the end of the first
* freeze period to the start of the second freeze period, both exclusive): 60 days.
*/
static final int FREEZE_PERIOD_MIN_SEPARATION = 60;
/**
* An exception class that represents various validation errors thrown from
* {@link SystemUpdatePolicy#setFreezePeriods} and
* {@link DevicePolicyManager#setSystemUpdatePolicy}
*/
public static final class ValidationFailedException extends IllegalArgumentException
implements Parcelable {
/** @hide */
@IntDef(prefix = { "ERROR_" }, value = {
ERROR_NONE,
ERROR_DUPLICATE_OR_OVERLAP,
ERROR_NEW_FREEZE_PERIOD_TOO_LONG,
ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE,
ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG,
ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE,
ERROR_UNKNOWN,
})
@Retention(RetentionPolicy.SOURCE)
@interface ValidationFailureType {}
/** @hide */
public static final int ERROR_NONE = 0;
/**
* Validation failed with unknown error.
*/
public static final int ERROR_UNKNOWN = 1;
/**
* The freeze periods contains duplicates, periods that overlap with each
* other or periods whose start and end joins.
*/
public static final int ERROR_DUPLICATE_OR_OVERLAP = 2;
/**
* There exists at least one freeze period whose length exceeds 90 days.
*/
public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 3;
/**
* There exists some freeze period which starts within 60 days of the preceding period's
* end time.
*/
public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 4;
/**
* The device has been in a freeze period and when combining with the new freeze period
* to be set, it will result in the total freeze period being longer than 90 days.
*/
public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 5;
/**
* The device has been in a freeze period and some new freeze period to be set is less
* than 60 days from the end of the last freeze period the device went through.
*/
public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 6;
@ValidationFailureType
private final int mErrorCode;
private ValidationFailedException(int errorCode, String message) {
super(message);
mErrorCode = errorCode;
}
/**
* Returns the type of validation error associated with this exception.
*/
public @ValidationFailureType int getErrorCode() {
return mErrorCode;
}
/** @hide */
public static ValidationFailedException duplicateOrOverlapPeriods() {
return new ValidationFailedException(ERROR_DUPLICATE_OR_OVERLAP,
"Found duplicate or overlapping periods");
}
/** @hide */
public static ValidationFailedException freezePeriodTooLong(String message) {
return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_LONG, message);
}
/** @hide */
public static ValidationFailedException freezePeriodTooClose(String message) {
return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, message);
}
/** @hide */
public static ValidationFailedException combinedPeriodTooLong(String message) {
return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, message);
}
/** @hide */
public static ValidationFailedException combinedPeriodTooClose(String message) {
return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, message);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mErrorCode);
dest.writeString(getMessage());
}
public static final @android.annotation.NonNull Parcelable.Creator Note: security updates (e.g. monthly security patches) will not be affected
* by this policy.
*
* @see #TYPE_POSTPONE
*/
public static SystemUpdatePolicy createPostponeInstallPolicy() {
SystemUpdatePolicy policy = new SystemUpdatePolicy();
policy.mPolicyType = TYPE_POSTPONE;
return policy;
}
/**
* Returns the type of system update policy, or -1 if no policy has been set.
*
@return The policy type or -1 if the type isn't set.
*/
@SystemUpdatePolicyType
public int getPolicyType() {
return mPolicyType;
}
/**
* Get the start of the maintenance window.
*
* @return the start of the maintenance window measured as the number of minutes from midnight,
* or -1 if the policy does not have a maintenance window.
*/
public int getInstallWindowStart() {
if (mPolicyType == TYPE_INSTALL_WINDOWED) {
return mMaintenanceWindowStart;
} else {
return -1;
}
}
/**
* Get the end of the maintenance window.
*
* @return the end of the maintenance window measured as the number of minutes from midnight,
* or -1 if the policy does not have a maintenance window.
*/
public int getInstallWindowEnd() {
if (mPolicyType == TYPE_INSTALL_WINDOWED) {
return mMaintenanceWindowEnd;
} else {
return -1;
}
}
/**
* Return if this object represents a valid policy with:
* 1. Correct type
* 2. Valid maintenance window if applicable
* 3. Valid freeze periods
* @hide
*/
public boolean isValid() {
try {
validateType();
validateFreezePeriods();
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
/**
* Validate the type and maintenance window (if applicable) of this policy object,
* throws {@link IllegalArgumentException} if it's invalid.
* @hide
*/
public void validateType() {
if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) {
return;
} else if (mPolicyType == TYPE_INSTALL_WINDOWED) {
if (!(mMaintenanceWindowStart >= 0 && mMaintenanceWindowStart < WINDOW_BOUNDARY
&& mMaintenanceWindowEnd >= 0 && mMaintenanceWindowEnd < WINDOW_BOUNDARY)) {
throw new IllegalArgumentException("Invalid maintenance window");
}
} else {
throw new IllegalArgumentException("Invalid system update policy type.");
}
}
/**
* Configure a list of freeze periods on top of the current policy. When the device's clock is
* within any of the freeze periods, all incoming system updates including security patches will
* be blocked and cannot be installed. When the device is outside the freeze periods, the normal
* policy behavior will apply.
*
* Each individual freeze period is allowed to be at most 90 days long, and adjacent freeze
* periods need to be at least 60 days apart. Also, the list of freeze periods should not
* contain duplicates or overlap with each other. If any of these conditions is not met, a
* {@link ValidationFailedException} will be thrown.
*
* Handling of leap year: we ignore leap years in freeze period calculations, in particular,
*
*
*
* @param freezePeriods the list of freeze periods
* @throws ValidationFailedException if the supplied freeze periods do not meet the
* requirement set above
* @return this instance
*/
public SystemUpdatePolicy setFreezePeriods(List
*
*
* The effective time measures how long this installation option is valid for from the queried
* time, in milliseconds.
*
* This is an internal API for system update clients.
* @hide
*/
@SystemApi
public static class InstallationOption {
/** @hide */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_INSTALL_AUTOMATIC,
TYPE_PAUSE,
TYPE_POSTPONE
})
@Retention(RetentionPolicy.SOURCE)
@interface InstallationOptionType {}
@InstallationOptionType
private final int mType;
private long mEffectiveTime;
InstallationOption(@InstallationOptionType int type, long effectiveTime) {
this.mType = type;
this.mEffectiveTime = effectiveTime;
}
/**
* Returns the type of the current installation option, could be one of
* {@link #TYPE_INSTALL_AUTOMATIC}, {@link #TYPE_POSTPONE} and {@link #TYPE_PAUSE}.
* @return type of installation option.
*/
public @InstallationOptionType int getType() {
return mType;
}
/**
* Returns how long the current installation option in effective for, starting from the time
* of query.
* @return the effective time in milliseconds.
*/
public long getEffectiveTime() {
return mEffectiveTime;
}
/** @hide */
protected void limitEffectiveTime(long otherTime) {
mEffectiveTime = Long.min(mEffectiveTime, otherTime);
}
}
/**
* Returns the installation option at the specified time, under the current
* {@code SystemUpdatePolicy} object. This is a convenience method for system update clients
* so they can instantiate this policy at any given time and find out what to do with incoming
* system updates, without the need of examining the overall policy structure.
*
* Normally the system update clients will query the current installation option by calling this
* method with the current timestamp, and act on the returned option until its effective time
* lapses. It can then query the latest option using a new timestamp. It should also listen
* for {@code DevicePolicyManager#ACTION_SYSTEM_UPDATE_POLICY_CHANGED} broadcast, in case the
* whole policy is updated.
*
* @param when At what time the intallation option is being queried, specified in number of
milliseonds since the epoch.
* @see InstallationOption
* @hide
*/
@SystemApi
public InstallationOption getInstallationOptionAt(long when) {
LocalDate whenDate = millisToDate(when);
Pair