Home | History | Annotate | Download | only in admin
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.app.admin;
     18 
     19 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
     20 import static org.xmlpull.v1.XmlPullParser.END_TAG;
     21 import static org.xmlpull.v1.XmlPullParser.TEXT;
     22 
     23 import android.annotation.IntDef;
     24 import android.annotation.SystemApi;
     25 import android.os.Parcel;
     26 import android.os.Parcelable;
     27 import android.util.Log;
     28 import android.util.Pair;
     29 
     30 import org.xmlpull.v1.XmlPullParser;
     31 import org.xmlpull.v1.XmlPullParserException;
     32 import org.xmlpull.v1.XmlSerializer;
     33 
     34 import java.io.IOException;
     35 import java.lang.annotation.Retention;
     36 import java.lang.annotation.RetentionPolicy;
     37 import java.time.Instant;
     38 import java.time.LocalDate;
     39 import java.time.LocalDateTime;
     40 import java.time.LocalTime;
     41 import java.time.MonthDay;
     42 import java.time.ZoneId;
     43 import java.util.ArrayList;
     44 import java.util.Calendar;
     45 import java.util.Collections;
     46 import java.util.List;
     47 import java.util.concurrent.TimeUnit;
     48 import java.util.stream.Collectors;
     49 
     50 /**
     51  * Determines when over-the-air system updates are installed on a device. Only a device policy
     52  * controller (DPC) running in device owner mode can set an update policy for the deviceby calling
     53  * the {@code DevicePolicyManager} method
     54  * {@link DevicePolicyManager#setSystemUpdatePolicy setSystemUpdatePolicy()}. An update
     55  * policy affects the pending system update (if there is one) and any future updates for the device.
     56  *
     57  * <p>If a policy is set on a device, the system doesn't notify the user about updates.</p>
     58  * <h3>Example</h3>
     59  *
     60  * <p>The example below shows how a DPC might set a maintenance window for system updates:</p>
     61  * <pre><code>
     62  * private final MAINTENANCE_WINDOW_START = 1380; // 11pm
     63  * private final MAINTENANCE_WINDOW_END = 120; // 2am
     64  *
     65  * // ...
     66  *
     67  * // Create the system update policy
     68  * SystemUpdatePolicy policy = SystemUpdatePolicy.createWindowedInstallPolicy(
     69  *     MAINTENANCE_WINDOW_START, MAINTENANCE_WINDOW_END);
     70  *
     71  * // Get a DevicePolicyManager instance to set the policy on the device
     72  * DevicePolicyManager dpm =
     73  *     (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
     74  * ComponentName adminComponent = getComponentName(context);
     75  * dpm.setSystemUpdatePolicy(adminComponent, policy);
     76  * </code></pre>
     77  *
     78  * <h3>Developer guide</h3>
     79  * To learn more about managing system updates, read
     80  * <a href="{@docRoot}/work/dpc/security.html#control_remote_software_updates">Control remote
     81  * software updates</a>.
     82  *
     83  * @see DevicePolicyManager#setSystemUpdatePolicy
     84  * @see DevicePolicyManager#getSystemUpdatePolicy
     85  */
     86 public final class SystemUpdatePolicy implements Parcelable {
     87     private static final String TAG = "SystemUpdatePolicy";
     88 
     89     /** @hide */
     90     @IntDef(prefix = { "TYPE_" }, value = {
     91             TYPE_INSTALL_AUTOMATIC,
     92             TYPE_INSTALL_WINDOWED,
     93             TYPE_POSTPONE
     94     })
     95     @Retention(RetentionPolicy.SOURCE)
     96     @interface SystemUpdatePolicyType {}
     97 
     98     /**
     99      * Unknown policy type, used only internally.
    100      */
    101     private static final int TYPE_UNKNOWN = -1;
    102 
    103     /**
    104      * Installs system updates (without user interaction) as soon as they become available. Setting
    105      * this policy type immediately installs any pending updates that might be postponed or waiting
    106      * for a maintenance window.
    107      */
    108     public static final int TYPE_INSTALL_AUTOMATIC = 1;
    109 
    110     /**
    111      * Installs system updates (without user interaction) during a daily maintenance window. Set the
    112      * start and end of the daily maintenance window, as minutes of the day, when creating a new
    113      * {@code TYPE_INSTALL_WINDOWED} policy. See
    114      * {@link #createWindowedInstallPolicy createWindowedInstallPolicy()}.
    115      *
    116      * <p>No connectivity, not enough disk space, or a low battery are typical reasons Android might
    117      * not install a system update in the daily maintenance window. After 30 days trying to install
    118      * an update in the maintenance window (regardless of policy changes in this period), the system
    119      * prompts the device user to install the update.
    120      */
    121     public static final int TYPE_INSTALL_WINDOWED = 2;
    122 
    123     /**
    124      * Postpones the installation of system updates for 30 days. After the 30-day period has ended,
    125      * the system prompts the device user to install the update.
    126      *
    127      * <p>The system limits each update to one 30-day postponement. The period begins when the
    128      * system first postpones the update and setting new {@code TYPE_POSTPONE} policies wont extend
    129      * the period. If, after 30 days the update isnt installed (through policy changes), the system
    130      * prompts the user to install the update.
    131      *
    132      * <p><strong>Note</strong>: Device manufacturers or carriers might choose to exempt important
    133      * security updates from a postponement policy. Exempted updates notify the device user when
    134      * they become available.
    135      */
    136     public static final int TYPE_POSTPONE = 3;
    137 
    138     /**
    139      * Incoming system updates (including security updates) should be blocked. This flag is not
    140      * exposed to third-party apps (and any attempt to set it will raise exceptions). This is used
    141      * to represent the current installation option type to the privileged system update clients,
    142      * for example to indicate OTA freeze is currently in place or when system is outside a daily
    143      * maintenance window.
    144      *
    145      * @see InstallationOption
    146      * @hide
    147      */
    148     @SystemApi
    149     public static final int TYPE_PAUSE = 4;
    150 
    151     private static final String KEY_POLICY_TYPE = "policy_type";
    152     private static final String KEY_INSTALL_WINDOW_START = "install_window_start";
    153     private static final String KEY_INSTALL_WINDOW_END = "install_window_end";
    154     private static final String KEY_FREEZE_TAG = "freeze";
    155     private static final String KEY_FREEZE_START = "start";
    156     private static final String KEY_FREEZE_END = "end";
    157 
    158     /**
    159      * The upper boundary of the daily maintenance window: 24 * 60 minutes.
    160      */
    161     private static final int WINDOW_BOUNDARY = 24 * 60;
    162 
    163     /**
    164      * The maximum length of a single freeze period: 90  days.
    165      */
    166     static final int FREEZE_PERIOD_MAX_LENGTH = 90;
    167 
    168     /**
    169      * The minimum allowed time between two adjacent freeze period (from the end of the first
    170      * freeze period to the start of the second freeze period, both exclusive): 60 days.
    171      */
    172     static final int FREEZE_PERIOD_MIN_SEPARATION = 60;
    173 
    174 
    175     /**
    176      * An exception class that represents various validation errors thrown from
    177      * {@link SystemUpdatePolicy#setFreezePeriods} and
    178      * {@link DevicePolicyManager#setSystemUpdatePolicy}
    179      */
    180     public static final class ValidationFailedException extends IllegalArgumentException
    181             implements Parcelable {
    182 
    183         /** @hide */
    184         @IntDef(prefix = { "ERROR_" }, value = {
    185                 ERROR_NONE,
    186                 ERROR_DUPLICATE_OR_OVERLAP,
    187                 ERROR_NEW_FREEZE_PERIOD_TOO_LONG,
    188                 ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE,
    189                 ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG,
    190                 ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE,
    191                 ERROR_UNKNOWN,
    192         })
    193         @Retention(RetentionPolicy.SOURCE)
    194         @interface ValidationFailureType {}
    195 
    196         /** @hide */
    197         public static final int ERROR_NONE = 0;
    198 
    199         /**
    200          * Validation failed with unknown error.
    201          */
    202         public static final int ERROR_UNKNOWN = 1;
    203 
    204         /**
    205          * The freeze periods contains duplicates, periods that overlap with each
    206          * other or periods whose start and end joins.
    207          */
    208         public static final int ERROR_DUPLICATE_OR_OVERLAP = 2;
    209 
    210         /**
    211          * There exists at least one freeze period whose length exceeds 90 days.
    212          */
    213         public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 3;
    214 
    215         /**
    216          * There exists some freeze period which starts within 60 days of the preceding period's
    217          * end time.
    218          */
    219         public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 4;
    220 
    221         /**
    222          * The device has been in a freeze period and when combining with the new freeze period
    223          * to be set, it will result in the total freeze period being longer than 90 days.
    224          */
    225         public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 5;
    226 
    227         /**
    228          * The device has been in a freeze period and some new freeze period to be set is less
    229          * than 60 days from the end of the last freeze period the device went through.
    230          */
    231         public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 6;
    232 
    233         @ValidationFailureType
    234         private final int mErrorCode;
    235 
    236         private ValidationFailedException(int errorCode, String message) {
    237             super(message);
    238             mErrorCode = errorCode;
    239         }
    240 
    241         /**
    242          * Returns the type of validation error associated with this exception.
    243          */
    244         public @ValidationFailureType int getErrorCode() {
    245             return mErrorCode;
    246         }
    247 
    248         /** @hide */
    249         public static ValidationFailedException duplicateOrOverlapPeriods() {
    250             return new ValidationFailedException(ERROR_DUPLICATE_OR_OVERLAP,
    251                     "Found duplicate or overlapping periods");
    252         }
    253 
    254         /** @hide */
    255         public static ValidationFailedException freezePeriodTooLong(String message) {
    256             return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_LONG, message);
    257         }
    258 
    259         /** @hide */
    260         public static ValidationFailedException freezePeriodTooClose(String message) {
    261             return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, message);
    262         }
    263 
    264         /** @hide */
    265         public static ValidationFailedException combinedPeriodTooLong(String message) {
    266             return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, message);
    267         }
    268 
    269         /** @hide */
    270         public static ValidationFailedException combinedPeriodTooClose(String message) {
    271             return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, message);
    272         }
    273 
    274         @Override
    275         public int describeContents() {
    276             return 0;
    277         }
    278 
    279         @Override
    280         public void writeToParcel(Parcel dest, int flags) {
    281             dest.writeInt(mErrorCode);
    282             dest.writeString(getMessage());
    283         }
    284 
    285         public static final Parcelable.Creator<ValidationFailedException> CREATOR =
    286                 new Parcelable.Creator<ValidationFailedException>() {
    287             @Override
    288             public ValidationFailedException createFromParcel(Parcel source) {
    289                 return new ValidationFailedException(source.readInt(), source.readString());
    290             }
    291 
    292             @Override
    293             public ValidationFailedException[] newArray(int size) {
    294                 return new ValidationFailedException[size];
    295             }
    296 
    297         };
    298     }
    299 
    300     @SystemUpdatePolicyType
    301     private int mPolicyType;
    302 
    303     private int mMaintenanceWindowStart;
    304     private int mMaintenanceWindowEnd;
    305 
    306     private final ArrayList<FreezePeriod> mFreezePeriods;
    307 
    308     private SystemUpdatePolicy() {
    309         mPolicyType = TYPE_UNKNOWN;
    310         mFreezePeriods = new ArrayList<>();
    311     }
    312 
    313     /**
    314      * Create a policy object and set it to install update automatically as soon as one is
    315      * available.
    316      *
    317      * @see #TYPE_INSTALL_AUTOMATIC
    318      */
    319     public static SystemUpdatePolicy createAutomaticInstallPolicy() {
    320         SystemUpdatePolicy policy = new SystemUpdatePolicy();
    321         policy.mPolicyType = TYPE_INSTALL_AUTOMATIC;
    322         return policy;
    323     }
    324 
    325     /**
    326      * Create a policy object and set it to: new system update will only be installed automatically
    327      * when the system clock is inside a daily maintenance window. If the start and end times are
    328      * the same, the window is considered to include the <i>whole 24 hours</i>. That is, updates can
    329      * install at any time. If start time is later than end time, the window is considered spanning
    330      * midnight (i.e. the end time denotes a time on the next day). The maintenance window will last
    331      * for 30 days for any given update, after which the window will no longer be effective and
    332      * the pending update will be made available for manual installation as if no system update
    333      * policy were set on the device. See {@link #TYPE_INSTALL_WINDOWED} for the details of this
    334      * policy's behavior.
    335      *
    336      * @param startTime the start of the maintenance window, measured as the number of minutes from
    337      *            midnight in the device's local time. Must be in the range of [0, 1440).
    338      * @param endTime the end of the maintenance window, measured as the number of minutes from
    339      *            midnight in the device's local time. Must be in the range of [0, 1440).
    340      * @throws IllegalArgumentException If the {@code startTime} or {@code endTime} isn't in the
    341      *            accepted range.
    342      * @return The configured policy.
    343      * @see #TYPE_INSTALL_WINDOWED
    344      */
    345     public static SystemUpdatePolicy createWindowedInstallPolicy(int startTime, int endTime) {
    346         if (startTime < 0 || startTime >= WINDOW_BOUNDARY
    347                 || endTime < 0 || endTime >= WINDOW_BOUNDARY) {
    348             throw new IllegalArgumentException("startTime and endTime must be inside [0, 1440)");
    349         }
    350         SystemUpdatePolicy policy = new SystemUpdatePolicy();
    351         policy.mPolicyType = TYPE_INSTALL_WINDOWED;
    352         policy.mMaintenanceWindowStart = startTime;
    353         policy.mMaintenanceWindowEnd = endTime;
    354         return policy;
    355     }
    356 
    357     /**
    358      * Create a policy object and set it to block installation for a maximum period of 30 days.
    359      * To learn more about this policy's behavior, see {@link #TYPE_POSTPONE}.
    360      *
    361      * <p><b>Note: </b> security updates (e.g. monthly security patches) will <i>not</i> be affected
    362      * by this policy.
    363      *
    364      * @see #TYPE_POSTPONE
    365      */
    366     public static SystemUpdatePolicy createPostponeInstallPolicy() {
    367         SystemUpdatePolicy policy = new SystemUpdatePolicy();
    368         policy.mPolicyType = TYPE_POSTPONE;
    369         return policy;
    370     }
    371 
    372     /**
    373      * Returns the type of system update policy, or -1 if no policy has been set.
    374      *
    375      @return The policy type or -1 if the type isn't set.
    376      */
    377     @SystemUpdatePolicyType
    378     public int getPolicyType() {
    379         return mPolicyType;
    380     }
    381 
    382     /**
    383      * Get the start of the maintenance window.
    384      *
    385      * @return the start of the maintenance window measured as the number of minutes from midnight,
    386      * or -1 if the policy does not have a maintenance window.
    387      */
    388     public int getInstallWindowStart() {
    389         if (mPolicyType == TYPE_INSTALL_WINDOWED) {
    390             return mMaintenanceWindowStart;
    391         } else {
    392             return -1;
    393         }
    394     }
    395 
    396     /**
    397      * Get the end of the maintenance window.
    398      *
    399      * @return the end of the maintenance window measured as the number of minutes from midnight,
    400      * or -1 if the policy does not have a maintenance window.
    401      */
    402     public int getInstallWindowEnd() {
    403         if (mPolicyType == TYPE_INSTALL_WINDOWED) {
    404             return mMaintenanceWindowEnd;
    405         } else {
    406             return -1;
    407         }
    408     }
    409 
    410     /**
    411      * Return if this object represents a valid policy with:
    412      * 1. Correct type
    413      * 2. Valid maintenance window if applicable
    414      * 3. Valid freeze periods
    415      * @hide
    416      */
    417     public boolean isValid() {
    418         try {
    419             validateType();
    420             validateFreezePeriods();
    421             return true;
    422         } catch (IllegalArgumentException e) {
    423             return false;
    424         }
    425     }
    426 
    427     /**
    428      * Validate the type and maintenance window (if applicable) of this policy object,
    429      * throws {@link IllegalArgumentException} if it's invalid.
    430      * @hide
    431      */
    432     public void validateType() {
    433         if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) {
    434             return;
    435         } else if (mPolicyType == TYPE_INSTALL_WINDOWED) {
    436             if (!(mMaintenanceWindowStart >= 0 && mMaintenanceWindowStart < WINDOW_BOUNDARY
    437                     && mMaintenanceWindowEnd >= 0 && mMaintenanceWindowEnd < WINDOW_BOUNDARY)) {
    438                 throw new IllegalArgumentException("Invalid maintenance window");
    439             }
    440         } else {
    441             throw new IllegalArgumentException("Invalid system update policy type.");
    442         }
    443     }
    444 
    445     /**
    446      * Configure a list of freeze periods on top of the current policy. When the device's clock is
    447      * within any of the freeze periods, all incoming system updates including security patches will
    448      * be blocked and cannot be installed. When the device is outside the freeze periods, the normal
    449      * policy behavior will apply.
    450      * <p>
    451      * Each individual freeze period is allowed to be at most 90 days long, and adjacent freeze
    452      * periods need to be at least 60 days apart. Also, the list of freeze periods should not
    453      * contain duplicates or overlap with each other. If any of these conditions is not met, a
    454      * {@link ValidationFailedException} will be thrown.
    455      * <p>
    456      * Handling of leap year: we ignore leap years in freeze period calculations, in particular,
    457      * <ul>
    458      * <li>When a freeze period is defined, February 29th is disregarded so even though a freeze
    459      * period can be specified to start or end on February 29th, it will be treated as if the period
    460      * started or ended on February 28th.</li>
    461      * <li>When applying freeze period behavior to the device, a system clock of February 29th is
    462      * treated as if it were February 28th</li>
    463      * <li>When calculating the number of days of a freeze period or separation between two freeze
    464      * periods, February 29th is also ignored and not counted as one day.</li>
    465      * </ul>
    466      *
    467      * @param freezePeriods the list of freeze periods
    468      * @throws ValidationFailedException if the supplied freeze periods do not meet the
    469      *         requirement set above
    470      * @return this instance
    471      */
    472     public SystemUpdatePolicy setFreezePeriods(List<FreezePeriod> freezePeriods) {
    473         FreezePeriod.validatePeriods(freezePeriods);
    474         mFreezePeriods.clear();
    475         mFreezePeriods.addAll(freezePeriods);
    476         return this;
    477     }
    478 
    479     /**
    480      * Returns the list of freeze periods previously set on this system update policy object.
    481      *
    482      * @return the list of freeze periods, or an empty list if none was set.
    483      */
    484     public List<FreezePeriod> getFreezePeriods() {
    485         return Collections.unmodifiableList(mFreezePeriods);
    486     }
    487 
    488     /**
    489      * Returns the real calendar dates of the current freeze period, or null if the device
    490      * is not in a freeze period at the moment.
    491      * @hide
    492      */
    493     public Pair<LocalDate, LocalDate> getCurrentFreezePeriod(LocalDate now) {
    494         for (FreezePeriod interval : mFreezePeriods) {
    495             if (interval.contains(now)) {
    496                 return interval.toCurrentOrFutureRealDates(now);
    497             }
    498         }
    499         return null;
    500     }
    501 
    502     /**
    503      * Returns time (in milliseconds) until the start of the next freeze period, assuming now
    504      * is not within a freeze period.
    505      */
    506     private long timeUntilNextFreezePeriod(long now) {
    507         List<FreezePeriod> sortedPeriods = FreezePeriod.canonicalizePeriods(mFreezePeriods);
    508         LocalDate nowDate = millisToDate(now);
    509         LocalDate nextFreezeStart = null;
    510         for (FreezePeriod interval : sortedPeriods) {
    511             if (interval.after(nowDate)) {
    512                 nextFreezeStart = interval.toCurrentOrFutureRealDates(nowDate).first;
    513                 break;
    514             } else if (interval.contains(nowDate)) {
    515                 throw new IllegalArgumentException("Given date is inside a freeze period");
    516             }
    517         }
    518         if (nextFreezeStart == null) {
    519             // If no interval is after now, then it must be the one that starts at the beginning
    520             // of next year
    521             nextFreezeStart = sortedPeriods.get(0).toCurrentOrFutureRealDates(nowDate).first;
    522         }
    523         return dateToMillis(nextFreezeStart) - now;
    524     }
    525 
    526     /** @hide */
    527     public void validateFreezePeriods() {
    528         FreezePeriod.validatePeriods(mFreezePeriods);
    529     }
    530 
    531     /** @hide */
    532     public void validateAgainstPreviousFreezePeriod(LocalDate prevPeriodStart,
    533             LocalDate prevPeriodEnd, LocalDate now) {
    534         FreezePeriod.validateAgainstPreviousFreezePeriod(mFreezePeriods, prevPeriodStart,
    535                 prevPeriodEnd, now);
    536     }
    537 
    538     /**
    539      * An installation option represents how system update clients should act on incoming system
    540      * updates and how long this action is valid for, given the current system update policy. Its
    541      * action could be one of the following
    542      * <ul>
    543      * <li> {@link #TYPE_INSTALL_AUTOMATIC} system updates should be installed immedately and
    544      * without user intervention as soon as they become available.
    545      * <li> {@link #TYPE_POSTPONE} system updates should be postponed for a maximum of 30 days
    546      * <li> {@link #TYPE_PAUSE} system updates should be postponed indefinitely until further notice
    547      * </ul>
    548      *
    549      * The effective time measures how long this installation option is valid for from the queried
    550      * time, in milliseconds.
    551      *
    552      * This is an internal API for system update clients.
    553      * @hide
    554      */
    555     @SystemApi
    556     public static class InstallationOption {
    557         /** @hide */
    558         @IntDef(prefix = { "TYPE_" }, value = {
    559                 TYPE_INSTALL_AUTOMATIC,
    560                 TYPE_PAUSE,
    561                 TYPE_POSTPONE
    562         })
    563         @Retention(RetentionPolicy.SOURCE)
    564         @interface InstallationOptionType {}
    565 
    566         @InstallationOptionType
    567         private final int mType;
    568         private long mEffectiveTime;
    569 
    570         InstallationOption(@InstallationOptionType int type, long effectiveTime) {
    571             this.mType = type;
    572             this.mEffectiveTime = effectiveTime;
    573         }
    574 
    575         /**
    576          * Returns the type of the current installation option, could be one of
    577          * {@link #TYPE_INSTALL_AUTOMATIC}, {@link #TYPE_POSTPONE} and {@link #TYPE_PAUSE}.
    578          * @return type of installation option.
    579          */
    580         public @InstallationOptionType int getType() {
    581             return mType;
    582         }
    583 
    584         /**
    585          * Returns how long the current installation option in effective for, starting from the time
    586          * of query.
    587          * @return the effective time in milliseconds.
    588          */
    589         public long getEffectiveTime() {
    590             return mEffectiveTime;
    591         }
    592 
    593         /** @hide */
    594         protected void limitEffectiveTime(long otherTime) {
    595             mEffectiveTime = Long.min(mEffectiveTime, otherTime);
    596         }
    597     }
    598 
    599     /**
    600      * Returns the installation option at the specified time, under the current
    601      * {@code SystemUpdatePolicy} object. This is a convenience method for system update clients
    602      * so they can instantiate this policy at any given time and find out what to do with incoming
    603      * system updates, without the need of examining the overall policy structure.
    604      *
    605      * Normally the system update clients will query the current installation option by calling this
    606      * method with the current timestamp, and act on the returned option until its effective time
    607      * lapses. It can then query the latest option using a new timestamp. It should also listen
    608      * for {@code DevicePolicyManager#ACTION_SYSTEM_UPDATE_POLICY_CHANGED} broadcast, in case the
    609      * whole policy is updated.
    610      *
    611      * @param when At what time the intallation option is being queried, specified in number of
    612            milliseonds since the epoch.
    613      * @see InstallationOption
    614      * @hide
    615      */
    616     @SystemApi
    617     public InstallationOption getInstallationOptionAt(long when) {
    618         LocalDate whenDate = millisToDate(when);
    619         Pair<LocalDate, LocalDate> current = getCurrentFreezePeriod(whenDate);
    620         if (current != null) {
    621             return new InstallationOption(TYPE_PAUSE,
    622                     dateToMillis(roundUpLeapDay(current.second).plusDays(1)) - when);
    623         }
    624         // We are not within a freeze period, query the underlying policy.
    625         // But also consider the start of the next freeze period, which might
    626         // reduce the effective time of the current installation option
    627         InstallationOption option = getInstallationOptionRegardlessFreezeAt(when);
    628         if (mFreezePeriods.size() > 0) {
    629             option.limitEffectiveTime(timeUntilNextFreezePeriod(when));
    630         }
    631         return option;
    632     }
    633 
    634     private InstallationOption getInstallationOptionRegardlessFreezeAt(long when) {
    635         if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) {
    636             return new InstallationOption(mPolicyType, Long.MAX_VALUE);
    637         } else if (mPolicyType == TYPE_INSTALL_WINDOWED) {
    638             Calendar query = Calendar.getInstance();
    639             query.setTimeInMillis(when);
    640             // Calculate the number of milliseconds since midnight of the time specified by when
    641             long whenMillis = TimeUnit.HOURS.toMillis(query.get(Calendar.HOUR_OF_DAY))
    642                     + TimeUnit.MINUTES.toMillis(query.get(Calendar.MINUTE))
    643                     + TimeUnit.SECONDS.toMillis(query.get(Calendar.SECOND))
    644                     + query.get(Calendar.MILLISECOND);
    645             long windowStartMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowStart);
    646             long windowEndMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowEnd);
    647             final long dayInMillis = TimeUnit.DAYS.toMillis(1);
    648 
    649             if ((windowStartMillis <= whenMillis && whenMillis <= windowEndMillis)
    650                     || ((windowStartMillis > windowEndMillis)
    651                     && (windowStartMillis <= whenMillis || whenMillis <= windowEndMillis))) {
    652                 return new InstallationOption(TYPE_INSTALL_AUTOMATIC,
    653                         (windowEndMillis - whenMillis + dayInMillis) % dayInMillis);
    654             } else {
    655                 return new InstallationOption(TYPE_PAUSE,
    656                         (windowStartMillis - whenMillis + dayInMillis) % dayInMillis);
    657             }
    658         } else {
    659             throw new RuntimeException("Unknown policy type");
    660         }
    661     }
    662 
    663     private static LocalDate roundUpLeapDay(LocalDate date) {
    664         if (date.isLeapYear() && date.getMonthValue() == 2 && date.getDayOfMonth() == 28) {
    665             return date.plusDays(1);
    666         } else {
    667             return date;
    668         }
    669     }
    670 
    671     /** Convert a timestamp since epoch to a LocalDate using default timezone, truncating
    672      * the hour/min/seconds part.
    673      */
    674     private static LocalDate millisToDate(long when) {
    675         return Instant.ofEpochMilli(when).atZone(ZoneId.systemDefault()).toLocalDate();
    676     }
    677 
    678     /**
    679      * Returns the timestamp since epoch of a LocalDate, assuming the time is 00:00:00.
    680      */
    681     private static long dateToMillis(LocalDate when) {
    682         return LocalDateTime.of(when, LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant()
    683                 .toEpochMilli();
    684     }
    685 
    686     @Override
    687     public String toString() {
    688         return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d, "
    689                 + "freezes: [%s])",
    690                 mPolicyType, mMaintenanceWindowStart, mMaintenanceWindowEnd,
    691                 mFreezePeriods.stream().map(n -> n.toString()).collect(Collectors.joining(",")));
    692     }
    693 
    694     @SystemApi
    695     @Override
    696     public int describeContents() {
    697         return 0;
    698     }
    699 
    700     @SystemApi
    701     @Override
    702     public void writeToParcel(Parcel dest, int flags) {
    703         dest.writeInt(mPolicyType);
    704         dest.writeInt(mMaintenanceWindowStart);
    705         dest.writeInt(mMaintenanceWindowEnd);
    706         int freezeCount = mFreezePeriods.size();
    707         dest.writeInt(freezeCount);
    708         for (int i = 0; i < freezeCount; i++) {
    709             FreezePeriod interval = mFreezePeriods.get(i);
    710             dest.writeInt(interval.getStart().getMonthValue());
    711             dest.writeInt(interval.getStart().getDayOfMonth());
    712             dest.writeInt(interval.getEnd().getMonthValue());
    713             dest.writeInt(interval.getEnd().getDayOfMonth());
    714         }
    715     }
    716 
    717     @SystemApi
    718     public static final Parcelable.Creator<SystemUpdatePolicy> CREATOR =
    719             new Parcelable.Creator<SystemUpdatePolicy>() {
    720 
    721                 @Override
    722                 public SystemUpdatePolicy createFromParcel(Parcel source) {
    723                     SystemUpdatePolicy policy = new SystemUpdatePolicy();
    724                     policy.mPolicyType = source.readInt();
    725                     policy.mMaintenanceWindowStart = source.readInt();
    726                     policy.mMaintenanceWindowEnd = source.readInt();
    727                     int freezeCount = source.readInt();
    728                     policy.mFreezePeriods.ensureCapacity(freezeCount);
    729                     for (int i = 0; i < freezeCount; i++) {
    730                         MonthDay start = MonthDay.of(source.readInt(), source.readInt());
    731                         MonthDay end = MonthDay.of(source.readInt(), source.readInt());
    732                         policy.mFreezePeriods.add(new FreezePeriod(start, end));
    733                     }
    734                     return policy;
    735                 }
    736 
    737                 @Override
    738                 public SystemUpdatePolicy[] newArray(int size) {
    739                     return new SystemUpdatePolicy[size];
    740                 }
    741     };
    742 
    743     /**
    744      * Restore a previously saved SystemUpdatePolicy from XML. No need to validate
    745      * the reconstructed policy since the XML is supposed to be created by the
    746      * system server from a validated policy object previously.
    747      * @hide
    748      */
    749     public static SystemUpdatePolicy restoreFromXml(XmlPullParser parser) {
    750         try {
    751             SystemUpdatePolicy policy = new SystemUpdatePolicy();
    752             String value = parser.getAttributeValue(null, KEY_POLICY_TYPE);
    753             if (value != null) {
    754                 policy.mPolicyType = Integer.parseInt(value);
    755 
    756                 value = parser.getAttributeValue(null, KEY_INSTALL_WINDOW_START);
    757                 if (value != null) {
    758                     policy.mMaintenanceWindowStart = Integer.parseInt(value);
    759                 }
    760                 value = parser.getAttributeValue(null, KEY_INSTALL_WINDOW_END);
    761                 if (value != null) {
    762                     policy.mMaintenanceWindowEnd = Integer.parseInt(value);
    763                 }
    764 
    765                 int outerDepth = parser.getDepth();
    766                 int type;
    767                 while ((type = parser.next()) != END_DOCUMENT
    768                         && (type != END_TAG || parser.getDepth() > outerDepth)) {
    769                     if (type == END_TAG || type == TEXT) {
    770                         continue;
    771                     }
    772                     if (!parser.getName().equals(KEY_FREEZE_TAG)) {
    773                         continue;
    774                     }
    775                     policy.mFreezePeriods.add(new FreezePeriod(
    776                             MonthDay.parse(parser.getAttributeValue(null, KEY_FREEZE_START)),
    777                             MonthDay.parse(parser.getAttributeValue(null, KEY_FREEZE_END))));
    778                 }
    779                 return policy;
    780             }
    781         } catch (NumberFormatException | XmlPullParserException | IOException e) {
    782             // Fail through
    783             Log.w(TAG, "Load xml failed", e);
    784         }
    785         return null;
    786     }
    787 
    788     /**
    789      * @hide
    790      */
    791     public void saveToXml(XmlSerializer out) throws IOException {
    792         out.attribute(null, KEY_POLICY_TYPE, Integer.toString(mPolicyType));
    793         out.attribute(null, KEY_INSTALL_WINDOW_START, Integer.toString(mMaintenanceWindowStart));
    794         out.attribute(null, KEY_INSTALL_WINDOW_END, Integer.toString(mMaintenanceWindowEnd));
    795         for (int i = 0; i < mFreezePeriods.size(); i++) {
    796             FreezePeriod interval = mFreezePeriods.get(i);
    797             out.startTag(null, KEY_FREEZE_TAG);
    798             out.attribute(null, KEY_FREEZE_START, interval.getStart().toString());
    799             out.attribute(null, KEY_FREEZE_END, interval.getEnd().toString());
    800             out.endTag(null, KEY_FREEZE_TAG);
    801         }
    802     }
    803 }
    804 
    805