1 /* 2 * Copyright (C) 2019 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 com.android.dynsystem; 18 19 import static android.os.AsyncTask.Status.FINISHED; 20 import static android.os.AsyncTask.Status.PENDING; 21 import static android.os.AsyncTask.Status.RUNNING; 22 import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE; 23 import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL; 24 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION; 25 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_INVALID_URL; 26 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_IO; 27 import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_CANCELLED; 28 import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_COMPLETED; 29 import static android.os.image.DynamicSystemClient.CAUSE_NOT_SPECIFIED; 30 import static android.os.image.DynamicSystemClient.STATUS_IN_PROGRESS; 31 import static android.os.image.DynamicSystemClient.STATUS_IN_USE; 32 import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED; 33 import static android.os.image.DynamicSystemClient.STATUS_READY; 34 35 import static com.android.dynsystem.InstallationAsyncTask.RESULT_CANCELLED; 36 import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_EXCEPTION; 37 import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_IO; 38 import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_UNSUPPORTED_FORMAT; 39 import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_UNSUPPORTED_URL; 40 import static com.android.dynsystem.InstallationAsyncTask.RESULT_OK; 41 42 import android.app.Notification; 43 import android.app.NotificationChannel; 44 import android.app.NotificationManager; 45 import android.app.PendingIntent; 46 import android.app.Service; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.net.http.HttpResponseCache; 50 import android.os.Bundle; 51 import android.os.Handler; 52 import android.os.IBinder; 53 import android.os.Message; 54 import android.os.Messenger; 55 import android.os.ParcelableException; 56 import android.os.PowerManager; 57 import android.os.RemoteException; 58 import android.os.image.DynamicSystemClient; 59 import android.os.image.DynamicSystemManager; 60 import android.text.TextUtils; 61 import android.util.EventLog; 62 import android.util.Log; 63 import android.widget.Toast; 64 65 import java.io.File; 66 import java.io.IOException; 67 import java.lang.ref.WeakReference; 68 import java.util.ArrayList; 69 70 /** 71 * This class is the service in charge of DynamicSystem installation. 72 * It also posts status to notification bar and wait for user's 73 * cancel and confirm commnands. 74 */ 75 public class DynamicSystemInstallationService extends Service 76 implements InstallationAsyncTask.ProgressListener { 77 78 private static final String TAG = "DynamicSystemInstallationService"; 79 80 // TODO (b/131866826): This is currently for test only. Will move this to System API. 81 static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED"; 82 static final String KEY_DSU_SLOT = "KEY_DSU_SLOT"; 83 static final String DEFAULT_DSU_SLOT = "dsu"; 84 static final String KEY_PUBKEY = "KEY_PUBKEY"; 85 86 // Default userdata partition size is 2GiB. 87 private static final long DEFAULT_USERDATA_SIZE = 2L << 30; 88 89 /* 90 * Intent actions 91 */ 92 private static final String ACTION_CANCEL_INSTALL = 93 "com.android.dynsystem.ACTION_CANCEL_INSTALL"; 94 private static final String ACTION_DISCARD_INSTALL = 95 "com.android.dynsystem.ACTION_DISCARD_INSTALL"; 96 private static final String ACTION_REBOOT_TO_DYN_SYSTEM = 97 "com.android.dynsystem.ACTION_REBOOT_TO_DYN_SYSTEM"; 98 private static final String ACTION_REBOOT_TO_NORMAL = 99 "com.android.dynsystem.ACTION_REBOOT_TO_NORMAL"; 100 101 /* 102 * For notification 103 */ 104 private static final String NOTIFICATION_CHANNEL_ID = "com.android.dynsystem"; 105 private static final int NOTIFICATION_ID = 1; 106 107 /* 108 * Event log tags 109 */ 110 private static final int EVENT_DSU_PROGRESS_UPDATE = 120000; 111 private static final int EVENT_DSU_INSTALL_COMPLETE = 120001; 112 private static final int EVENT_DSU_INSTALL_FAILED = 120002; 113 private static final int EVENT_DSU_INSTALL_INSUFFICIENT_SPACE = 120003; 114 logEventProgressUpdate( String partitionName, long installedBytes, long totalBytes, int partitionNumber, int totalPartitionNumber, int totalProgressPercentage)115 protected static void logEventProgressUpdate( 116 String partitionName, 117 long installedBytes, 118 long totalBytes, 119 int partitionNumber, 120 int totalPartitionNumber, 121 int totalProgressPercentage) { 122 EventLog.writeEvent( 123 EVENT_DSU_PROGRESS_UPDATE, 124 partitionName, 125 installedBytes, 126 totalBytes, 127 partitionNumber, 128 totalPartitionNumber, 129 totalProgressPercentage); 130 } 131 logEventComplete()132 protected static void logEventComplete() { 133 EventLog.writeEvent(EVENT_DSU_INSTALL_COMPLETE); 134 } 135 logEventFailed(String cause)136 protected static void logEventFailed(String cause) { 137 EventLog.writeEvent(EVENT_DSU_INSTALL_FAILED, cause); 138 } 139 logEventInsufficientSpace()140 protected static void logEventInsufficientSpace() { 141 EventLog.writeEvent(EVENT_DSU_INSTALL_INSUFFICIENT_SPACE); 142 } 143 144 /* 145 * IPC 146 */ 147 /** Keeps track of all current registered clients. */ 148 ArrayList<Messenger> mClients = new ArrayList<>(); 149 150 /** Handler of incoming messages from clients. */ 151 final Messenger mMessenger = new Messenger(new IncomingHandler(this)); 152 153 static class IncomingHandler extends Handler { 154 private final WeakReference<DynamicSystemInstallationService> mWeakService; 155 IncomingHandler(DynamicSystemInstallationService service)156 IncomingHandler(DynamicSystemInstallationService service) { 157 mWeakService = new WeakReference<>(service); 158 } 159 160 @Override handleMessage(Message msg)161 public void handleMessage(Message msg) { 162 DynamicSystemInstallationService service = mWeakService.get(); 163 164 if (service != null) { 165 service.handleMessage(msg); 166 } 167 } 168 } 169 170 private DynamicSystemManager mDynSystem; 171 private NotificationManager mNM; 172 173 // This is for testing only now 174 private boolean mEnableWhenCompleted; 175 176 private InstallationAsyncTask.Progress mInstallTaskProgress; 177 private InstallationAsyncTask mInstallTask; 178 179 180 @Override onCreate()181 public void onCreate() { 182 super.onCreate(); 183 184 prepareNotification(); 185 186 mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE); 187 188 // Install an HttpResponseCache in the application cache directory so we can cache 189 // gsi key revocation list. The http(s) protocol handler uses this cache transparently. 190 // The cache size is chosen heuristically. Since we don't have too much traffic right now, 191 // a moderate size of 1MiB should be enough. 192 try { 193 File httpCacheDir = new File(getCacheDir(), "httpCache"); 194 long httpCacheSize = 1 * 1024 * 1024; // 1 MiB 195 HttpResponseCache.install(httpCacheDir, httpCacheSize); 196 } catch (IOException e) { 197 Log.d(TAG, "HttpResponseCache.install() failed: " + e); 198 } 199 } 200 201 @Override onDestroy()202 public void onDestroy() { 203 HttpResponseCache cache = HttpResponseCache.getInstalled(); 204 if (cache != null) { 205 cache.flush(); 206 } 207 } 208 209 @Override onBind(Intent intent)210 public IBinder onBind(Intent intent) { 211 return mMessenger.getBinder(); 212 } 213 214 @Override onStartCommand(Intent intent, int flags, int startId)215 public int onStartCommand(Intent intent, int flags, int startId) { 216 String action = intent.getAction(); 217 218 Log.d(TAG, "onStartCommand(): action=" + action); 219 220 if (ACTION_START_INSTALL.equals(action)) { 221 executeInstallCommand(intent); 222 } else if (ACTION_CANCEL_INSTALL.equals(action)) { 223 executeCancelCommand(); 224 } else if (ACTION_DISCARD_INSTALL.equals(action)) { 225 executeDiscardCommand(); 226 } else if (ACTION_REBOOT_TO_DYN_SYSTEM.equals(action)) { 227 executeRebootToDynSystemCommand(); 228 } else if (ACTION_REBOOT_TO_NORMAL.equals(action)) { 229 executeRebootToNormalCommand(); 230 } else if (ACTION_NOTIFY_IF_IN_USE.equals(action)) { 231 executeNotifyIfInUseCommand(); 232 } 233 234 return Service.START_NOT_STICKY; 235 } 236 237 @Override onProgressUpdate(InstallationAsyncTask.Progress progress)238 public void onProgressUpdate(InstallationAsyncTask.Progress progress) { 239 logEventProgressUpdate( 240 progress.partitionName, 241 progress.installedBytes, 242 progress.totalBytes, 243 progress.partitionNumber, 244 progress.totalPartitionNumber, 245 progress.totalProgressPercentage); 246 247 mInstallTaskProgress = progress; 248 postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED, null); 249 } 250 251 @Override onResult(int result, Throwable detail)252 public void onResult(int result, Throwable detail) { 253 if (result == RESULT_OK) { 254 logEventComplete(); 255 postStatus(STATUS_READY, CAUSE_INSTALL_COMPLETED, null); 256 257 // For testing: enable DSU and restart the device when install completed 258 if (mEnableWhenCompleted) { 259 executeRebootToDynSystemCommand(); 260 } 261 return; 262 } 263 264 if (result == RESULT_CANCELLED) { 265 logEventFailed("Dynamic System installation task is canceled by the user."); 266 } else if (detail instanceof InstallationAsyncTask.InsufficientSpaceException) { 267 logEventInsufficientSpace(); 268 } else { 269 logEventFailed("error: " + detail); 270 } 271 272 boolean removeNotification = false; 273 switch (result) { 274 case RESULT_CANCELLED: 275 postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null); 276 removeNotification = true; 277 break; 278 279 case RESULT_ERROR_IO: 280 postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO, detail); 281 break; 282 283 case RESULT_ERROR_UNSUPPORTED_URL: 284 case RESULT_ERROR_UNSUPPORTED_FORMAT: 285 postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_INVALID_URL, detail); 286 break; 287 288 case RESULT_ERROR_EXCEPTION: 289 postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_EXCEPTION, detail); 290 break; 291 } 292 293 // if it's not successful, reset the task and stop self. 294 resetTaskAndStop(removeNotification); 295 } 296 executeInstallCommand(Intent intent)297 private void executeInstallCommand(Intent intent) { 298 if (!verifyRequest(intent)) { 299 Log.e(TAG, "Verification failed. Did you use VerificationActivity?"); 300 logEventFailed("VerificationActivity"); 301 return; 302 } 303 304 if (mInstallTask != null) { 305 Log.e(TAG, "There is already an installation task running"); 306 logEventFailed("There is already an ongoing installation task."); 307 return; 308 } 309 310 if (isInDynamicSystem()) { 311 Log.e(TAG, "We are already running in DynamicSystem"); 312 logEventFailed( 313 "Cannot start a Dynamic System installation task within a Dynamic System."); 314 return; 315 } 316 317 String url = intent.getDataString(); 318 long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0); 319 long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0); 320 mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false); 321 String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT); 322 String publicKey = intent.getStringExtra(KEY_PUBKEY); 323 324 if (userdataSize == 0) { 325 userdataSize = DEFAULT_USERDATA_SIZE; 326 } 327 328 if (TextUtils.isEmpty(dsuSlot)) { 329 dsuSlot = DEFAULT_DSU_SLOT; 330 } 331 // TODO: better constructor or builder 332 mInstallTask = 333 new InstallationAsyncTask( 334 url, dsuSlot, publicKey, systemSize, userdataSize, this, mDynSystem, this); 335 336 mInstallTask.execute(); 337 338 // start fore ground 339 startForeground(NOTIFICATION_ID, 340 buildNotification(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED)); 341 } 342 executeCancelCommand()343 private void executeCancelCommand() { 344 if (mInstallTask == null || mInstallTask.getStatus() != RUNNING) { 345 Log.e(TAG, "Cancel command triggered, but there is no task running"); 346 return; 347 } 348 349 if (mInstallTask.cancel(false)) { 350 // onResult() would call resetTaskAndStop() upon task completion. 351 Log.d(TAG, "Cancel request filed successfully"); 352 // Dismiss the notification as soon as possible as DynamicSystemManager.remove() may 353 // block. 354 stopForeground(STOP_FOREGROUND_REMOVE); 355 } else { 356 Log.e(TAG, "Trying to cancel installation while it's already completed."); 357 } 358 } 359 executeDiscardCommand()360 private void executeDiscardCommand() { 361 if (isInDynamicSystem()) { 362 Log.e(TAG, "We are now running in AOT, please reboot to normal system first"); 363 return; 364 } 365 366 if (!isDynamicSystemInstalled() && (getStatus() != STATUS_READY)) { 367 Log.e(TAG, "Trying to discard AOT while there is no complete installation"); 368 // Stop foreground state and dismiss stale notification. 369 resetTaskAndStop(true); 370 return; 371 } 372 373 Toast.makeText(this, 374 getString(R.string.toast_dynsystem_discarded), 375 Toast.LENGTH_LONG).show(); 376 377 postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null); 378 resetTaskAndStop(true); 379 380 mDynSystem.remove(); 381 } 382 executeRebootToDynSystemCommand()383 private void executeRebootToDynSystemCommand() { 384 boolean enabled = false; 385 386 if (mInstallTask != null && mInstallTask.isCompleted()) { 387 enabled = mInstallTask.commit(); 388 } else if (isDynamicSystemInstalled()) { 389 enabled = mDynSystem.setEnable(true, true); 390 } else { 391 Log.e(TAG, "Trying to reboot to AOT while there is no complete installation"); 392 return; 393 } 394 395 if (enabled) { 396 PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); 397 398 if (powerManager != null) { 399 powerManager.reboot("dynsystem"); 400 } 401 return; 402 } 403 404 Log.e(TAG, "Failed to enable DynamicSystem because of native runtime error."); 405 406 Toast.makeText(this, 407 getString(R.string.toast_failed_to_reboot_to_dynsystem), 408 Toast.LENGTH_LONG).show(); 409 410 postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_EXCEPTION, null); 411 resetTaskAndStop(); 412 mDynSystem.remove(); 413 } 414 executeRebootToNormalCommand()415 private void executeRebootToNormalCommand() { 416 if (!isInDynamicSystem()) { 417 Log.e(TAG, "It's already running in normal system."); 418 return; 419 } 420 421 if (!mDynSystem.setEnable(/* enable = */ false, /* oneShot = */ false)) { 422 Log.e(TAG, "Failed to disable DynamicSystem."); 423 424 // Dismiss status bar and show a toast. 425 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 426 Toast.makeText(this, 427 getString(R.string.toast_failed_to_disable_dynsystem), 428 Toast.LENGTH_LONG).show(); 429 return; 430 } 431 432 PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); 433 434 if (powerManager != null) { 435 powerManager.reboot(null); 436 } 437 } 438 executeNotifyIfInUseCommand()439 private void executeNotifyIfInUseCommand() { 440 switch (getStatus()) { 441 case STATUS_IN_USE: 442 startForeground(NOTIFICATION_ID, 443 buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); 444 break; 445 case STATUS_READY: 446 startForeground(NOTIFICATION_ID, 447 buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED)); 448 break; 449 case STATUS_IN_PROGRESS: 450 break; 451 case STATUS_NOT_STARTED: 452 default: 453 stopSelf(); 454 } 455 } 456 resetTaskAndStop()457 private void resetTaskAndStop() { 458 resetTaskAndStop(/* removeNotification= */ false); 459 } 460 resetTaskAndStop(boolean removeNotification)461 private void resetTaskAndStop(boolean removeNotification) { 462 mInstallTask = null; 463 stopForeground(removeNotification ? STOP_FOREGROUND_REMOVE : STOP_FOREGROUND_DETACH); 464 stopSelf(); 465 } 466 prepareNotification()467 private void prepareNotification() { 468 NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, 469 getString(R.string.notification_channel_name), 470 NotificationManager.IMPORTANCE_LOW); 471 472 mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 473 474 if (mNM != null) { 475 mNM.createNotificationChannel(chan); 476 } 477 } 478 createPendingIntent(String action)479 private PendingIntent createPendingIntent(String action) { 480 Intent intent = new Intent(this, DynamicSystemInstallationService.class); 481 intent.setAction(action); 482 return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); 483 } 484 buildNotification(int status, int cause)485 private Notification buildNotification(int status, int cause) { 486 return buildNotification(status, cause, null); 487 } 488 buildNotification(int status, int cause, Throwable detail)489 private Notification buildNotification(int status, int cause, Throwable detail) { 490 Notification.Builder builder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID) 491 .setSmallIcon(R.drawable.ic_system_update_googblue_24dp) 492 .setProgress(0, 0, false); 493 494 switch (status) { 495 case STATUS_IN_PROGRESS: 496 String msgInProgress = getString(R.string.notification_install_inprogress); 497 498 if (mInstallTaskProgress == null) { 499 builder.setContentText(msgInProgress); 500 } else { 501 if (mInstallTaskProgress.totalPartitionNumber > 0) { 502 builder.setContentText( 503 String.format( 504 "%s: %s partition [%d/%d]", 505 msgInProgress, 506 mInstallTaskProgress.partitionName, 507 mInstallTaskProgress.partitionNumber, 508 mInstallTaskProgress.totalPartitionNumber)); 509 510 // totalProgressPercentage is defined iff totalPartitionNumber is defined 511 builder.setProgress( 512 100, 513 mInstallTaskProgress.totalProgressPercentage, 514 /* indeterminate = */ false); 515 } else { 516 builder.setContentText( 517 String.format( 518 "%s: %s partition", 519 msgInProgress, mInstallTaskProgress.partitionName)); 520 521 int max = 1024; 522 int progress = 0; 523 524 int currentMax = max >> mInstallTaskProgress.partitionNumber; 525 progress = max - currentMax * 2; 526 527 long currentProgress = 528 (mInstallTaskProgress.installedBytes >> 20) 529 * currentMax 530 / Math.max(mInstallTaskProgress.totalBytes >> 20, 1); 531 532 progress += (int) currentProgress; 533 534 builder.setProgress(max, progress, false); 535 } 536 } 537 builder.addAction(new Notification.Action.Builder( 538 null, getString(R.string.notification_action_cancel), 539 createPendingIntent(ACTION_CANCEL_INSTALL)).build()); 540 541 break; 542 543 case STATUS_READY: 544 String msgCompleted = getString(R.string.notification_install_completed); 545 builder.setContentText(msgCompleted) 546 .setStyle(new Notification.BigTextStyle().bigText(msgCompleted)); 547 548 builder.addAction(new Notification.Action.Builder( 549 null, getString(R.string.notification_action_discard), 550 createPendingIntent(ACTION_DISCARD_INSTALL)).build()); 551 552 builder.addAction(new Notification.Action.Builder( 553 null, getString(R.string.notification_action_reboot_to_dynsystem), 554 createPendingIntent(ACTION_REBOOT_TO_DYN_SYSTEM)).build()); 555 556 break; 557 558 case STATUS_IN_USE: 559 String msgInUse = getString(R.string.notification_dynsystem_in_use); 560 builder.setContentText(msgInUse) 561 .setStyle(new Notification.BigTextStyle().bigText(msgInUse)); 562 563 builder.addAction(new Notification.Action.Builder( 564 null, getString(R.string.notification_action_reboot_to_origin), 565 createPendingIntent(ACTION_REBOOT_TO_NORMAL)).build()); 566 567 break; 568 569 case STATUS_NOT_STARTED: 570 if (cause != CAUSE_NOT_SPECIFIED && cause != CAUSE_INSTALL_CANCELLED) { 571 if (detail instanceof InstallationAsyncTask.ImageValidationException) { 572 builder.setContentText( 573 getString(R.string.notification_image_validation_failed)); 574 } else { 575 builder.setContentText(getString(R.string.notification_install_failed)); 576 } 577 } else { 578 // no need to notify the user if the task is not started, or cancelled. 579 } 580 break; 581 582 default: 583 throw new IllegalStateException("status is invalid"); 584 } 585 586 return builder.build(); 587 } 588 verifyRequest(Intent intent)589 private boolean verifyRequest(Intent intent) { 590 String url = intent.getDataString(); 591 592 return VerificationActivity.isVerified(url); 593 } 594 postStatus(int status, int cause, Throwable detail)595 private void postStatus(int status, int cause, Throwable detail) { 596 String statusString; 597 String causeString; 598 boolean notifyOnNotificationBar = true; 599 600 switch (status) { 601 case STATUS_NOT_STARTED: 602 statusString = "NOT_STARTED"; 603 break; 604 case STATUS_IN_PROGRESS: 605 statusString = "IN_PROGRESS"; 606 break; 607 case STATUS_READY: 608 statusString = "READY"; 609 break; 610 case STATUS_IN_USE: 611 statusString = "IN_USE"; 612 break; 613 default: 614 statusString = "UNKNOWN"; 615 break; 616 } 617 618 switch (cause) { 619 case CAUSE_INSTALL_COMPLETED: 620 causeString = "INSTALL_COMPLETED"; 621 break; 622 case CAUSE_INSTALL_CANCELLED: 623 causeString = "INSTALL_CANCELLED"; 624 notifyOnNotificationBar = false; 625 break; 626 case CAUSE_ERROR_IO: 627 causeString = "ERROR_IO"; 628 break; 629 case CAUSE_ERROR_INVALID_URL: 630 causeString = "ERROR_INVALID_URL"; 631 break; 632 case CAUSE_ERROR_EXCEPTION: 633 causeString = "ERROR_EXCEPTION"; 634 break; 635 default: 636 causeString = "CAUSE_NOT_SPECIFIED"; 637 break; 638 } 639 640 StringBuilder msg = new StringBuilder(); 641 msg.append("status: " + statusString + ", cause: " + causeString); 642 if (status == STATUS_IN_PROGRESS && mInstallTaskProgress != null) { 643 msg.append( 644 String.format( 645 ", partition name: %s, progress: %d/%d, total_progress: %d%%", 646 mInstallTaskProgress.partitionName, 647 mInstallTaskProgress.installedBytes, 648 mInstallTaskProgress.totalBytes, 649 mInstallTaskProgress.totalProgressPercentage)); 650 } 651 if (detail != null) { 652 msg.append(", detail: " + detail); 653 } 654 Log.d(TAG, msg.toString()); 655 656 if (notifyOnNotificationBar) { 657 mNM.notify(NOTIFICATION_ID, buildNotification(status, cause, detail)); 658 } 659 660 for (int i = mClients.size() - 1; i >= 0; i--) { 661 try { 662 notifyOneClient(mClients.get(i), status, cause, detail); 663 } catch (RemoteException e) { 664 mClients.remove(i); 665 } 666 } 667 } 668 notifyOneClient(Messenger client, int status, int cause, Throwable detail)669 private void notifyOneClient(Messenger client, int status, int cause, Throwable detail) 670 throws RemoteException { 671 Bundle bundle = new Bundle(); 672 673 // TODO: send more info to the clients 674 if (mInstallTaskProgress != null) { 675 bundle.putLong( 676 DynamicSystemClient.KEY_INSTALLED_SIZE, mInstallTaskProgress.installedBytes); 677 } 678 679 if (detail != null) { 680 bundle.putSerializable(DynamicSystemClient.KEY_EXCEPTION_DETAIL, 681 new ParcelableException(detail)); 682 } 683 684 client.send(Message.obtain(null, 685 DynamicSystemClient.MSG_POST_STATUS, status, cause, bundle)); 686 } 687 getStatus()688 private int getStatus() { 689 if (isInDynamicSystem()) { 690 return STATUS_IN_USE; 691 } else if (isDynamicSystemInstalled()) { 692 return STATUS_READY; 693 } else if (mInstallTask == null) { 694 return STATUS_NOT_STARTED; 695 } 696 697 switch (mInstallTask.getStatus()) { 698 case PENDING: 699 return STATUS_NOT_STARTED; 700 701 case RUNNING: 702 return STATUS_IN_PROGRESS; 703 704 case FINISHED: 705 if (mInstallTask.isCompleted()) { 706 return STATUS_READY; 707 } else { 708 throw new IllegalStateException("A failed InstallationTask is not reset"); 709 } 710 711 default: 712 return STATUS_NOT_STARTED; 713 } 714 } 715 isInDynamicSystem()716 private boolean isInDynamicSystem() { 717 return mDynSystem.isInUse(); 718 } 719 isDynamicSystemInstalled()720 private boolean isDynamicSystemInstalled() { 721 return mDynSystem.isInstalled(); 722 } 723 handleMessage(Message msg)724 void handleMessage(Message msg) { 725 switch (msg.what) { 726 case DynamicSystemClient.MSG_REGISTER_LISTENER: 727 try { 728 Messenger client = msg.replyTo; 729 730 int status = getStatus(); 731 732 // tell just registered client my status, but do not specify cause 733 notifyOneClient(client, status, CAUSE_NOT_SPECIFIED, null); 734 735 mClients.add(client); 736 } catch (RemoteException e) { 737 // do nothing if we cannot send update to the client 738 e.printStackTrace(); 739 } 740 741 break; 742 case DynamicSystemClient.MSG_UNREGISTER_LISTENER: 743 mClients.remove(msg.replyTo); 744 break; 745 default: 746 // do nothing 747 } 748 } 749 } 750