Am.java revision e2515eebf42c763c0a2d9f873a153711778cfc17
1/*
2**
3** Copyright 2007, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18
19package com.android.commands.am;
20
21import android.app.ActivityManagerNative;
22import android.app.IActivityController;
23import android.app.IActivityManager;
24import android.app.IInstrumentationWatcher;
25import android.app.Instrumentation;
26import android.content.ComponentName;
27import android.content.IIntentReceiver;
28import android.content.Intent;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.ParcelFileDescriptor;
32import android.os.RemoteException;
33import android.os.ServiceManager;
34import android.util.AndroidException;
35import android.view.IWindowManager;
36
37import java.io.BufferedReader;
38import java.io.File;
39import java.io.FileNotFoundException;
40import java.io.IOException;
41import java.io.InputStreamReader;
42import java.io.PrintStream;
43import java.net.URISyntaxException;
44import java.util.Iterator;
45import java.util.Set;
46
47public class Am {
48
49    private IActivityManager mAm;
50    private String[] mArgs;
51    private int mNextArg;
52    private String mCurArgData;
53
54    private boolean mDebugOption = false;
55    private boolean mWaitOption = false;
56
57    // These are magic strings understood by the Eclipse plugin.
58    private static final String FATAL_ERROR_CODE = "Error type 1";
59    private static final String NO_SYSTEM_ERROR_CODE = "Error type 2";
60    private static final String NO_CLASS_ERROR_CODE = "Error type 3";
61
62    /**
63     * Command-line entry point.
64     *
65     * @param args The command-line arguments
66     */
67    public static void main(String[] args) {
68        try {
69            (new Am()).run(args);
70        } catch (IllegalArgumentException e) {
71            showUsage();
72            System.err.println("Error: " + e.getMessage());
73        } catch (Exception e) {
74            System.err.println(e.toString());
75            System.exit(1);
76        }
77    }
78
79    private void run(String[] args) throws Exception {
80        if (args.length < 1) {
81            showUsage();
82            return;
83        }
84
85        mAm = ActivityManagerNative.getDefault();
86        if (mAm == null) {
87            System.err.println(NO_SYSTEM_ERROR_CODE);
88            throw new AndroidException("Can't connect to activity manager; is the system running?");
89        }
90
91        mArgs = args;
92        String op = args[0];
93        mNextArg = 1;
94
95        if (op.equals("start")) {
96            runStart();
97        } else if (op.equals("startservice")) {
98            runStartService();
99        } else if (op.equals("instrument")) {
100            runInstrument();
101        } else if (op.equals("broadcast")) {
102            sendBroadcast();
103        } else if (op.equals("profile")) {
104            runProfile();
105        } else if (op.equals("dumpheap")) {
106            runDumpHeap();
107        } else if (op.equals("monitor")) {
108            runMonitor();
109        } else if (op.equals("screen-compat")) {
110            runScreenCompat();
111        } else {
112            throw new IllegalArgumentException("Unknown command: " + op);
113        }
114    }
115
116    private Intent makeIntent() throws URISyntaxException {
117        Intent intent = new Intent();
118        boolean hasIntentInfo = false;
119
120        mDebugOption = false;
121        mWaitOption = false;
122        Uri data = null;
123        String type = null;
124
125        String opt;
126        while ((opt=nextOption()) != null) {
127            if (opt.equals("-a")) {
128                intent.setAction(nextArgRequired());
129                hasIntentInfo = true;
130            } else if (opt.equals("-d")) {
131                data = Uri.parse(nextArgRequired());
132                hasIntentInfo = true;
133            } else if (opt.equals("-t")) {
134                type = nextArgRequired();
135                hasIntentInfo = true;
136            } else if (opt.equals("-c")) {
137                intent.addCategory(nextArgRequired());
138                hasIntentInfo = true;
139            } else if (opt.equals("-e") || opt.equals("--es")) {
140                String key = nextArgRequired();
141                String value = nextArgRequired();
142                intent.putExtra(key, value);
143                hasIntentInfo = true;
144            } else if (opt.equals("--esn")) {
145                String key = nextArgRequired();
146                intent.putExtra(key, (String) null);
147                hasIntentInfo = true;
148            } else if (opt.equals("--ei")) {
149                String key = nextArgRequired();
150                String value = nextArgRequired();
151                intent.putExtra(key, Integer.valueOf(value));
152                hasIntentInfo = true;
153            } else if (opt.equals("--eia")) {
154                String key = nextArgRequired();
155                String value = nextArgRequired();
156                String[] strings = value.split(",");
157                int[] list = new int[strings.length];
158                for (int i = 0; i < strings.length; i++) {
159                    list[i] = Integer.valueOf(strings[i]);
160                }
161                intent.putExtra(key, list);
162                hasIntentInfo = true;
163            } else if (opt.equals("--el")) {
164                String key = nextArgRequired();
165                String value = nextArgRequired();
166                intent.putExtra(key, Long.valueOf(value));
167                hasIntentInfo = true;
168            } else if (opt.equals("--ela")) {
169                String key = nextArgRequired();
170                String value = nextArgRequired();
171                String[] strings = value.split(",");
172                long[] list = new long[strings.length];
173                for (int i = 0; i < strings.length; i++) {
174                    list[i] = Long.valueOf(strings[i]);
175                }
176                intent.putExtra(key, list);
177                hasIntentInfo = true;
178            } else if (opt.equals("--ez")) {
179                String key = nextArgRequired();
180                String value = nextArgRequired();
181                intent.putExtra(key, Boolean.valueOf(value));
182                hasIntentInfo = true;
183            } else if (opt.equals("-n")) {
184                String str = nextArgRequired();
185                ComponentName cn = ComponentName.unflattenFromString(str);
186                if (cn == null) throw new IllegalArgumentException("Bad component name: " + str);
187                intent.setComponent(cn);
188                hasIntentInfo = true;
189            } else if (opt.equals("-f")) {
190                String str = nextArgRequired();
191                intent.setFlags(Integer.decode(str).intValue());
192            } else if (opt.equals("--grant-read-uri-permission")) {
193                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
194            } else if (opt.equals("--grant-write-uri-permission")) {
195                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
196            } else if (opt.equals("--debug-log-resolution")) {
197                intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
198            } else if (opt.equals("--activity-brought-to-front")) {
199                intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
200            } else if (opt.equals("--activity-clear-top")) {
201                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
202            } else if (opt.equals("--activity-clear-when-task-reset")) {
203                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
204            } else if (opt.equals("--activity-exclude-from-recents")) {
205                intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
206            } else if (opt.equals("--activity-launched-from-history")) {
207                intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
208            } else if (opt.equals("--activity-multiple-task")) {
209                intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
210            } else if (opt.equals("--activity-no-animation")) {
211                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
212            } else if (opt.equals("--activity-no-history")) {
213                intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
214            } else if (opt.equals("--activity-no-user-action")) {
215                intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
216            } else if (opt.equals("--activity-previous-is-top")) {
217                intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
218            } else if (opt.equals("--activity-reorder-to-front")) {
219                intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
220            } else if (opt.equals("--activity-reset-task-if-needed")) {
221                intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
222            } else if (opt.equals("--activity-single-top")) {
223                intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
224            } else if (opt.equals("--receiver-registered-only")) {
225                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
226            } else if (opt.equals("--receiver-replace-pending")) {
227                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
228            } else if (opt.equals("-D")) {
229                mDebugOption = true;
230            } else if (opt.equals("-W")) {
231                mWaitOption = true;
232            } else {
233                System.err.println("Error: Unknown option: " + opt);
234                showUsage();
235                return null;
236            }
237        }
238        intent.setDataAndType(data, type);
239
240        String uri = nextArg();
241        if (uri != null) {
242            Intent oldIntent = intent;
243            intent = Intent.parseUri(uri, 0);
244            if (oldIntent.getAction() != null) {
245                intent.setAction(oldIntent.getAction());
246            }
247            if (oldIntent.getData() != null || oldIntent.getType() != null) {
248                intent.setDataAndType(oldIntent.getData(), oldIntent.getType());
249            }
250            Set cats = oldIntent.getCategories();
251            if (cats != null) {
252                Iterator it = cats.iterator();
253                while (it.hasNext()) {
254                    intent.addCategory((String)it.next());
255                }
256            }
257            hasIntentInfo = true;
258        }
259
260        if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
261        return intent;
262    }
263
264    private void runStartService() throws Exception {
265        Intent intent = makeIntent();
266        System.out.println("Starting service: " + intent);
267        ComponentName cn = mAm.startService(null, intent, intent.getType());
268        if (cn == null) {
269            System.err.println("Error: Not found; no service started.");
270        }
271    }
272
273    private void runStart() throws Exception {
274        Intent intent = makeIntent();
275        System.out.println("Starting: " + intent);
276        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
277        // XXX should do something to determine the MIME type.
278        IActivityManager.WaitResult result = null;
279        int res;
280        if (mWaitOption) {
281            result = mAm.startActivityAndWait(null, intent, intent.getType(),
282                        null, 0, null, null, 0, false, mDebugOption);
283            res = result.result;
284        } else {
285            res = mAm.startActivity(null, intent, intent.getType(),
286                    null, 0, null, null, 0, false, mDebugOption);
287        }
288        PrintStream out = mWaitOption ? System.out : System.err;
289        boolean launched = false;
290        switch (res) {
291            case IActivityManager.START_SUCCESS:
292                launched = true;
293                break;
294            case IActivityManager.START_SWITCHES_CANCELED:
295                launched = true;
296                out.println(
297                        "Warning: Activity not started because the "
298                        + " current activity is being kept for the user.");
299                break;
300            case IActivityManager.START_DELIVERED_TO_TOP:
301                launched = true;
302                out.println(
303                        "Warning: Activity not started, intent has "
304                        + "been delivered to currently running "
305                        + "top-most instance.");
306                break;
307            case IActivityManager.START_RETURN_INTENT_TO_CALLER:
308                launched = true;
309                out.println(
310                        "Warning: Activity not started because intent "
311                        + "should be handled by the caller");
312                break;
313            case IActivityManager.START_TASK_TO_FRONT:
314                launched = true;
315                out.println(
316                        "Warning: Activity not started, its current "
317                        + "task has been brought to the front");
318                break;
319            case IActivityManager.START_INTENT_NOT_RESOLVED:
320                out.println(
321                        "Error: Activity not started, unable to "
322                        + "resolve " + intent.toString());
323                break;
324            case IActivityManager.START_CLASS_NOT_FOUND:
325                out.println(NO_CLASS_ERROR_CODE);
326                out.println("Error: Activity class " +
327                        intent.getComponent().toShortString()
328                        + " does not exist.");
329                break;
330            case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
331                out.println(
332                        "Error: Activity not started, you requested to "
333                        + "both forward and receive its result");
334                break;
335            case IActivityManager.START_PERMISSION_DENIED:
336                out.println(
337                        "Error: Activity not started, you do not "
338                        + "have permission to access it.");
339                break;
340            default:
341                out.println(
342                        "Error: Activity not started, unknown error code " + res);
343                break;
344        }
345        if (mWaitOption && launched) {
346            if (result == null) {
347                result = new IActivityManager.WaitResult();
348                result.who = intent.getComponent();
349            }
350            System.out.println("Status: " + (result.timeout ? "timeout" : "ok"));
351            if (result.who != null) {
352                System.out.println("Activity: " + result.who.flattenToShortString());
353            }
354            if (result.thisTime >= 0) {
355                System.out.println("ThisTime: " + result.thisTime);
356            }
357            if (result.totalTime >= 0) {
358                System.out.println("TotalTime: " + result.totalTime);
359            }
360            System.out.println("Complete");
361        }
362    }
363
364    private void sendBroadcast() throws Exception {
365        Intent intent = makeIntent();
366        IntentReceiver receiver = new IntentReceiver();
367        System.out.println("Broadcasting: " + intent);
368        mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null, true, false);
369        receiver.waitForFinish();
370    }
371
372    private void runInstrument() throws Exception {
373        String profileFile = null;
374        boolean wait = false;
375        boolean rawMode = false;
376        boolean no_window_animation = false;
377        Bundle args = new Bundle();
378        String argKey = null, argValue = null;
379        IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
380
381        String opt;
382        while ((opt=nextOption()) != null) {
383            if (opt.equals("-p")) {
384                profileFile = nextArgRequired();
385            } else if (opt.equals("-w")) {
386                wait = true;
387            } else if (opt.equals("-r")) {
388                rawMode = true;
389            } else if (opt.equals("-e")) {
390                argKey = nextArgRequired();
391                argValue = nextArgRequired();
392                args.putString(argKey, argValue);
393            } else if (opt.equals("--no_window_animation")) {
394                no_window_animation = true;
395            } else {
396                System.err.println("Error: Unknown option: " + opt);
397                showUsage();
398                return;
399            }
400        }
401
402        String cnArg = nextArgRequired();
403        ComponentName cn = ComponentName.unflattenFromString(cnArg);
404        if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
405
406        InstrumentationWatcher watcher = null;
407        if (wait) {
408            watcher = new InstrumentationWatcher();
409            watcher.setRawOutput(rawMode);
410        }
411        float[] oldAnims = null;
412        if (no_window_animation) {
413            oldAnims = wm.getAnimationScales();
414            wm.setAnimationScale(0, 0.0f);
415            wm.setAnimationScale(1, 0.0f);
416        }
417
418        if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher)) {
419            throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
420        }
421
422        if (watcher != null) {
423            if (!watcher.waitForFinish()) {
424                System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");
425            }
426        }
427
428        if (oldAnims != null) {
429            wm.setAnimationScales(oldAnims);
430        }
431    }
432
433    private void runProfile() throws Exception {
434        String profileFile = null;
435        boolean start = false;
436        String process = nextArgRequired();
437        ParcelFileDescriptor fd = null;
438
439        String cmd = nextArgRequired();
440        if ("start".equals(cmd)) {
441            start = true;
442            profileFile = nextArgRequired();
443            try {
444                fd = ParcelFileDescriptor.open(
445                        new File(profileFile),
446                        ParcelFileDescriptor.MODE_CREATE |
447                        ParcelFileDescriptor.MODE_TRUNCATE |
448                        ParcelFileDescriptor.MODE_READ_WRITE);
449            } catch (FileNotFoundException e) {
450                System.err.println("Error: Unable to open file: " + profileFile);
451                return;
452            }
453        } else if (!"stop".equals(cmd)) {
454            throw new IllegalArgumentException("Profile command " + cmd + " not valid");
455        }
456
457        if (!mAm.profileControl(process, start, profileFile, fd)) {
458            throw new AndroidException("PROFILE FAILED on process " + process);
459        }
460    }
461
462    private void runDumpHeap() throws Exception {
463        boolean managed = !"-n".equals(nextOption());
464        String process = nextArgRequired();
465        String heapFile = nextArgRequired();
466        ParcelFileDescriptor fd = null;
467
468        try {
469            fd = ParcelFileDescriptor.open(
470                    new File(heapFile),
471                    ParcelFileDescriptor.MODE_CREATE |
472                    ParcelFileDescriptor.MODE_TRUNCATE |
473                    ParcelFileDescriptor.MODE_READ_WRITE);
474        } catch (FileNotFoundException e) {
475            System.err.println("Error: Unable to open file: " + heapFile);
476            return;
477        }
478
479        if (!mAm.dumpHeap(process, managed, heapFile, fd)) {
480            throw new AndroidException("HEAP DUMP FAILED on process " + process);
481        }
482    }
483
484    class MyActivityController extends IActivityController.Stub {
485        final String mGdbPort;
486
487        static final int STATE_NORMAL = 0;
488        static final int STATE_CRASHED = 1;
489        static final int STATE_EARLY_ANR = 2;
490        static final int STATE_ANR = 3;
491
492        int mState;
493
494        static final int RESULT_DEFAULT = 0;
495
496        static final int RESULT_CRASH_DIALOG = 0;
497        static final int RESULT_CRASH_KILL = 1;
498
499        static final int RESULT_EARLY_ANR_CONTINUE = 0;
500        static final int RESULT_EARLY_ANR_KILL = 1;
501
502        static final int RESULT_ANR_DIALOG = 0;
503        static final int RESULT_ANR_KILL = 1;
504        static final int RESULT_ANR_WAIT = 1;
505
506        int mResult;
507
508        Process mGdbProcess;
509        Thread mGdbThread;
510        boolean mGotGdbPrint;
511
512        MyActivityController(String gdbPort) {
513            mGdbPort = gdbPort;
514        }
515
516        @Override
517        public boolean activityResuming(String pkg) throws RemoteException {
518            synchronized (this) {
519                System.out.println("** Activity resuming: " + pkg);
520            }
521            return true;
522        }
523
524        @Override
525        public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
526            synchronized (this) {
527                System.out.println("** Activity starting: " + pkg);
528            }
529            return true;
530        }
531
532        @Override
533        public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
534                long timeMillis, String stackTrace) throws RemoteException {
535            synchronized (this) {
536                System.out.println("** ERROR: PROCESS CRASHED");
537                System.out.println("processName: " + processName);
538                System.out.println("processPid: " + pid);
539                System.out.println("shortMsg: " + shortMsg);
540                System.out.println("longMsg: " + longMsg);
541                System.out.println("timeMillis: " + timeMillis);
542                System.out.println("stack:");
543                System.out.print(stackTrace);
544                System.out.println("#");
545                int result = waitControllerLocked(pid, STATE_CRASHED);
546                return result == RESULT_CRASH_KILL ? false : true;
547            }
548        }
549
550        @Override
551        public int appEarlyNotResponding(String processName, int pid, String annotation)
552                throws RemoteException {
553            synchronized (this) {
554                System.out.println("** ERROR: EARLY PROCESS NOT RESPONDING");
555                System.out.println("processName: " + processName);
556                System.out.println("processPid: " + pid);
557                System.out.println("annotation: " + annotation);
558                int result = waitControllerLocked(pid, STATE_EARLY_ANR);
559                if (result == RESULT_EARLY_ANR_KILL) return -1;
560                return 0;
561            }
562        }
563
564        @Override
565        public int appNotResponding(String processName, int pid, String processStats)
566                throws RemoteException {
567            synchronized (this) {
568                System.out.println("** ERROR: PROCESS NOT RESPONDING");
569                System.out.println("processName: " + processName);
570                System.out.println("processPid: " + pid);
571                System.out.println("processStats:");
572                System.out.print(processStats);
573                System.out.println("#");
574                int result = waitControllerLocked(pid, STATE_ANR);
575                if (result == RESULT_ANR_KILL) return -1;
576                if (result == RESULT_ANR_WAIT) return 1;
577                return 0;
578            }
579        }
580
581        void killGdbLocked() {
582            mGotGdbPrint = false;
583            if (mGdbProcess != null) {
584                System.out.println("Stopping gdbserver");
585                mGdbProcess.destroy();
586                mGdbProcess = null;
587            }
588            if (mGdbThread != null) {
589                mGdbThread.interrupt();
590                mGdbThread = null;
591            }
592        }
593
594        int waitControllerLocked(int pid, int state) {
595            if (mGdbPort != null) {
596                killGdbLocked();
597
598                try {
599                    System.out.println("Starting gdbserver on port " + mGdbPort);
600                    System.out.println("Do the following:");
601                    System.out.println("  adb forward tcp:" + mGdbPort + " tcp:" + mGdbPort);
602                    System.out.println("  gdbclient app_process :" + mGdbPort);
603
604                    mGdbProcess = Runtime.getRuntime().exec(new String[] {
605                            "gdbserver", ":" + mGdbPort, "--attach", Integer.toString(pid)
606                    });
607                    final InputStreamReader converter = new InputStreamReader(
608                            mGdbProcess.getInputStream());
609                    mGdbThread = new Thread() {
610                        @Override
611                        public void run() {
612                            BufferedReader in = new BufferedReader(converter);
613                            String line;
614                            int count = 0;
615                            while (true) {
616                                synchronized (MyActivityController.this) {
617                                    if (mGdbThread == null) {
618                                        return;
619                                    }
620                                    if (count == 2) {
621                                        mGotGdbPrint = true;
622                                        MyActivityController.this.notifyAll();
623                                    }
624                                }
625                                try {
626                                    line = in.readLine();
627                                    if (line == null) {
628                                        return;
629                                    }
630                                    System.out.println("GDB: " + line);
631                                    count++;
632                                } catch (IOException e) {
633                                    return;
634                                }
635                            }
636                        }
637                    };
638                    mGdbThread.start();
639
640                    // Stupid waiting for .5s.  Doesn't matter if we end early.
641                    try {
642                        this.wait(500);
643                    } catch (InterruptedException e) {
644                    }
645
646                } catch (IOException e) {
647                    System.err.println("Failure starting gdbserver: " + e);
648                    killGdbLocked();
649                }
650            }
651            mState = state;
652            System.out.println("");
653            printMessageForState();
654
655            while (mState != STATE_NORMAL) {
656                try {
657                    wait();
658                } catch (InterruptedException e) {
659                }
660            }
661
662            killGdbLocked();
663
664            return mResult;
665        }
666
667        void resumeController(int result) {
668            synchronized (this) {
669                mState = STATE_NORMAL;
670                mResult = result;
671                notifyAll();
672            }
673        }
674
675        void printMessageForState() {
676            switch (mState) {
677                case STATE_NORMAL:
678                    System.out.println("Monitoring activity manager...  available commands:");
679                    break;
680                case STATE_CRASHED:
681                    System.out.println("Waiting after crash...  available commands:");
682                    System.out.println("(c)ontinue: show crash dialog");
683                    System.out.println("(k)ill: immediately kill app");
684                    break;
685                case STATE_EARLY_ANR:
686                    System.out.println("Waiting after early ANR...  available commands:");
687                    System.out.println("(c)ontinue: standard ANR processing");
688                    System.out.println("(k)ill: immediately kill app");
689                    break;
690                case STATE_ANR:
691                    System.out.println("Waiting after ANR...  available commands:");
692                    System.out.println("(c)ontinue: show ANR dialog");
693                    System.out.println("(k)ill: immediately kill app");
694                    System.out.println("(w)ait: wait some more");
695                    break;
696            }
697            System.out.println("(q)uit: finish monitoring");
698        }
699
700        void run() throws RemoteException {
701            try {
702                printMessageForState();
703
704                mAm.setActivityController(this);
705                mState = STATE_NORMAL;
706
707                InputStreamReader converter = new InputStreamReader(System.in);
708                BufferedReader in = new BufferedReader(converter);
709                String line;
710
711                while ((line = in.readLine()) != null) {
712                    boolean addNewline = true;
713                    if (line.length() <= 0) {
714                        addNewline = false;
715                    } else if ("q".equals(line) || "quit".equals(line)) {
716                        resumeController(RESULT_DEFAULT);
717                        break;
718                    } else if (mState == STATE_CRASHED) {
719                        if ("c".equals(line) || "continue".equals(line)) {
720                            resumeController(RESULT_CRASH_DIALOG);
721                        } else if ("k".equals(line) || "kill".equals(line)) {
722                            resumeController(RESULT_CRASH_KILL);
723                        } else {
724                            System.out.println("Invalid command: " + line);
725                        }
726                    } else if (mState == STATE_ANR) {
727                        if ("c".equals(line) || "continue".equals(line)) {
728                            resumeController(RESULT_ANR_DIALOG);
729                        } else if ("k".equals(line) || "kill".equals(line)) {
730                            resumeController(RESULT_ANR_KILL);
731                        } else if ("w".equals(line) || "wait".equals(line)) {
732                            resumeController(RESULT_ANR_WAIT);
733                        } else {
734                            System.out.println("Invalid command: " + line);
735                        }
736                    } else if (mState == STATE_EARLY_ANR) {
737                        if ("c".equals(line) || "continue".equals(line)) {
738                            resumeController(RESULT_EARLY_ANR_CONTINUE);
739                        } else if ("k".equals(line) || "kill".equals(line)) {
740                            resumeController(RESULT_EARLY_ANR_KILL);
741                        } else {
742                            System.out.println("Invalid command: " + line);
743                        }
744                    } else {
745                        System.out.println("Invalid command: " + line);
746                    }
747
748                    synchronized (this) {
749                        if (addNewline) {
750                            System.out.println("");
751                        }
752                        printMessageForState();
753                    }
754                }
755
756            } catch (IOException e) {
757                e.printStackTrace();
758            } finally {
759                mAm.setActivityController(null);
760            }
761        }
762    }
763
764    private void runMonitor() throws Exception {
765        String opt;
766        String gdbPort = null;
767        while ((opt=nextOption()) != null) {
768            if (opt.equals("--gdb")) {
769                gdbPort = nextArgRequired();
770            } else {
771                System.err.println("Error: Unknown option: " + opt);
772                showUsage();
773                return;
774            }
775        }
776
777        MyActivityController controller = new MyActivityController(gdbPort);
778        controller.run();
779    }
780
781    private void runScreenCompat() throws Exception {
782        String mode = nextArgRequired();
783        boolean enabled;
784        if ("on".equals(mode)) {
785            enabled = true;
786        } else if ("off".equals(mode)) {
787            enabled = false;
788        } else {
789            System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode);
790            showUsage();
791            return;
792        }
793
794        String packageName = nextArgRequired();
795        do {
796            try {
797                mAm.setPackageScreenCompatMode(packageName, enabled);
798            } catch (RemoteException e) {
799            }
800            packageName = nextArg();
801        } while (packageName != null);
802    }
803
804    private class IntentReceiver extends IIntentReceiver.Stub {
805        private boolean mFinished = false;
806
807        public synchronized void performReceive(
808                Intent intent, int rc, String data, Bundle ext, boolean ord,
809                boolean sticky) {
810            String line = "Broadcast completed: result=" + rc;
811            if (data != null) line = line + ", data=\"" + data + "\"";
812            if (ext != null) line = line + ", extras: " + ext;
813            System.out.println(line);
814            mFinished = true;
815            notifyAll();
816        }
817
818        public synchronized void waitForFinish() {
819            try {
820                while (!mFinished) wait();
821            } catch (InterruptedException e) {
822                throw new IllegalStateException(e);
823            }
824        }
825    }
826
827    private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
828        private boolean mFinished = false;
829        private boolean mRawMode = false;
830
831        /**
832         * Set or reset "raw mode".  In "raw mode", all bundles are dumped.  In "pretty mode",
833         * if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
834         * @param rawMode true for raw mode, false for pretty mode.
835         */
836        public void setRawOutput(boolean rawMode) {
837            mRawMode = rawMode;
838        }
839
840        public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
841            synchronized (this) {
842                // pretty printer mode?
843                String pretty = null;
844                if (!mRawMode && results != null) {
845                    pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
846                }
847                if (pretty != null) {
848                    System.out.print(pretty);
849                } else {
850                    if (results != null) {
851                        for (String key : results.keySet()) {
852                            System.out.println(
853                                    "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
854                        }
855                    }
856                    System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
857                }
858                notifyAll();
859            }
860        }
861
862        public void instrumentationFinished(ComponentName name, int resultCode,
863                Bundle results) {
864            synchronized (this) {
865                // pretty printer mode?
866                String pretty = null;
867                if (!mRawMode && results != null) {
868                    pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
869                }
870                if (pretty != null) {
871                    System.out.println(pretty);
872                } else {
873                    if (results != null) {
874                        for (String key : results.keySet()) {
875                            System.out.println(
876                                    "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
877                        }
878                    }
879                    System.out.println("INSTRUMENTATION_CODE: " + resultCode);
880                }
881                mFinished = true;
882                notifyAll();
883            }
884        }
885
886        public boolean waitForFinish() {
887            synchronized (this) {
888                while (!mFinished) {
889                    try {
890                        if (!mAm.asBinder().pingBinder()) {
891                            return false;
892                        }
893                        wait(1000);
894                    } catch (InterruptedException e) {
895                        throw new IllegalStateException(e);
896                    }
897                }
898            }
899            return true;
900        }
901    }
902
903    private String nextOption() {
904        if (mCurArgData != null) {
905            String prev = mArgs[mNextArg - 1];
906            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
907        }
908        if (mNextArg >= mArgs.length) {
909            return null;
910        }
911        String arg = mArgs[mNextArg];
912        if (!arg.startsWith("-")) {
913            return null;
914        }
915        mNextArg++;
916        if (arg.equals("--")) {
917            return null;
918        }
919        if (arg.length() > 1 && arg.charAt(1) != '-') {
920            if (arg.length() > 2) {
921                mCurArgData = arg.substring(2);
922                return arg.substring(0, 2);
923            } else {
924                mCurArgData = null;
925                return arg;
926            }
927        }
928        mCurArgData = null;
929        return arg;
930    }
931
932    private String nextArg() {
933        if (mCurArgData != null) {
934            String arg = mCurArgData;
935            mCurArgData = null;
936            return arg;
937        } else if (mNextArg < mArgs.length) {
938            return mArgs[mNextArg++];
939        } else {
940            return null;
941        }
942    }
943
944    private String nextArgRequired() {
945        String arg = nextArg();
946        if (arg == null) {
947            String prev = mArgs[mNextArg - 1];
948            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
949        }
950        return arg;
951    }
952
953    private static void showUsage() {
954        System.err.println(
955                "usage: am [subcommand] [options]\n" +
956                "\n" +
957                "    start an Activity: am start [-D] [-W] <INTENT>\n" +
958                "        -D: enable debugging\n" +
959                "        -W: wait for launch to complete\n" +
960                "\n" +
961                "    start a Service: am startservice <INTENT>\n" +
962                "\n" +
963                "    send a broadcast Intent: am broadcast <INTENT>\n" +
964                "\n" +
965                "    start an Instrumentation: am instrument [flags] <COMPONENT>\n" +
966                "        -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT)\n" +
967                "        -e <NAME> <VALUE>: set argument <NAME> to <VALUE>\n" +
968                "        -p <FILE>: write profiling data to <FILE>\n" +
969                "        -w: wait for instrumentation to finish before returning\n" +
970                "\n" +
971                "    run a test package against an application: am instrument [flags] <TEST_PACKAGE>/<RUNNER_CLASS>\n" +
972                "        -e <testrunner_flag> <testrunner_value> [,<testrunner_value>]\n" +
973                "        -w wait for the test to finish (required)\n" +
974                "        -r use with -e perf true to generate raw output for performance measurements\n" +
975                "\n" +
976                "    start profiling: am profile <PROCESS> start <FILE>\n" +
977                "    stop profiling: am profile <PROCESS> stop\n" +
978                "    dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" +
979                "        -n: dump native heap instead of managed heap\n" +
980                "\n" +
981                "    start monitoring: am monitor [--gdb <port>]\n" +
982                "        --gdb: start gdbserv on the given port at crash/ANR\n" +
983                "\n" +
984                "    control screen compatibility: am screen-compat [on|off] [package]\n" +
985                "\n" +
986                "    <INTENT> specifications include these flags:\n" +
987                "        [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
988                "        [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
989                "        [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
990                "        [--esn <EXTRA_KEY> ...]\n" +
991                "        [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
992                "        [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
993                "        [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
994                "        [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
995                "        [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
996                "        [-n <COMPONENT>] [-f <FLAGS>]\n" +
997                "        [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
998                "        [--debug-log-resolution]\n" +
999                "        [--activity-brought-to-front] [--activity-clear-top]\n" +
1000                "        [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
1001                "        [--activity-launched-from-history] [--activity-multiple-task]\n" +
1002                "        [--activity-no-animation] [--activity-no-history]\n" +
1003                "        [--activity-no-user-action] [--activity-previous-is-top]\n" +
1004                "        [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
1005                "        [--activity-single-top]\n" +
1006                "        [--receiver-registered-only] [--receiver-replace-pending]\n" +
1007                "        [<URI>]\n"
1008                );
1009    }
1010}
1011