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