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