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