1 /* 2 * Copyright (C) 2016 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.os; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.SystemApi; 22 import android.annotation.WorkerThread; 23 import android.content.res.AssetFileDescriptor; 24 import android.os.IUpdateEngine; 25 import android.os.IUpdateEngineCallback; 26 import android.os.RemoteException; 27 28 /** 29 * UpdateEngine handles calls to the update engine which takes care of A/B OTA 30 * updates. It wraps up the update engine Binder APIs and exposes them as 31 * SystemApis, which will be called by the system app responsible for OTAs. 32 * On a Google device, this will be GmsCore. 33 * 34 * The minimal flow is: 35 * <ol> 36 * <li>Create a new UpdateEngine instance. 37 * <li>Call {@link #bind}, optionally providing callbacks. 38 * <li>Call {@link #applyPayload}. 39 * </ol> 40 * 41 * In addition, methods are provided to {@link #cancel} or 42 * {@link #suspend}/{@link #resume} application of an update. 43 * 44 * The APIs defined in this class and UpdateEngineCallback class must be in 45 * sync with the ones in 46 * {@code system/update_engine/binder_bindings/android/os/IUpdateEngine.aidl} 47 * and 48 * {@code system/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl}. 49 * 50 * {@hide} 51 */ 52 @SystemApi 53 public class UpdateEngine { 54 private static final String TAG = "UpdateEngine"; 55 56 private static final String UPDATE_ENGINE_SERVICE = "android.os.UpdateEngineService"; 57 58 /** 59 * Error codes from update engine upon finishing a call to 60 * {@link applyPayload}. Values will be passed via the callback function 61 * {@link UpdateEngineCallback#onPayloadApplicationComplete}. Values must 62 * agree with the ones in {@code system/update_engine/common/error_code.h}. 63 */ 64 public static final class ErrorCodeConstants { 65 /** 66 * Error code: a request finished successfully. 67 */ 68 public static final int SUCCESS = 0; 69 /** 70 * Error code: a request failed due to a generic error. 71 */ 72 public static final int ERROR = 1; 73 /** 74 * Error code: an update failed to apply due to filesystem copier 75 * error. 76 */ 77 public static final int FILESYSTEM_COPIER_ERROR = 4; 78 /** 79 * Error code: an update failed to apply due to an error in running 80 * post-install hooks. 81 */ 82 public static final int POST_INSTALL_RUNNER_ERROR = 5; 83 /** 84 * Error code: an update failed to apply due to a mismatching payload. 85 * 86 * <p>For example, the given payload uses a feature that's not 87 * supported by the current update engine. 88 */ 89 public static final int PAYLOAD_MISMATCHED_TYPE_ERROR = 6; 90 /** 91 * Error code: an update failed to apply due to an error in opening 92 * devices. 93 */ 94 public static final int INSTALL_DEVICE_OPEN_ERROR = 7; 95 /** 96 * Error code: an update failed to apply due to an error in opening 97 * kernel device. 98 */ 99 public static final int KERNEL_DEVICE_OPEN_ERROR = 8; 100 /** 101 * Error code: an update failed to apply due to an error in fetching 102 * the payload. 103 * 104 * <p>For example, this could be a result of bad network connection 105 * when streaming an update. 106 */ 107 public static final int DOWNLOAD_TRANSFER_ERROR = 9; 108 /** 109 * Error code: an update failed to apply due to a mismatch in payload 110 * hash. 111 * 112 * <p>Update engine does validity checks for the given payload and its 113 * metadata. 114 */ 115 public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10; 116 117 /** 118 * Error code: an update failed to apply due to a mismatch in payload 119 * size. 120 */ 121 public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11; 122 123 /** 124 * Error code: an update failed to apply due to failing to verify 125 * payload signatures. 126 */ 127 public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12; 128 129 /** 130 * Error code: an update failed to apply due to a downgrade in payload 131 * timestamp. 132 * 133 * <p>The timestamp of a build is encoded into the payload, which will 134 * be enforced during install to prevent downgrading a device. 135 */ 136 public static final int PAYLOAD_TIMESTAMP_ERROR = 51; 137 138 /** 139 * Error code: an update has been applied successfully but the new slot 140 * hasn't been set to active. 141 * 142 * <p>It indicates a successful finish of calling {@link #applyPayload} with 143 * {@code SWITCH_SLOT_ON_REBOOT=0}. See {@link #applyPayload}. 144 */ 145 public static final int UPDATED_BUT_NOT_ACTIVE = 52; 146 147 /** 148 * Error code: there is not enough space on the device to apply the update. User should 149 * be prompted to free up space and re-try the update. 150 * 151 * <p>See {@link UpdateEngine#allocateSpace}. 152 */ 153 public static final int NOT_ENOUGH_SPACE = 60; 154 155 /** 156 * Error code: the device is corrupted and no further updates may be applied. 157 * 158 * <p>See {@link UpdateEngine#cleanupAppliedPayload}. 159 */ 160 public static final int DEVICE_CORRUPTED = 61; 161 } 162 163 /** @hide */ 164 @IntDef(value = { 165 ErrorCodeConstants.SUCCESS, 166 ErrorCodeConstants.ERROR, 167 ErrorCodeConstants.FILESYSTEM_COPIER_ERROR, 168 ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR, 169 ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR, 170 ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR, 171 ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR, 172 ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR, 173 ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, 174 ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR, 175 ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR, 176 ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR, 177 ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE, 178 ErrorCodeConstants.NOT_ENOUGH_SPACE, 179 ErrorCodeConstants.DEVICE_CORRUPTED, 180 }) 181 public @interface ErrorCode {} 182 183 /** 184 * Status codes for update engine. Values must agree with the ones in 185 * {@code system/update_engine/client_library/include/update_engine/update_status.h}. 186 */ 187 public static final class UpdateStatusConstants { 188 /** 189 * Update status code: update engine is in idle state. 190 */ 191 public static final int IDLE = 0; 192 193 /** 194 * Update status code: update engine is checking for update. 195 */ 196 public static final int CHECKING_FOR_UPDATE = 1; 197 198 /** 199 * Update status code: an update is available. 200 */ 201 public static final int UPDATE_AVAILABLE = 2; 202 203 /** 204 * Update status code: update engine is downloading an update. 205 */ 206 public static final int DOWNLOADING = 3; 207 208 /** 209 * Update status code: update engine is verifying an update. 210 */ 211 public static final int VERIFYING = 4; 212 213 /** 214 * Update status code: update engine is finalizing an update. 215 */ 216 public static final int FINALIZING = 5; 217 218 /** 219 * Update status code: an update has been applied and is pending for 220 * reboot. 221 */ 222 public static final int UPDATED_NEED_REBOOT = 6; 223 224 /** 225 * Update status code: update engine is reporting an error event. 226 */ 227 public static final int REPORTING_ERROR_EVENT = 7; 228 229 /** 230 * Update status code: update engine is attempting to rollback an 231 * update. 232 */ 233 public static final int ATTEMPTING_ROLLBACK = 8; 234 235 /** 236 * Update status code: update engine is in disabled state. 237 */ 238 public static final int DISABLED = 9; 239 } 240 241 private final IUpdateEngine mUpdateEngine; 242 private IUpdateEngineCallback mUpdateEngineCallback = null; 243 private final Object mUpdateEngineCallbackLock = new Object(); 244 245 /** 246 * Creates a new instance. 247 */ UpdateEngine()248 public UpdateEngine() { 249 mUpdateEngine = IUpdateEngine.Stub.asInterface( 250 ServiceManager.getService(UPDATE_ENGINE_SERVICE)); 251 if (mUpdateEngine == null) { 252 throw new IllegalStateException("Failed to find update_engine"); 253 } 254 } 255 256 /** 257 * Prepares this instance for use. The callback will be notified on any 258 * status change, and when the update completes. A handler can be supplied 259 * to control which thread runs the callback, or null. 260 */ bind(final UpdateEngineCallback callback, final Handler handler)261 public boolean bind(final UpdateEngineCallback callback, final Handler handler) { 262 synchronized (mUpdateEngineCallbackLock) { 263 mUpdateEngineCallback = new IUpdateEngineCallback.Stub() { 264 @Override 265 public void onStatusUpdate(final int status, final float percent) { 266 if (handler != null) { 267 handler.post(new Runnable() { 268 @Override 269 public void run() { 270 callback.onStatusUpdate(status, percent); 271 } 272 }); 273 } else { 274 callback.onStatusUpdate(status, percent); 275 } 276 } 277 278 @Override 279 public void onPayloadApplicationComplete(final int errorCode) { 280 if (handler != null) { 281 handler.post(new Runnable() { 282 @Override 283 public void run() { 284 callback.onPayloadApplicationComplete(errorCode); 285 } 286 }); 287 } else { 288 callback.onPayloadApplicationComplete(errorCode); 289 } 290 } 291 }; 292 293 try { 294 return mUpdateEngine.bind(mUpdateEngineCallback); 295 } catch (RemoteException e) { 296 throw e.rethrowFromSystemServer(); 297 } 298 } 299 } 300 301 /** 302 * Equivalent to {@code bind(callback, null)}. 303 */ bind(final UpdateEngineCallback callback)304 public boolean bind(final UpdateEngineCallback callback) { 305 return bind(callback, null); 306 } 307 308 /** 309 * Applies the payload found at the given {@code url}. For non-streaming 310 * updates, the URL can be a local file using the {@code file://} scheme. 311 * 312 * <p>The {@code offset} and {@code size} parameters specify the location 313 * of the payload within the file represented by the URL. This is useful 314 * if the downloadable package at the URL contains more than just the 315 * update_engine payload (such as extra metadata). This is true for 316 * Google's OTA system, where the URL points to a zip file in which the 317 * payload is stored uncompressed within the zip file alongside other 318 * data. 319 * 320 * <p>The {@code headerKeyValuePairs} parameter is used to pass metadata 321 * to update_engine. In Google's implementation, this is stored as 322 * {@code payload_properties.txt} in the zip file. It's generated by the 323 * script {@code system/update_engine/scripts/brillo_update_payload}. 324 * The complete list of keys and their documentation is in 325 * {@code system/update_engine/common/constants.cc}, but an example 326 * might be: 327 * <pre> 328 * String[] pairs = { 329 * "FILE_HASH=lURPCIkIAjtMOyB/EjQcl8zDzqtD6Ta3tJef6G/+z2k=", 330 * "FILE_SIZE=871903868", 331 * "METADATA_HASH=tBvj43QOB0Jn++JojcpVdbRLz0qdAuL+uTkSy7hokaw=", 332 * "METADATA_SIZE=70604" 333 * }; 334 * </pre> 335 * 336 * <p>The callback functions registered via {@code #bind} will be called 337 * during and at the end of the payload application. 338 * 339 * <p>By default the newly updated slot will be set active upon 340 * successfully finishing an update. Device will attempt to boot into the 341 * new slot on next reboot. This behavior can be customized by specifying 342 * {@code SWITCH_SLOT_ON_REBOOT=0} in {@code headerKeyValuePairs}, which 343 * allows the caller to later determine a good time to boot into the new 344 * slot. Calling {@code applyPayload} again with the same payload but with 345 * {@code SWITCH_SLOT_ON_REBOOT=1} will do the minimal work to set the new 346 * slot active, after verifying its integrity. 347 */ applyPayload(String url, long offset, long size, String[] headerKeyValuePairs)348 public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) { 349 try { 350 mUpdateEngine.applyPayload(url, offset, size, headerKeyValuePairs); 351 } catch (RemoteException e) { 352 throw e.rethrowFromSystemServer(); 353 } 354 } 355 356 /** 357 * Applies the payload passed as AssetFileDescriptor {@code assetFd} 358 * instead of using the {@code file://} scheme. 359 * 360 * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and 361 * {@code headerKeyValuePairs} parameters. 362 */ applyPayload(@onNull AssetFileDescriptor assetFd, @NonNull String[] headerKeyValuePairs)363 public void applyPayload(@NonNull AssetFileDescriptor assetFd, 364 @NonNull String[] headerKeyValuePairs) { 365 try { 366 mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(), 367 assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs); 368 } catch (RemoteException e) { 369 throw e.rethrowFromSystemServer(); 370 } 371 } 372 373 /** 374 * Permanently cancels an in-progress update. 375 * 376 * <p>See {@link #resetStatus} to undo a finshed update (only available 377 * before the updated system has been rebooted). 378 * 379 * <p>See {@link #suspend} for a way to temporarily stop an in-progress 380 * update with the ability to resume it later. 381 */ cancel()382 public void cancel() { 383 try { 384 mUpdateEngine.cancel(); 385 } catch (RemoteException e) { 386 throw e.rethrowFromSystemServer(); 387 } 388 } 389 390 /** 391 * Suspends an in-progress update. This can be undone by calling 392 * {@link #resume}. 393 */ suspend()394 public void suspend() { 395 try { 396 mUpdateEngine.suspend(); 397 } catch (RemoteException e) { 398 throw e.rethrowFromSystemServer(); 399 } 400 } 401 402 /** 403 * Resumes a suspended update. 404 */ resume()405 public void resume() { 406 try { 407 mUpdateEngine.resume(); 408 } catch (RemoteException e) { 409 throw e.rethrowFromSystemServer(); 410 } 411 } 412 413 /** 414 * Resets the bootable flag on the non-current partition and all internal 415 * update_engine state. Note this call will clear the entire update 416 * progress. So a subsequent {@link #applyPayload} will apply the update 417 * from scratch. 418 * 419 * <p>After this call completes, update_engine will no longer report 420 * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding 421 * notification that rebooting into the new system is possible. 422 */ resetStatus()423 public void resetStatus() { 424 try { 425 mUpdateEngine.resetStatus(); 426 } catch (RemoteException e) { 427 throw e.rethrowFromSystemServer(); 428 } 429 } 430 431 /** 432 * Sets the A/B slot switch for the next boot after applying an ota update. If 433 * {@link #applyPayload} hasn't switched the slot, the updater APP can call 434 * this API to switch the slot and apply the update on next boot. 435 * 436 * @param payloadMetadataFilename the location of the metadata without the 437 * {@code file://} prefix. 438 */ setShouldSwitchSlotOnReboot(@onNull String payloadMetadataFilename)439 public void setShouldSwitchSlotOnReboot(@NonNull String payloadMetadataFilename) { 440 try { 441 mUpdateEngine.setShouldSwitchSlotOnReboot(payloadMetadataFilename); 442 } catch (RemoteException e) { 443 throw e.rethrowFromSystemServer(); 444 } 445 } 446 447 /** 448 * Resets the boot slot to the source/current slot, without cancelling the 449 * update progress. This can be called after the update is installed, and to 450 * prevent the device from accidentally taking the update when it reboots. 451 * 452 * This is useful when users don't want to take the update immediately; or 453 * the updater determines some condition hasn't met, e.g. insufficient space 454 * for boot. 455 */ resetShouldSwitchSlotOnReboot()456 public void resetShouldSwitchSlotOnReboot() { 457 try { 458 mUpdateEngine.resetShouldSwitchSlotOnReboot(); 459 } catch (RemoteException e) { 460 throw e.rethrowFromSystemServer(); 461 } 462 } 463 464 /** 465 * Unbinds the last bound callback function. 466 */ unbind()467 public boolean unbind() { 468 synchronized (mUpdateEngineCallbackLock) { 469 if (mUpdateEngineCallback == null) { 470 return true; 471 } 472 try { 473 boolean result = mUpdateEngine.unbind(mUpdateEngineCallback); 474 mUpdateEngineCallback = null; 475 return result; 476 } catch (RemoteException e) { 477 throw e.rethrowFromSystemServer(); 478 } 479 } 480 } 481 482 /** 483 * Verifies that a payload associated with the given payload metadata 484 * {@code payloadMetadataFilename} can be safely applied to ths device. 485 * Returns {@code true} if the update can successfully be applied and 486 * returns {@code false} otherwise. 487 * 488 * @param payloadMetadataFilename the location of the metadata without the 489 * {@code file://} prefix. 490 */ verifyPayloadMetadata(String payloadMetadataFilename)491 public boolean verifyPayloadMetadata(String payloadMetadataFilename) { 492 try { 493 return mUpdateEngine.verifyPayloadApplicable(payloadMetadataFilename); 494 } catch (RemoteException e) { 495 throw e.rethrowFromSystemServer(); 496 } 497 } 498 499 /** 500 * Return value of {@link #allocateSpace.} 501 */ 502 public static final class AllocateSpaceResult { 503 private @ErrorCode int mErrorCode = ErrorCodeConstants.SUCCESS; 504 private long mFreeSpaceRequired = 0; AllocateSpaceResult()505 private AllocateSpaceResult() {} 506 /** 507 * Error code. 508 * 509 * @return The following error codes: 510 * <ul> 511 * <li>{@link ErrorCodeConstants#SUCCESS} if space has been allocated 512 * successfully.</li> 513 * <li>{@link ErrorCodeConstants#NOT_ENOUGH_SPACE} if insufficient 514 * space.</li> 515 * <li>Other {@link ErrorCodeConstants} for other errors.</li> 516 * </ul> 517 */ 518 @ErrorCode getErrorCode()519 public int getErrorCode() { 520 return mErrorCode; 521 } 522 523 /** 524 * Estimated total space that needs to be available on the userdata partition to apply the 525 * payload (in bytes). 526 * 527 * <p> 528 * Note that in practice, more space needs to be made available before applying the payload 529 * to keep the device working. 530 * 531 * @return The following values: 532 * <ul> 533 * <li>zero if {@link #getErrorCode} returns {@link ErrorCodeConstants#SUCCESS}</li> 534 * <li>non-zero if {@link #getErrorCode} returns 535 * {@link ErrorCodeConstants#NOT_ENOUGH_SPACE}. 536 * Value is the estimated total space required on userdata partition.</li> 537 * </ul> 538 * @throws IllegalStateException if {@link #getErrorCode} is not one of the above. 539 * 540 */ getFreeSpaceRequired()541 public long getFreeSpaceRequired() { 542 if (mErrorCode == ErrorCodeConstants.SUCCESS) { 543 return 0; 544 } 545 if (mErrorCode == ErrorCodeConstants.NOT_ENOUGH_SPACE) { 546 return mFreeSpaceRequired; 547 } 548 throw new IllegalStateException(String.format( 549 "getFreeSpaceRequired() is not available when error code is %d", mErrorCode)); 550 } 551 } 552 553 /** 554 * Initialize partitions for a payload associated with the given payload 555 * metadata {@code payloadMetadataFilename} by preallocating required space. 556 * 557 * <p>This function should be called after payload has been verified after 558 * {@link #verifyPayloadMetadata}. This function does not verify whether 559 * the given payload is applicable or not. 560 * 561 * <p>Implementation of {@code allocateSpace} uses 562 * {@code headerKeyValuePairs} to determine whether space has been allocated 563 * for a different or same payload previously. If space has been allocated 564 * for a different payload before, space will be reallocated for the given 565 * payload. If space has been allocated for the same payload, no actions to 566 * storage devices are taken. 567 * 568 * <p>This function is synchronous and may take a non-trivial amount of 569 * time. Callers should call this function in a background thread. 570 * 571 * @param payloadMetadataFilename See {@link #verifyPayloadMetadata}. 572 * @param headerKeyValuePairs See {@link #applyPayload}. 573 * @return See {@link AllocateSpaceResult#getErrorCode} and 574 * {@link AllocateSpaceResult#getFreeSpaceRequired}. 575 */ 576 @WorkerThread 577 @NonNull allocateSpace( @onNull String payloadMetadataFilename, @NonNull String[] headerKeyValuePairs)578 public AllocateSpaceResult allocateSpace( 579 @NonNull String payloadMetadataFilename, 580 @NonNull String[] headerKeyValuePairs) { 581 AllocateSpaceResult result = new AllocateSpaceResult(); 582 try { 583 result.mFreeSpaceRequired = mUpdateEngine.allocateSpaceForPayload( 584 payloadMetadataFilename, 585 headerKeyValuePairs); 586 result.mErrorCode = result.mFreeSpaceRequired == 0 587 ? ErrorCodeConstants.SUCCESS 588 : ErrorCodeConstants.NOT_ENOUGH_SPACE; 589 return result; 590 } catch (ServiceSpecificException e) { 591 result.mErrorCode = e.errorCode; 592 result.mFreeSpaceRequired = 0; 593 return result; 594 } catch (RemoteException e) { 595 throw e.rethrowFromSystemServer(); 596 } 597 } 598 599 private static class CleanupAppliedPayloadCallback extends IUpdateEngineCallback.Stub { 600 private int mErrorCode = ErrorCodeConstants.ERROR; 601 private boolean mCompleted = false; 602 private Object mLock = new Object(); getResult()603 private int getResult() { 604 synchronized (mLock) { 605 while (!mCompleted) { 606 try { 607 mLock.wait(); 608 } catch (InterruptedException ex) { 609 // do nothing, just wait again. 610 } 611 } 612 return mErrorCode; 613 } 614 } 615 616 @Override onStatusUpdate(int status, float percent)617 public void onStatusUpdate(int status, float percent) { 618 } 619 620 @Override onPayloadApplicationComplete(int errorCode)621 public void onPayloadApplicationComplete(int errorCode) { 622 synchronized (mLock) { 623 mErrorCode = errorCode; 624 mCompleted = true; 625 mLock.notifyAll(); 626 } 627 } 628 } 629 630 /** 631 * Cleanup files used by the previous update and free up space after the 632 * device has been booted successfully into the new build. 633 * 634 * <p>In particular, this function waits until delta files for snapshots for 635 * Virtual A/B update are merged to OS partitions, then delete these delta 636 * files. 637 * 638 * <p>This function is synchronous and may take a non-trivial amount of 639 * time. Callers should call this function in a background thread. 640 * 641 * <p>This function does not delete payload binaries downloaded for a 642 * non-streaming OTA update. 643 * 644 * @return One of the following: 645 * <ul> 646 * <li>{@link ErrorCodeConstants#SUCCESS} if execution is successful.</li> 647 * <li>{@link ErrorCodeConstants#ERROR} if a transient error has occurred. 648 * The device should be able to recover after a reboot. The function should 649 * be retried after the reboot.</li> 650 * <li>{@link ErrorCodeConstants#DEVICE_CORRUPTED} if a permanent error is 651 * encountered. Device is corrupted, and future updates must not be applied. 652 * The device cannot recover without flashing and factory resets. 653 * </ul> 654 */ 655 @WorkerThread 656 @ErrorCode cleanupAppliedPayload()657 public int cleanupAppliedPayload() { 658 CleanupAppliedPayloadCallback callback = new CleanupAppliedPayloadCallback(); 659 try { 660 mUpdateEngine.cleanupSuccessfulUpdate(callback); 661 return callback.getResult(); 662 } catch (RemoteException e) { 663 throw e.rethrowFromSystemServer(); 664 } 665 } 666 } 667