/* * 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 org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; 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 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.

*

Example

* *

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);
 * 
* *

Developer guide

* To learn more about managing system updates, read * Control remote * software updates. * * @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 Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public ValidationFailedException createFromParcel(Parcel source) { return new ValidationFailedException(source.readInt(), source.readString()); } @Override public ValidationFailedException[] newArray(int size) { return new ValidationFailedException[size]; } }; } @SystemUpdatePolicyType private int mPolicyType; private int mMaintenanceWindowStart; private int mMaintenanceWindowEnd; private final ArrayList mFreezePeriods; private SystemUpdatePolicy() { mPolicyType = TYPE_UNKNOWN; mFreezePeriods = new ArrayList<>(); } /** * Create a policy object and set it to install update automatically as soon as one is * available. * * @see #TYPE_INSTALL_AUTOMATIC */ public static SystemUpdatePolicy createAutomaticInstallPolicy() { SystemUpdatePolicy policy = new SystemUpdatePolicy(); policy.mPolicyType = TYPE_INSTALL_AUTOMATIC; return policy; } /** * Create a policy object and set it to: new system update will only be installed automatically * when the system clock is inside a daily maintenance window. If the start and end times are * the same, the window is considered to include the whole 24 hours. That is, updates can * install at any time. If start time is later than end time, the window is considered spanning * midnight (i.e. the end time denotes a time on the next day). The maintenance window will last * for 30 days for any given update, after which the window will no longer be effective and * the pending update will be made available for manual installation as if no system update * policy were set on the device. See {@link #TYPE_INSTALL_WINDOWED} for the details of this * policy's behavior. * * @param startTime the start of the maintenance window, measured as the number of minutes from * midnight in the device's local time. Must be in the range of [0, 1440). * @param endTime the end of the maintenance window, measured as the number of minutes from * midnight in the device's local time. Must be in the range of [0, 1440). * @throws IllegalArgumentException If the {@code startTime} or {@code endTime} isn't in the * accepted range. * @return The configured policy. * @see #TYPE_INSTALL_WINDOWED */ public static SystemUpdatePolicy createWindowedInstallPolicy(int startTime, int endTime) { if (startTime < 0 || startTime >= WINDOW_BOUNDARY || endTime < 0 || endTime >= WINDOW_BOUNDARY) { throw new IllegalArgumentException("startTime and endTime must be inside [0, 1440)"); } SystemUpdatePolicy policy = new SystemUpdatePolicy(); policy.mPolicyType = TYPE_INSTALL_WINDOWED; policy.mMaintenanceWindowStart = startTime; policy.mMaintenanceWindowEnd = endTime; return policy; } /** * Create a policy object and set it to block installation for a maximum period of 30 days. * To learn more about this policy's behavior, see {@link #TYPE_POSTPONE}. * *

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 freezePeriods) { FreezePeriod.validatePeriods(freezePeriods); mFreezePeriods.clear(); mFreezePeriods.addAll(freezePeriods); return this; } /** * Returns the list of freeze periods previously set on this system update policy object. * * @return the list of freeze periods, or an empty list if none was set. */ public List getFreezePeriods() { return Collections.unmodifiableList(mFreezePeriods); } /** * Returns the real calendar dates of the current freeze period, or null if the device * is not in a freeze period at the moment. * @hide */ public Pair getCurrentFreezePeriod(LocalDate now) { for (FreezePeriod interval : mFreezePeriods) { if (interval.contains(now)) { return interval.toCurrentOrFutureRealDates(now); } } return null; } /** * Returns time (in milliseconds) until the start of the next freeze period, assuming now * is not within a freeze period. */ private long timeUntilNextFreezePeriod(long now) { List sortedPeriods = FreezePeriod.canonicalizePeriods(mFreezePeriods); LocalDate nowDate = millisToDate(now); LocalDate nextFreezeStart = null; for (FreezePeriod interval : sortedPeriods) { if (interval.after(nowDate)) { nextFreezeStart = interval.toCurrentOrFutureRealDates(nowDate).first; break; } else if (interval.contains(nowDate)) { throw new IllegalArgumentException("Given date is inside a freeze period"); } } if (nextFreezeStart == null) { // If no interval is after now, then it must be the one that starts at the beginning // of next year nextFreezeStart = sortedPeriods.get(0).toCurrentOrFutureRealDates(nowDate).first; } return dateToMillis(nextFreezeStart) - now; } /** @hide */ public void validateFreezePeriods() { FreezePeriod.validatePeriods(mFreezePeriods); } /** @hide */ public void validateAgainstPreviousFreezePeriod(LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) { FreezePeriod.validateAgainstPreviousFreezePeriod(mFreezePeriods, prevPeriodStart, prevPeriodEnd, now); } /** * An installation option represents how system update clients should act on incoming system * updates and how long this action is valid for, given the current system update policy. Its * action could be one of the following *
    *
  • {@link #TYPE_INSTALL_AUTOMATIC} system updates should be installed immedately and * without user intervention as soon as they become available. *
  • {@link #TYPE_POSTPONE} system updates should be postponed for a maximum of 30 days *
  • {@link #TYPE_PAUSE} system updates should be postponed indefinitely until further notice *
* * 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 current = getCurrentFreezePeriod(whenDate); if (current != null) { return new InstallationOption(TYPE_PAUSE, dateToMillis(roundUpLeapDay(current.second).plusDays(1)) - when); } // We are not within a freeze period, query the underlying policy. // But also consider the start of the next freeze period, which might // reduce the effective time of the current installation option InstallationOption option = getInstallationOptionRegardlessFreezeAt(when); if (mFreezePeriods.size() > 0) { option.limitEffectiveTime(timeUntilNextFreezePeriod(when)); } return option; } private InstallationOption getInstallationOptionRegardlessFreezeAt(long when) { if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) { return new InstallationOption(mPolicyType, Long.MAX_VALUE); } else if (mPolicyType == TYPE_INSTALL_WINDOWED) { Calendar query = Calendar.getInstance(); query.setTimeInMillis(when); // Calculate the number of milliseconds since midnight of the time specified by when long whenMillis = TimeUnit.HOURS.toMillis(query.get(Calendar.HOUR_OF_DAY)) + TimeUnit.MINUTES.toMillis(query.get(Calendar.MINUTE)) + TimeUnit.SECONDS.toMillis(query.get(Calendar.SECOND)) + query.get(Calendar.MILLISECOND); long windowStartMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowStart); long windowEndMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowEnd); final long dayInMillis = TimeUnit.DAYS.toMillis(1); if ((windowStartMillis <= whenMillis && whenMillis <= windowEndMillis) || ((windowStartMillis > windowEndMillis) && (windowStartMillis <= whenMillis || whenMillis <= windowEndMillis))) { return new InstallationOption(TYPE_INSTALL_AUTOMATIC, (windowEndMillis - whenMillis + dayInMillis) % dayInMillis); } else { return new InstallationOption(TYPE_PAUSE, (windowStartMillis - whenMillis + dayInMillis) % dayInMillis); } } else { throw new RuntimeException("Unknown policy type"); } } private static LocalDate roundUpLeapDay(LocalDate date) { if (date.isLeapYear() && date.getMonthValue() == 2 && date.getDayOfMonth() == 28) { return date.plusDays(1); } else { return date; } } /** Convert a timestamp since epoch to a LocalDate using default timezone, truncating * the hour/min/seconds part. */ private static LocalDate millisToDate(long when) { return Instant.ofEpochMilli(when).atZone(ZoneId.systemDefault()).toLocalDate(); } /** * Returns the timestamp since epoch of a LocalDate, assuming the time is 00:00:00. */ private static long dateToMillis(LocalDate when) { return LocalDateTime.of(when, LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant() .toEpochMilli(); } @Override public String toString() { return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d, " + "freezes: [%s])", mPolicyType, mMaintenanceWindowStart, mMaintenanceWindowEnd, mFreezePeriods.stream().map(n -> n.toString()).collect(Collectors.joining(","))); } @SystemApi @Override public int describeContents() { return 0; } @SystemApi @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mPolicyType); dest.writeInt(mMaintenanceWindowStart); dest.writeInt(mMaintenanceWindowEnd); int freezeCount = mFreezePeriods.size(); dest.writeInt(freezeCount); for (int i = 0; i < freezeCount; i++) { FreezePeriod interval = mFreezePeriods.get(i); dest.writeInt(interval.getStart().getMonthValue()); dest.writeInt(interval.getStart().getDayOfMonth()); dest.writeInt(interval.getEnd().getMonthValue()); dest.writeInt(interval.getEnd().getDayOfMonth()); } } @SystemApi public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public SystemUpdatePolicy createFromParcel(Parcel source) { SystemUpdatePolicy policy = new SystemUpdatePolicy(); policy.mPolicyType = source.readInt(); policy.mMaintenanceWindowStart = source.readInt(); policy.mMaintenanceWindowEnd = source.readInt(); int freezeCount = source.readInt(); policy.mFreezePeriods.ensureCapacity(freezeCount); for (int i = 0; i < freezeCount; i++) { MonthDay start = MonthDay.of(source.readInt(), source.readInt()); MonthDay end = MonthDay.of(source.readInt(), source.readInt()); policy.mFreezePeriods.add(new FreezePeriod(start, end)); } return policy; } @Override public SystemUpdatePolicy[] newArray(int size) { return new SystemUpdatePolicy[size]; } }; /** * Restore a previously saved SystemUpdatePolicy from XML. No need to validate * the reconstructed policy since the XML is supposed to be created by the * system server from a validated policy object previously. * @hide */ public static SystemUpdatePolicy restoreFromXml(XmlPullParser parser) { try { SystemUpdatePolicy policy = new SystemUpdatePolicy(); String value = parser.getAttributeValue(null, KEY_POLICY_TYPE); if (value != null) { policy.mPolicyType = Integer.parseInt(value); value = parser.getAttributeValue(null, KEY_INSTALL_WINDOW_START); if (value != null) { policy.mMaintenanceWindowStart = Integer.parseInt(value); } value = parser.getAttributeValue(null, KEY_INSTALL_WINDOW_END); if (value != null) { policy.mMaintenanceWindowEnd = Integer.parseInt(value); } int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != END_DOCUMENT && (type != END_TAG || parser.getDepth() > outerDepth)) { if (type == END_TAG || type == TEXT) { continue; } if (!parser.getName().equals(KEY_FREEZE_TAG)) { continue; } policy.mFreezePeriods.add(new FreezePeriod( MonthDay.parse(parser.getAttributeValue(null, KEY_FREEZE_START)), MonthDay.parse(parser.getAttributeValue(null, KEY_FREEZE_END)))); } return policy; } } catch (NumberFormatException | XmlPullParserException | IOException e) { // Fail through Log.w(TAG, "Load xml failed", e); } return null; } /** * @hide */ public void saveToXml(XmlSerializer out) throws IOException { out.attribute(null, KEY_POLICY_TYPE, Integer.toString(mPolicyType)); out.attribute(null, KEY_INSTALL_WINDOW_START, Integer.toString(mMaintenanceWindowStart)); out.attribute(null, KEY_INSTALL_WINDOW_END, Integer.toString(mMaintenanceWindowEnd)); for (int i = 0; i < mFreezePeriods.size(); i++) { FreezePeriod interval = mFreezePeriods.get(i); out.startTag(null, KEY_FREEZE_TAG); out.attribute(null, KEY_FREEZE_START, interval.getStart().toString()); out.attribute(null, KEY_FREEZE_END, interval.getEnd().toString()); out.endTag(null, KEY_FREEZE_TAG); } } }