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