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