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