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 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 won’t extend
129      * the period. If, after 30 days the update isn’t 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 
ValidationFailedException(int errorCode, String message)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          */
getErrorCode()244         public @ValidationFailureType int getErrorCode() {
245             return mErrorCode;
246         }
247 
248         /** @hide */
duplicateOrOverlapPeriods()249         public static ValidationFailedException duplicateOrOverlapPeriods() {
250             return new ValidationFailedException(ERROR_DUPLICATE_OR_OVERLAP,
251                     "Found duplicate or overlapping periods");
252         }
253 
254         /** @hide */
freezePeriodTooLong(String message)255         public static ValidationFailedException freezePeriodTooLong(String message) {
256             return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_LONG, message);
257         }
258 
259         /** @hide */
freezePeriodTooClose(String message)260         public static ValidationFailedException freezePeriodTooClose(String message) {
261             return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, message);
262         }
263 
264         /** @hide */
combinedPeriodTooLong(String message)265         public static ValidationFailedException combinedPeriodTooLong(String message) {
266             return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, message);
267         }
268 
269         /** @hide */
combinedPeriodTooClose(String message)270         public static ValidationFailedException combinedPeriodTooClose(String message) {
271             return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, message);
272         }
273 
274         @Override
describeContents()275         public int describeContents() {
276             return 0;
277         }
278 
279         @Override
writeToParcel(Parcel dest, int flags)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 
SystemUpdatePolicy()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      */
createAutomaticInstallPolicy()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      */
createWindowedInstallPolicy(int startTime, int endTime)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      */
createPostponeInstallPolicy()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
getPolicyType()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      */
getInstallWindowStart()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      */
getInstallWindowEnd()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      */
isValid()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      */
validateType()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      */
setFreezePeriods(List<FreezePeriod> freezePeriods)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      */
getFreezePeriods()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      */
getCurrentFreezePeriod(LocalDate now)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      */
timeUntilNextFreezePeriod(long now)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 */
validateFreezePeriods()527     public void validateFreezePeriods() {
528         FreezePeriod.validatePeriods(mFreezePeriods);
529     }
530 
531     /** @hide */
validateAgainstPreviousFreezePeriod(LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now)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 
InstallationOption(@nstallationOptionType int type, long effectiveTime)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          */
getType()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          */
getEffectiveTime()589         public long getEffectiveTime() {
590             return mEffectiveTime;
591         }
592 
593         /** @hide */
limitEffectiveTime(long otherTime)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
getInstallationOptionAt(long when)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 
getInstallationOptionRegardlessFreezeAt(long when)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 
roundUpLeapDay(LocalDate date)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      */
millisToDate(long when)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      */
dateToMillis(LocalDate when)681     private static long dateToMillis(LocalDate when) {
682         return LocalDateTime.of(when, LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant()
683                 .toEpochMilli();
684     }
685 
686     @Override
toString()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
describeContents()696     public int describeContents() {
697         return 0;
698     }
699 
700     @SystemApi
701     @Override
writeToParcel(Parcel dest, int flags)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      */
restoreFromXml(XmlPullParser parser)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      */
saveToXml(XmlSerializer out)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