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