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