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