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