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 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 } 252 253 /** 254 * Prepares this instance for use. The callback will be notified on any 255 * status change, and when the update completes. A handler can be supplied 256 * to control which thread runs the callback, or null. 257 */ bind(final UpdateEngineCallback callback, final Handler handler)258 public boolean bind(final UpdateEngineCallback callback, final Handler handler) { 259 synchronized (mUpdateEngineCallbackLock) { 260 mUpdateEngineCallback = new IUpdateEngineCallback.Stub() { 261 @Override 262 public void onStatusUpdate(final int status, final float percent) { 263 if (handler != null) { 264 handler.post(new Runnable() { 265 @Override 266 public void run() { 267 callback.onStatusUpdate(status, percent); 268 } 269 }); 270 } else { 271 callback.onStatusUpdate(status, percent); 272 } 273 } 274 275 @Override 276 public void onPayloadApplicationComplete(final int errorCode) { 277 if (handler != null) { 278 handler.post(new Runnable() { 279 @Override 280 public void run() { 281 callback.onPayloadApplicationComplete(errorCode); 282 } 283 }); 284 } else { 285 callback.onPayloadApplicationComplete(errorCode); 286 } 287 } 288 }; 289 290 try { 291 return mUpdateEngine.bind(mUpdateEngineCallback); 292 } catch (RemoteException e) { 293 throw e.rethrowFromSystemServer(); 294 } 295 } 296 } 297 298 /** 299 * Equivalent to {@code bind(callback, null)}. 300 */ bind(final UpdateEngineCallback callback)301 public boolean bind(final UpdateEngineCallback callback) { 302 return bind(callback, null); 303 } 304 305 /** 306 * Applies the payload found at the given {@code url}. For non-streaming 307 * updates, the URL can be a local file using the {@code file://} scheme. 308 * 309 * <p>The {@code offset} and {@code size} parameters specify the location 310 * of the payload within the file represented by the URL. This is useful 311 * if the downloadable package at the URL contains more than just the 312 * update_engine payload (such as extra metadata). This is true for 313 * Google's OTA system, where the URL points to a zip file in which the 314 * payload is stored uncompressed within the zip file alongside other 315 * data. 316 * 317 * <p>The {@code headerKeyValuePairs} parameter is used to pass metadata 318 * to update_engine. In Google's implementation, this is stored as 319 * {@code payload_properties.txt} in the zip file. It's generated by the 320 * script {@code system/update_engine/scripts/brillo_update_payload}. 321 * The complete list of keys and their documentation is in 322 * {@code system/update_engine/common/constants.cc}, but an example 323 * might be: 324 * <pre> 325 * String[] pairs = { 326 * "FILE_HASH=lURPCIkIAjtMOyB/EjQcl8zDzqtD6Ta3tJef6G/+z2k=", 327 * "FILE_SIZE=871903868", 328 * "METADATA_HASH=tBvj43QOB0Jn++JojcpVdbRLz0qdAuL+uTkSy7hokaw=", 329 * "METADATA_SIZE=70604" 330 * }; 331 * </pre> 332 * 333 * <p>The callback functions registered via {@code #bind} will be called 334 * during and at the end of the payload application. 335 * 336 * <p>By default the newly updated slot will be set active upon 337 * successfully finishing an update. Device will attempt to boot into the 338 * new slot on next reboot. This behavior can be customized by specifying 339 * {@code SWITCH_SLOT_ON_REBOOT=0} in {@code headerKeyValuePairs}, which 340 * allows the caller to later determine a good time to boot into the new 341 * slot. Calling {@code applyPayload} again with the same payload but with 342 * {@code SWITCH_SLOT_ON_REBOOT=1} will do the minimal work to set the new 343 * slot active, after verifying its integrity. 344 */ applyPayload(String url, long offset, long size, String[] headerKeyValuePairs)345 public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) { 346 try { 347 mUpdateEngine.applyPayload(url, offset, size, headerKeyValuePairs); 348 } catch (RemoteException e) { 349 throw e.rethrowFromSystemServer(); 350 } 351 } 352 353 /** 354 * Applies the payload passed as AssetFileDescriptor {@code assetFd} 355 * instead of using the {@code file://} scheme. 356 * 357 * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and 358 * {@code headerKeyValuePairs} parameters. 359 */ applyPayload(@onNull AssetFileDescriptor assetFd, @NonNull String[] headerKeyValuePairs)360 public void applyPayload(@NonNull AssetFileDescriptor assetFd, 361 @NonNull String[] headerKeyValuePairs) { 362 try { 363 mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(), 364 assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs); 365 } catch (RemoteException e) { 366 throw e.rethrowFromSystemServer(); 367 } 368 } 369 370 /** 371 * Permanently cancels an in-progress update. 372 * 373 * <p>See {@link #resetStatus} to undo a finshed update (only available 374 * before the updated system has been rebooted). 375 * 376 * <p>See {@link #suspend} for a way to temporarily stop an in-progress 377 * update with the ability to resume it later. 378 */ cancel()379 public void cancel() { 380 try { 381 mUpdateEngine.cancel(); 382 } catch (RemoteException e) { 383 throw e.rethrowFromSystemServer(); 384 } 385 } 386 387 /** 388 * Suspends an in-progress update. This can be undone by calling 389 * {@link #resume}. 390 */ suspend()391 public void suspend() { 392 try { 393 mUpdateEngine.suspend(); 394 } catch (RemoteException e) { 395 throw e.rethrowFromSystemServer(); 396 } 397 } 398 399 /** 400 * Resumes a suspended update. 401 */ resume()402 public void resume() { 403 try { 404 mUpdateEngine.resume(); 405 } catch (RemoteException e) { 406 throw e.rethrowFromSystemServer(); 407 } 408 } 409 410 /** 411 * Resets the bootable flag on the non-current partition and all internal 412 * update_engine state. This can be used after an unwanted payload has been 413 * successfully applied and the device has not yet been rebooted to signal 414 * that we no longer want to boot into that updated system. After this call 415 * completes, update_engine will no longer report 416 * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding 417 * notification that rebooting into the new system is possible. 418 */ resetStatus()419 public void resetStatus() { 420 try { 421 mUpdateEngine.resetStatus(); 422 } catch (RemoteException e) { 423 throw e.rethrowFromSystemServer(); 424 } 425 } 426 427 /** 428 * Unbinds the last bound callback function. 429 */ unbind()430 public boolean unbind() { 431 synchronized (mUpdateEngineCallbackLock) { 432 if (mUpdateEngineCallback == null) { 433 return true; 434 } 435 try { 436 boolean result = mUpdateEngine.unbind(mUpdateEngineCallback); 437 mUpdateEngineCallback = null; 438 return result; 439 } catch (RemoteException e) { 440 throw e.rethrowFromSystemServer(); 441 } 442 } 443 } 444 445 /** 446 * Verifies that a payload associated with the given payload metadata 447 * {@code payloadMetadataFilename} can be safely applied to ths device. 448 * Returns {@code true} if the update can successfully be applied and 449 * returns {@code false} otherwise. 450 * 451 * @param payloadMetadataFilename the location of the metadata without the 452 * {@code file://} prefix. 453 */ verifyPayloadMetadata(String payloadMetadataFilename)454 public boolean verifyPayloadMetadata(String payloadMetadataFilename) { 455 try { 456 return mUpdateEngine.verifyPayloadApplicable(payloadMetadataFilename); 457 } catch (RemoteException e) { 458 throw e.rethrowFromSystemServer(); 459 } 460 } 461 462 /** 463 * Return value of {@link #allocateSpace.} 464 */ 465 public static final class AllocateSpaceResult { 466 private @ErrorCode int mErrorCode = ErrorCodeConstants.SUCCESS; 467 private long mFreeSpaceRequired = 0; AllocateSpaceResult()468 private AllocateSpaceResult() {} 469 /** 470 * Error code. 471 * 472 * @return The following error codes: 473 * <ul> 474 * <li>{@link ErrorCodeConstants#SUCCESS} if space has been allocated 475 * successfully.</li> 476 * <li>{@link ErrorCodeConstants#NOT_ENOUGH_SPACE} if insufficient 477 * space.</li> 478 * <li>Other {@link ErrorCodeConstants} for other errors.</li> 479 * </ul> 480 */ 481 @ErrorCode getErrorCode()482 public int getErrorCode() { 483 return mErrorCode; 484 } 485 486 /** 487 * Estimated total space that needs to be available on the userdata partition to apply the 488 * payload (in bytes). 489 * 490 * <p> 491 * Note that in practice, more space needs to be made available before applying the payload 492 * to keep the device working. 493 * 494 * @return The following values: 495 * <ul> 496 * <li>zero if {@link #getErrorCode} returns {@link ErrorCodeConstants#SUCCESS}</li> 497 * <li>non-zero if {@link #getErrorCode} returns 498 * {@link ErrorCodeConstants#NOT_ENOUGH_SPACE}. 499 * Value is the estimated total space required on userdata partition.</li> 500 * </ul> 501 * @throws IllegalStateException if {@link #getErrorCode} is not one of the above. 502 * 503 */ getFreeSpaceRequired()504 public long getFreeSpaceRequired() { 505 if (mErrorCode == ErrorCodeConstants.SUCCESS) { 506 return 0; 507 } 508 if (mErrorCode == ErrorCodeConstants.NOT_ENOUGH_SPACE) { 509 return mFreeSpaceRequired; 510 } 511 throw new IllegalStateException(String.format( 512 "getFreeSpaceRequired() is not available when error code is %d", mErrorCode)); 513 } 514 } 515 516 /** 517 * Initialize partitions for a payload associated with the given payload 518 * metadata {@code payloadMetadataFilename} by preallocating required space. 519 * 520 * <p>This function should be called after payload has been verified after 521 * {@link #verifyPayloadMetadata}. This function does not verify whether 522 * the given payload is applicable or not. 523 * 524 * <p>Implementation of {@code allocateSpace} uses 525 * {@code headerKeyValuePairs} to determine whether space has been allocated 526 * for a different or same payload previously. If space has been allocated 527 * for a different payload before, space will be reallocated for the given 528 * payload. If space has been allocated for the same payload, no actions to 529 * storage devices are taken. 530 * 531 * <p>This function is synchronous and may take a non-trivial amount of 532 * time. Callers should call this function in a background thread. 533 * 534 * @param payloadMetadataFilename See {@link #verifyPayloadMetadata}. 535 * @param headerKeyValuePairs See {@link #applyPayload}. 536 * @return See {@link AllocateSpaceResult#getErrorCode} and 537 * {@link AllocateSpaceResult#getFreeSpaceRequired}. 538 */ 539 @WorkerThread 540 @NonNull allocateSpace( @onNull String payloadMetadataFilename, @NonNull String[] headerKeyValuePairs)541 public AllocateSpaceResult allocateSpace( 542 @NonNull String payloadMetadataFilename, 543 @NonNull String[] headerKeyValuePairs) { 544 AllocateSpaceResult result = new AllocateSpaceResult(); 545 try { 546 result.mFreeSpaceRequired = mUpdateEngine.allocateSpaceForPayload( 547 payloadMetadataFilename, 548 headerKeyValuePairs); 549 result.mErrorCode = result.mFreeSpaceRequired == 0 550 ? ErrorCodeConstants.SUCCESS 551 : ErrorCodeConstants.NOT_ENOUGH_SPACE; 552 return result; 553 } catch (ServiceSpecificException e) { 554 result.mErrorCode = e.errorCode; 555 result.mFreeSpaceRequired = 0; 556 return result; 557 } catch (RemoteException e) { 558 throw e.rethrowFromSystemServer(); 559 } 560 } 561 562 private static class CleanupAppliedPayloadCallback extends IUpdateEngineCallback.Stub { 563 private int mErrorCode = ErrorCodeConstants.ERROR; 564 private boolean mCompleted = false; 565 private Object mLock = new Object(); getResult()566 private int getResult() { 567 synchronized (mLock) { 568 while (!mCompleted) { 569 try { 570 mLock.wait(); 571 } catch (InterruptedException ex) { 572 // do nothing, just wait again. 573 } 574 } 575 return mErrorCode; 576 } 577 } 578 579 @Override onStatusUpdate(int status, float percent)580 public void onStatusUpdate(int status, float percent) { 581 } 582 583 @Override onPayloadApplicationComplete(int errorCode)584 public void onPayloadApplicationComplete(int errorCode) { 585 synchronized (mLock) { 586 mErrorCode = errorCode; 587 mCompleted = true; 588 mLock.notifyAll(); 589 } 590 } 591 } 592 593 /** 594 * Cleanup files used by the previous update and free up space after the 595 * device has been booted successfully into the new build. 596 * 597 * <p>In particular, this function waits until delta files for snapshots for 598 * Virtual A/B update are merged to OS partitions, then delete these delta 599 * files. 600 * 601 * <p>This function is synchronous and may take a non-trivial amount of 602 * time. Callers should call this function in a background thread. 603 * 604 * <p>This function does not delete payload binaries downloaded for a 605 * non-streaming OTA update. 606 * 607 * @return One of the following: 608 * <ul> 609 * <li>{@link ErrorCodeConstants#SUCCESS} if execution is successful.</li> 610 * <li>{@link ErrorCodeConstants#ERROR} if a transient error has occurred. 611 * The device should be able to recover after a reboot. The function should 612 * be retried after the reboot.</li> 613 * <li>{@link ErrorCodeConstants#DEVICE_CORRUPTED} if a permanent error is 614 * encountered. Device is corrupted, and future updates must not be applied. 615 * The device cannot recover without flashing and factory resets. 616 * </ul> 617 */ 618 @WorkerThread 619 @ErrorCode cleanupAppliedPayload()620 public int cleanupAppliedPayload() { 621 CleanupAppliedPayloadCallback callback = new CleanupAppliedPayloadCallback(); 622 try { 623 mUpdateEngine.cleanupSuccessfulUpdate(callback); 624 return callback.getResult(); 625 } catch (RemoteException e) { 626 throw e.rethrowFromSystemServer(); 627 } 628 } 629 } 630