| /* |
| * 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. |
| * |
| * <p>If a policy is set on a device, the system doesn't notify the user about updates.</p> |
| * <h3>Example</h3> |
| * |
| * <p>The example below shows how a DPC might set a maintenance window for system updates:</p> |
| * <pre><code> |
| * 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); |
| * </code></pre> |
| * |
| * <h3>Developer guide</h3> |
| * To learn more about managing system updates, read |
| * <a href="{@docRoot}/work/dpc/security.html#control_remote_software_updates">Control remote |
| * software updates</a>. |
| * |
| * @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()}. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p><strong>Note</strong>: 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<ValidationFailedException> CREATOR = |
| new Parcelable.Creator<ValidationFailedException>() { |
| @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<FreezePeriod> 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 <i>whole 24 hours</i>. 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}. |
| * |
| * <p><b>Note: </b> security updates (e.g. monthly security patches) will <i>not</i> 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. |
| * <p> |
| * 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. |
| * <p> |
| * Handling of leap year: we ignore leap years in freeze period calculations, in particular, |
| * <ul> |
| * <li>When a freeze period is defined, February 29th is disregarded so even though a freeze |
| * period can be specified to start or end on February 29th, it will be treated as if the period |
| * started or ended on February 28th.</li> |
| * <li>When applying freeze period behavior to the device, a system clock of February 29th is |
| * treated as if it were February 28th</li> |
| * <li>When calculating the number of days of a freeze period or separation between two freeze |
| * periods, February 29th is also ignored and not counted as one day.</li> |
| * </ul> |
| * |
| * @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<FreezePeriod> 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<FreezePeriod> 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<LocalDate, LocalDate> 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<FreezePeriod> 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 |
| * <ul> |
| * <li> {@link #TYPE_INSTALL_AUTOMATIC} system updates should be installed immedately and |
| * without user intervention as soon as they become available. |
| * <li> {@link #TYPE_POSTPONE} system updates should be postponed for a maximum of 30 days |
| * <li> {@link #TYPE_PAUSE} system updates should be postponed indefinitely until further notice |
| * </ul> |
| * |
| * 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<LocalDate, LocalDate> 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<SystemUpdatePolicy> CREATOR = |
| new Parcelable.Creator<SystemUpdatePolicy>() { |
| |
| @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); |
| } |
| } |
| } |
| |