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