Am.java revision 6a69b4fbaee9a8251401453cd2d3509d52f5b91c
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.IActivityManager;
23import android.app.IInstrumentationWatcher;
24import android.app.Instrumentation;
25import android.content.ComponentName;
26import android.content.IIntentReceiver;
27import android.content.Intent;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.ParcelFileDescriptor;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.util.AndroidException;
34import android.view.IWindowManager;
35
36import java.io.File;
37import java.io.FileNotFoundException;
38import java.io.PrintStream;
39import java.net.URISyntaxException;
40import java.util.Iterator;
41import java.util.Set;
42
43public class Am {
44
45    private IActivityManager mAm;
46    private String[] mArgs;
47    private int mNextArg;
48    private String mCurArgData;
49
50    private boolean mDebugOption = false;
51    private boolean mWaitOption = false;
52
53    // These are magic strings understood by the Eclipse plugin.
54    private static final String FATAL_ERROR_CODE = "Error type 1";
55    private static final String NO_SYSTEM_ERROR_CODE = "Error type 2";
56    private static final String NO_CLASS_ERROR_CODE = "Error type 3";
57
58    /**
59     * Command-line entry point.
60     *
61     * @param args The command-line arguments
62     */
63    public static void main(String[] args) {
64        try {
65            (new Am()).run(args);
66        } catch (IllegalArgumentException e) {
67            showUsage();
68            System.err.println("Error: " + e.getMessage());
69        } catch (Exception e) {
70            System.err.println(e.toString());
71            System.exit(1);
72        }
73    }
74
75    private void run(String[] args) throws Exception {
76        if (args.length < 1) {
77            showUsage();
78            return;
79        }
80
81        mAm = ActivityManagerNative.getDefault();
82        if (mAm == null) {
83            System.err.println(NO_SYSTEM_ERROR_CODE);
84            throw new AndroidException("Can't connect to activity manager; is the system running?");
85        }
86
87        mArgs = args;
88        String op = args[0];
89        mNextArg = 1;
90
91        if (op.equals("start")) {
92            runStart();
93        } else if (op.equals("startservice")) {
94            runStartService();
95        } else if (op.equals("instrument")) {
96            runInstrument();
97        } else if (op.equals("broadcast")) {
98            sendBroadcast();
99        } else if (op.equals("profile")) {
100            runProfile();
101        } else if (op.equals("dumpheap")) {
102            runDumpHeap();
103        } else {
104            throw new IllegalArgumentException("Unknown command: " + op);
105        }
106    }
107
108    private Intent makeIntent() throws URISyntaxException {
109        Intent intent = new Intent();
110        boolean hasIntentInfo = false;
111
112        mDebugOption = false;
113        mWaitOption = false;
114        Uri data = null;
115        String type = null;
116
117        String opt;
118        while ((opt=nextOption()) != null) {
119            if (opt.equals("-a")) {
120                intent.setAction(nextArgRequired());
121                hasIntentInfo = true;
122            } else if (opt.equals("-d")) {
123                data = Uri.parse(nextArgRequired());
124                hasIntentInfo = true;
125            } else if (opt.equals("-t")) {
126                type = nextArgRequired();
127                hasIntentInfo = true;
128            } else if (opt.equals("-c")) {
129                intent.addCategory(nextArgRequired());
130                hasIntentInfo = true;
131            } else if (opt.equals("-e") || opt.equals("--es")) {
132                String key = nextArgRequired();
133                String value = nextArgRequired();
134                intent.putExtra(key, value);
135                hasIntentInfo = true;
136            } else if (opt.equals("--esn")) {
137                String key = nextArgRequired();
138                intent.putExtra(key, (String) null);
139                hasIntentInfo = true;
140            } else if (opt.equals("--ei")) {
141                String key = nextArgRequired();
142                String value = nextArgRequired();
143                intent.putExtra(key, Integer.valueOf(value));
144                hasIntentInfo = true;
145            } else if (opt.equals("--el")) {
146                String key = nextArgRequired();
147                String value = nextArgRequired();
148                intent.putExtra(key, Long.valueOf(value));
149                hasIntentInfo = true;
150            } else if (opt.equals("--ez")) {
151                String key = nextArgRequired();
152                String value = nextArgRequired();
153                intent.putExtra(key, Boolean.valueOf(value));
154                hasIntentInfo = true;
155            } else if (opt.equals("-n")) {
156                String str = nextArgRequired();
157                ComponentName cn = ComponentName.unflattenFromString(str);
158                if (cn == null) throw new IllegalArgumentException("Bad component name: " + str);
159                intent.setComponent(cn);
160                hasIntentInfo = true;
161            } else if (opt.equals("-f")) {
162                String str = nextArgRequired();
163                intent.setFlags(Integer.decode(str).intValue());
164            } else if (opt.equals("--grant-read-uri-permission")) {
165                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
166            } else if (opt.equals("--grant-write-uri-permission")) {
167                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
168            } else if (opt.equals("--debug-log-resolution")) {
169                intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
170            } else if (opt.equals("--activity-brought-to-front")) {
171                intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
172            } else if (opt.equals("--activity-clear-top")) {
173                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
174            } else if (opt.equals("--activity-clear-when-task-reset")) {
175                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
176            } else if (opt.equals("--activity-exclude-from-recents")) {
177                intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
178            } else if (opt.equals("--activity-launched-from-history")) {
179                intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
180            } else if (opt.equals("--activity-multiple-task")) {
181                intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
182            } else if (opt.equals("--activity-no-animation")) {
183                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
184            } else if (opt.equals("--activity-no-history")) {
185                intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
186            } else if (opt.equals("--activity-no-user-action")) {
187                intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
188            } else if (opt.equals("--activity-previous-is-top")) {
189                intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
190            } else if (opt.equals("--activity-reorder-to-front")) {
191                intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
192            } else if (opt.equals("--activity-reset-task-if-needed")) {
193                intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
194            } else if (opt.equals("--activity-single-top")) {
195                intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
196            } else if (opt.equals("--receiver-registered-only")) {
197                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
198            } else if (opt.equals("--receiver-replace-pending")) {
199                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
200            } else if (opt.equals("-D")) {
201                mDebugOption = true;
202            } else if (opt.equals("-W")) {
203                mWaitOption = true;
204            } else {
205                System.err.println("Error: Unknown option: " + opt);
206                showUsage();
207                return null;
208            }
209        }
210        intent.setDataAndType(data, type);
211
212        String uri = nextArg();
213        if (uri != null) {
214            Intent oldIntent = intent;
215            intent = Intent.parseUri(uri, 0);
216            if (oldIntent.getAction() != null) {
217                intent.setAction(oldIntent.getAction());
218            }
219            if (oldIntent.getData() != null || oldIntent.getType() != null) {
220                intent.setDataAndType(oldIntent.getData(), oldIntent.getType());
221            }
222            Set cats = oldIntent.getCategories();
223            if (cats != null) {
224                Iterator it = cats.iterator();
225                while (it.hasNext()) {
226                    intent.addCategory((String)it.next());
227                }
228            }
229            hasIntentInfo = true;
230        }
231
232        if (!hasIntentInfo) throw new IllegalArgumentException("No intent supplied");
233        return intent;
234    }
235
236    private void runStartService() throws Exception {
237        Intent intent = makeIntent();
238        System.out.println("Starting service: " + intent);
239        ComponentName cn = mAm.startService(null, intent, intent.getType());
240        if (cn == null) {
241            System.err.println("Error: Not found; no service started.");
242        }
243    }
244
245    private void runStart() throws Exception {
246        Intent intent = makeIntent();
247        System.out.println("Starting: " + intent);
248        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
249        // XXX should do something to determine the MIME type.
250        IActivityManager.WaitResult result = null;
251        int res;
252        if (mWaitOption) {
253            result = mAm.startActivityAndWait(null, intent, intent.getType(),
254                        null, 0, null, null, 0, false, mDebugOption);
255            res = result.result;
256        } else {
257            res = mAm.startActivity(null, intent, intent.getType(),
258                    null, 0, null, null, 0, false, mDebugOption);
259        }
260        PrintStream out = mWaitOption ? System.out : System.err;
261        boolean launched = false;
262        switch (res) {
263            case IActivityManager.START_SUCCESS:
264                launched = true;
265                break;
266            case IActivityManager.START_SWITCHES_CANCELED:
267                launched = true;
268                out.println(
269                        "Warning: Activity not started because the "
270                        + " current activity is being kept for the user.");
271                break;
272            case IActivityManager.START_DELIVERED_TO_TOP:
273                launched = true;
274                out.println(
275                        "Warning: Activity not started, intent has "
276                        + "been delivered to currently running "
277                        + "top-most instance.");
278                break;
279            case IActivityManager.START_RETURN_INTENT_TO_CALLER:
280                launched = true;
281                out.println(
282                        "Warning: Activity not started because intent "
283                        + "should be handled by the caller");
284                break;
285            case IActivityManager.START_TASK_TO_FRONT:
286                launched = true;
287                out.println(
288                        "Warning: Activity not started, its current "
289                        + "task has been brought to the front");
290                break;
291            case IActivityManager.START_INTENT_NOT_RESOLVED:
292                out.println(
293                        "Error: Activity not started, unable to "
294                        + "resolve " + intent.toString());
295                break;
296            case IActivityManager.START_CLASS_NOT_FOUND:
297                out.println(NO_CLASS_ERROR_CODE);
298                out.println("Error: Activity class " +
299                        intent.getComponent().toShortString()
300                        + " does not exist.");
301                break;
302            case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
303                out.println(
304                        "Error: Activity not started, you requested to "
305                        + "both forward and receive its result");
306                break;
307            case IActivityManager.START_PERMISSION_DENIED:
308                out.println(
309                        "Error: Activity not started, you do not "
310                        + "have permission to access it.");
311                break;
312            default:
313                out.println(
314                        "Error: Activity not started, unknown error code " + res);
315                break;
316        }
317        if (mWaitOption && launched) {
318            if (result == null) {
319                result = new IActivityManager.WaitResult();
320                result.who = intent.getComponent();
321            }
322            System.out.println("Status: " + (result.timeout ? "timeout" : "ok"));
323            if (result.who != null) {
324                System.out.println("Activity: " + result.who.flattenToShortString());
325            }
326            if (result.thisTime >= 0) {
327                System.out.println("ThisTime: " + result.thisTime);
328            }
329            if (result.totalTime >= 0) {
330                System.out.println("TotalTime: " + result.totalTime);
331            }
332            System.out.println("Complete");
333        }
334    }
335
336    private void sendBroadcast() throws Exception {
337        Intent intent = makeIntent();
338        IntentReceiver receiver = new IntentReceiver();
339        System.out.println("Broadcasting: " + intent);
340        mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null, true, false);
341        receiver.waitForFinish();
342    }
343
344    private void runInstrument() throws Exception {
345        String profileFile = null;
346        boolean wait = false;
347        boolean rawMode = false;
348        boolean no_window_animation = false;
349        Bundle args = new Bundle();
350        String argKey = null, argValue = null;
351        IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
352
353        String opt;
354        while ((opt=nextOption()) != null) {
355            if (opt.equals("-p")) {
356                profileFile = nextArgRequired();
357            } else if (opt.equals("-w")) {
358                wait = true;
359            } else if (opt.equals("-r")) {
360                rawMode = true;
361            } else if (opt.equals("-e")) {
362                argKey = nextArgRequired();
363                argValue = nextArgRequired();
364                args.putString(argKey, argValue);
365            } else if (opt.equals("--no_window_animation")) {
366                no_window_animation = true;
367            } else {
368                System.err.println("Error: Unknown option: " + opt);
369                showUsage();
370                return;
371            }
372        }
373
374        String cnArg = nextArgRequired();
375        ComponentName cn = ComponentName.unflattenFromString(cnArg);
376        if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
377
378        InstrumentationWatcher watcher = null;
379        if (wait) {
380            watcher = new InstrumentationWatcher();
381            watcher.setRawOutput(rawMode);
382        }
383        float[] oldAnims = null;
384        if (no_window_animation) {
385            oldAnims = wm.getAnimationScales();
386            wm.setAnimationScale(0, 0.0f);
387            wm.setAnimationScale(1, 0.0f);
388        }
389
390        if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher)) {
391            throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
392        }
393
394        if (watcher != null) {
395            if (!watcher.waitForFinish()) {
396                System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");
397            }
398        }
399
400        if (oldAnims != null) {
401            wm.setAnimationScales(oldAnims);
402        }
403    }
404
405    private void runProfile() throws Exception {
406        String profileFile = null;
407        boolean start = false;
408        String process = nextArgRequired();
409        ParcelFileDescriptor fd = null;
410
411        String cmd = nextArgRequired();
412        if ("start".equals(cmd)) {
413            start = true;
414            profileFile = nextArgRequired();
415            try {
416                fd = ParcelFileDescriptor.open(
417                        new File(profileFile),
418                        ParcelFileDescriptor.MODE_CREATE |
419                        ParcelFileDescriptor.MODE_TRUNCATE |
420                        ParcelFileDescriptor.MODE_READ_WRITE);
421            } catch (FileNotFoundException e) {
422                System.err.println("Error: Unable to open file: " + profileFile);
423                return;
424            }
425        } else if (!"stop".equals(cmd)) {
426            throw new IllegalArgumentException("Profile command " + cmd + " not valid");
427        }
428
429        if (!mAm.profileControl(process, start, profileFile, fd)) {
430            throw new AndroidException("PROFILE FAILED on process " + process);
431        }
432    }
433
434    private void runDumpHeap() throws Exception {
435        boolean managed = !"-n".equals(nextOption());
436        String process = nextArgRequired();
437        String heapFile = nextArgRequired();
438        ParcelFileDescriptor fd = null;
439
440        try {
441            fd = ParcelFileDescriptor.open(
442                    new File(heapFile),
443                    ParcelFileDescriptor.MODE_CREATE |
444                    ParcelFileDescriptor.MODE_TRUNCATE |
445                    ParcelFileDescriptor.MODE_READ_WRITE);
446        } catch (FileNotFoundException e) {
447            System.err.println("Error: Unable to open file: " + heapFile);
448            return;
449        }
450
451        if (!mAm.dumpHeap(process, managed, heapFile, fd)) {
452            throw new AndroidException("HEAP DUMP FAILED on process " + process);
453        }
454    }
455
456    private class IntentReceiver extends IIntentReceiver.Stub {
457        private boolean mFinished = false;
458
459        public synchronized void performReceive(
460                Intent intent, int rc, String data, Bundle ext, boolean ord,
461                boolean sticky) {
462            String line = "Broadcast completed: result=" + rc;
463            if (data != null) line = line + ", data=\"" + data + "\"";
464            if (ext != null) line = line + ", extras: " + ext;
465            System.out.println(line);
466            mFinished = true;
467            notifyAll();
468        }
469
470        public synchronized void waitForFinish() {
471            try {
472                while (!mFinished) wait();
473            } catch (InterruptedException e) {
474                throw new IllegalStateException(e);
475            }
476        }
477    }
478
479    private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
480        private boolean mFinished = false;
481        private boolean mRawMode = false;
482
483        /**
484         * Set or reset "raw mode".  In "raw mode", all bundles are dumped.  In "pretty mode",
485         * if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
486         * @param rawMode true for raw mode, false for pretty mode.
487         */
488        public void setRawOutput(boolean rawMode) {
489            mRawMode = rawMode;
490        }
491
492        public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
493            synchronized (this) {
494                // pretty printer mode?
495                String pretty = null;
496                if (!mRawMode && results != null) {
497                    pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
498                }
499                if (pretty != null) {
500                    System.out.print(pretty);
501                } else {
502                    if (results != null) {
503                        for (String key : results.keySet()) {
504                            System.out.println(
505                                    "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
506                        }
507                    }
508                    System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
509                }
510                notifyAll();
511            }
512        }
513
514        public void instrumentationFinished(ComponentName name, int resultCode,
515                Bundle results) {
516            synchronized (this) {
517                // pretty printer mode?
518                String pretty = null;
519                if (!mRawMode && results != null) {
520                    pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
521                }
522                if (pretty != null) {
523                    System.out.println(pretty);
524                } else {
525                    if (results != null) {
526                        for (String key : results.keySet()) {
527                            System.out.println(
528                                    "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
529                        }
530                    }
531                    System.out.println("INSTRUMENTATION_CODE: " + resultCode);
532                }
533                mFinished = true;
534                notifyAll();
535            }
536        }
537
538        public boolean waitForFinish() {
539            synchronized (this) {
540                while (!mFinished) {
541                    try {
542                        if (!mAm.asBinder().pingBinder()) {
543                            return false;
544                        }
545                        wait(1000);
546                    } catch (InterruptedException e) {
547                        throw new IllegalStateException(e);
548                    }
549                }
550            }
551            return true;
552        }
553    }
554
555    private String nextOption() {
556        if (mCurArgData != null) {
557            String prev = mArgs[mNextArg - 1];
558            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
559        }
560        if (mNextArg >= mArgs.length) {
561            return null;
562        }
563        String arg = mArgs[mNextArg];
564        if (!arg.startsWith("-")) {
565            return null;
566        }
567        mNextArg++;
568        if (arg.equals("--")) {
569            return null;
570        }
571        if (arg.length() > 1 && arg.charAt(1) != '-') {
572            if (arg.length() > 2) {
573                mCurArgData = arg.substring(2);
574                return arg.substring(0, 2);
575            } else {
576                mCurArgData = null;
577                return arg;
578            }
579        }
580        mCurArgData = null;
581        return arg;
582    }
583
584    private String nextArg() {
585        if (mCurArgData != null) {
586            String arg = mCurArgData;
587            mCurArgData = null;
588            return arg;
589        } else if (mNextArg < mArgs.length) {
590            return mArgs[mNextArg++];
591        } else {
592            return null;
593        }
594    }
595
596    private String nextArgRequired() {
597        String arg = nextArg();
598        if (arg == null) {
599            String prev = mArgs[mNextArg - 1];
600            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
601        }
602        return arg;
603    }
604
605    private static void showUsage() {
606        System.err.println(
607                "usage: am [subcommand] [options]\n" +
608                "\n" +
609                "    start an Activity: am start [-D] [-W] <INTENT>\n" +
610                "        -D: enable debugging\n" +
611                "        -W: wait for launch to complete\n" +
612                "\n" +
613                "    start a Service: am startservice <INTENT>\n" +
614                "\n" +
615                "    send a broadcast Intent: am broadcast <INTENT>\n" +
616                "\n" +
617                "    start an Instrumentation: am instrument [flags] <COMPONENT>\n" +
618                "        -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT)\n" +
619                "        -e <NAME> <VALUE>: set argument <NAME> to <VALUE>\n" +
620                "        -p <FILE>: write profiling data to <FILE>\n" +
621                "        -w: wait for instrumentation to finish before returning\n" +
622                "\n" +
623                "    start profiling: am profile <PROCESS> start <FILE>\n" +
624                "    stop profiling: am profile <PROCESS> stop\n" +
625                "    dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" +
626                "        -n: dump native heap instead of managed heap\n" +
627                "\n" +
628                "    <INTENT> specifications include these flags:\n" +
629                "        [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
630                "        [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
631                "        [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
632                "        [--esn <EXTRA_KEY> ...]\n" +
633                "        [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
634                "        [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
635                "        [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
636                "        [-n <COMPONENT>] [-f <FLAGS>]\n" +
637                "        [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
638                "        [--debug-log-resolution]\n" +
639                "        [--activity-brought-to-front] [--activity-clear-top]\n" +
640                "        [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
641                "        [--activity-launched-from-history] [--activity-multiple-task]\n" +
642                "        [--activity-no-animation] [--activity-no-history]\n" +
643                "        [--activity-no-user-action] [--activity-previous-is-top]\n" +
644                "        [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
645                "        [--activity-single-top]\n" +
646                "        [--receiver-registered-only] [--receiver-replace-pending]\n" +
647                "        [<URI>]\n"
648                );
649    }
650}
651