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