am instrument gets protobuf

Refactor the am instrument command and add a version that
outputs protobuf in addition to the old one that prints
loosely formatted text.

Change-Id: I34079d8af2b7b6c6c59837d54719806109ba286c
Test: bit tool
diff --git a/cmds/am/Android.mk b/cmds/am/Android.mk
index f8350dc..5586dd4 100644
--- a/cmds/am/Android.mk
+++ b/cmds/am/Android.mk
@@ -3,8 +3,11 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-proto-files-under, proto)
 LOCAL_MODULE := am
+LOCAL_PROTOC_OPTIMIZE_TYPE := stream
 include $(BUILD_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
@@ -13,3 +16,14 @@
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE_TAGS := optional
 include $(BUILD_PREBUILT)
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(call all-proto-files-under, proto)
+LOCAL_MODULE := libinstrumentation
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+    $(call intermediates-dir-for,STATIC_LIBRARIES,libinstrumentation,HOST,,,)/proto/$(LOCAL_PATH)/proto
+include $(BUILD_HOST_STATIC_LIBRARY)
+
diff --git a/cmds/am/proto/instrumentation_data.proto b/cmds/am/proto/instrumentation_data.proto
new file mode 100644
index 0000000..12a18a2
--- /dev/null
+++ b/cmds/am/proto/instrumentation_data.proto
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.am;
+
+option java_package = "com.android.commands.am";
+
+message ResultsBundleEntry {
+    optional string key = 1;
+
+    optional string value_string = 2;
+    optional sint32 value_int = 3;
+    optional float value_float = 4;
+    optional double value_double = 5;
+    optional sint64 value_long = 6;
+    optional ResultsBundle value_bundle = 7;
+}
+
+message ResultsBundle {
+    repeated ResultsBundleEntry entries = 1;
+}
+
+message TestStatus {
+    optional sint32 result_code = 3;
+    optional ResultsBundle results = 4;
+}
+
+enum SessionStatusCode {
+    /**
+     * The command ran successfully. This does not imply that the tests passed.
+     */
+    SESSION_FINISHED = 0;
+
+    /**
+     * There was an unrecoverable error running the tests.
+     */
+    SESSION_ABORTED = 1;
+}
+
+message SessionStatus {
+    optional SessionStatusCode status_code = 1;
+    optional string error_text = 2;
+    optional sint32 result_code = 3;
+    optional ResultsBundle results = 4;
+}
+
+message Session {
+    repeated TestStatus test_status = 1;
+    optional SessionStatus session_status = 2;
+}
+
+
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index e197bfc..91a4549 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1,20 +1,18 @@
 /*
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 package com.android.commands.am;
 
@@ -235,6 +233,7 @@
                 "    -e <NAME> <VALUE>: set argument <NAME> to <VALUE>.  For test runners a\n" +
                 "        common form is [-e <testrunner_flag> <value>[,<value>...]].\n" +
                 "    -p <FILE>: write profiling data to <FILE>\n" +
+                "    -m: Write output as protobuf (machine readable)\n" +
                 "    -w: wait for instrumentation to finish before returning.  Required for\n" +
                 "        test runners.\n" +
                 "    --user <USER_ID> | current: Specify user instrumentation runs in;\n" +
@@ -543,208 +542,43 @@
         receiver.waitForFinish();
     }
 
-    private void runInstrument() throws Exception {
-        String profileFile = null;
-        boolean wait = false;
-        boolean rawMode = false;
-        boolean no_window_animation = false;
-        int userId = UserHandle.USER_CURRENT;
-        Bundle args = new Bundle();
-        String argKey = null, argValue = null;
-        IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
-        String abi = null;
+    public void runInstrument() throws Exception {
+        Instrument instrument = new Instrument(mAm, mPm);
 
         String opt;
         while ((opt=nextOption()) != null) {
             if (opt.equals("-p")) {
-                profileFile = nextArgRequired();
+                instrument.profileFile = nextArgRequired();
             } else if (opt.equals("-w")) {
-                wait = true;
+                instrument.wait = true;
             } else if (opt.equals("-r")) {
-                rawMode = true;
+                instrument.rawMode = true;
+            } else if (opt.equals("-m")) {
+                instrument.proto = true;
             } else if (opt.equals("-e")) {
-                argKey = nextArgRequired();
-                argValue = nextArgRequired();
-                args.putString(argKey, argValue);
+                final String argKey = nextArgRequired();
+                final String argValue = nextArgRequired();
+                instrument.args.putString(argKey, argValue);
             } else if (opt.equals("--no_window_animation")
                     || opt.equals("--no-window-animation")) {
-                no_window_animation = true;
+                instrument.noWindowAnimation = true;
             } else if (opt.equals("--user")) {
-                userId = parseUserArg(nextArgRequired());
+                instrument.userId = parseUserArg(nextArgRequired());
             } else if (opt.equals("--abi")) {
-                abi = nextArgRequired();
+                instrument.abi = nextArgRequired();
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return;
             }
         }
 
-        if (userId == UserHandle.USER_ALL) {
+        if (instrument.userId == UserHandle.USER_ALL) {
             System.err.println("Error: Can't start instrumentation with user 'all'");
             return;
         }
 
-        String cnArg = nextArgRequired();
+        instrument.componentNameArg = nextArgRequired();
 
-        ComponentName cn;
-        if (cnArg.contains("/")) {
-            cn = ComponentName.unflattenFromString(cnArg);
-            if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
-        } else {
-            List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
-
-            final int numInfos = infos == null ? 0: infos.size();
-            List<ComponentName> cns = new ArrayList<>();
-            for (int i = 0; i < numInfos; i++) {
-                InstrumentationInfo info = infos.get(i);
-
-                ComponentName c = new ComponentName(info.packageName, info.name);
-                if (cnArg.equals(info.packageName)) {
-                    cns.add(c);
-                }
-            }
-
-            if (cns.size() == 0) {
-                throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
-            } else if (cns.size() == 1) {
-                cn = cns.get(0);
-            } else {
-                StringBuilder cnsStr = new StringBuilder();
-                final int numCns = cns.size();
-                for (int i = 0; i < numCns; i++) {
-                    cnsStr.append(cns.get(i).flattenToString());
-                    cnsStr.append(", ");
-                }
-
-                // Remove last ", "
-                cnsStr.setLength(cnsStr.length() - 2);
-
-                throw new IllegalArgumentException("Found multiple instrumentations: "
-                        + cnsStr.toString());
-            }
-        }
-
-        InstrumentationWatcher watcher = null;
-        UiAutomationConnection connection = null;
-        if (wait) {
-            watcher = new InstrumentationWatcher();
-            watcher.setRawOutput(rawMode);
-            connection = new UiAutomationConnection();
-        }
-
-        float[] oldAnims = null;
-        if (no_window_animation) {
-            oldAnims = wm.getAnimationScales();
-            wm.setAnimationScale(0, 0.0f);
-            wm.setAnimationScale(1, 0.0f);
-        }
-
-        if (abi != null) {
-            final String[] supportedAbis = Build.SUPPORTED_ABIS;
-            boolean matched = false;
-            for (String supportedAbi : supportedAbis) {
-                if (supportedAbi.equals(abi)) {
-                    matched = true;
-                    break;
-                }
-            }
-
-            if (!matched) {
-                throw new AndroidException(
-                        "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
-            }
-        }
-
-        if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) {
-            throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
-        }
-
-        if (watcher != null) {
-            if (!watcher.waitForFinish()) {
-                System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");
-            }
-        }
-
-        if (oldAnims != null) {
-            wm.setAnimationScales(oldAnims);
-        }
-    }
-
-    private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
-        private boolean mFinished = false;
-        private boolean mRawMode = false;
-
-        /**
-         * Set or reset "raw mode".  In "raw mode", all bundles are dumped.  In "pretty mode",
-         * if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
-         * @param rawMode true for raw mode, false for pretty mode.
-         */
-        public void setRawOutput(boolean rawMode) {
-            mRawMode = rawMode;
-        }
-
-        @Override
-        public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
-            synchronized (this) {
-                // pretty printer mode?
-                String pretty = null;
-                if (!mRawMode && results != null) {
-                    pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
-                }
-                if (pretty != null) {
-                    System.out.print(pretty);
-                } else {
-                    if (results != null) {
-                        for (String key : results.keySet()) {
-                            System.out.println(
-                                    "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
-                        }
-                    }
-                    System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
-                }
-                notifyAll();
-            }
-        }
-
-        @Override
-        public void instrumentationFinished(ComponentName name, int resultCode,
-                Bundle results) {
-            synchronized (this) {
-                // pretty printer mode?
-                String pretty = null;
-                if (!mRawMode && results != null) {
-                    pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
-                }
-                if (pretty != null) {
-                    System.out.println(pretty);
-                } else {
-                    if (results != null) {
-                        for (String key : results.keySet()) {
-                            System.out.println(
-                                    "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
-                        }
-                    }
-                    System.out.println("INSTRUMENTATION_CODE: " + resultCode);
-                }
-                mFinished = true;
-                notifyAll();
-            }
-        }
-
-        public boolean waitForFinish() {
-            synchronized (this) {
-                while (!mFinished) {
-                    try {
-                        if (!mAm.asBinder().pingBinder()) {
-                            return false;
-                        }
-                        wait(1000);
-                    } catch (InterruptedException e) {
-                        throw new IllegalStateException(e);
-                    }
-                }
-            }
-            return true;
-        }
+        instrument.run();
     }
 }
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
new file mode 100644
index 0000000..8eefd25
--- /dev/null
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.am;
+
+import android.app.IActivityManager;
+import android.app.IInstrumentationWatcher;
+import android.app.Instrumentation;
+import android.app.UiAutomationConnection;
+import android.content.ComponentName;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.AndroidException;
+import android.util.proto.ProtoOutputStream;
+import android.view.IWindowManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Runs the am instrument command
+ */
+public class Instrument {
+    private final IActivityManager mAm;
+    private final IPackageManager mPm;
+    private final IWindowManager mWm;
+
+    // Command line arguments
+    public String profileFile = null;
+    public boolean wait = false;
+    public boolean rawMode = false;
+    public boolean proto = false;
+    public boolean noWindowAnimation = false;
+    public String abi = null;
+    public int userId = UserHandle.USER_CURRENT;
+    public Bundle args = new Bundle();
+    // Required
+    public String componentNameArg;
+
+    /**
+     * Construct the instrument command runner.
+     */
+    public Instrument(IActivityManager am, IPackageManager pm) {
+        mAm = am;
+        mPm = pm;
+        mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+    }
+
+    /**
+     * Base class for status reporting.
+     *
+     * All the methods on this interface are called within the synchronized block
+     * of the InstrumentationWatcher, so calls are in order.  However, that means
+     * you must be careful not to do blocking operations because you don't know
+     * exactly the locking dependencies.
+     */
+    private interface StatusReporter {
+        /**
+         * Status update for tests.
+         */
+        public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+                Bundle results);
+
+        /**
+         * The tests finished.
+         */
+        public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+                Bundle results);
+
+        /**
+         * @param errorText a description of the error
+         * @param commandError True if the error is related to the commandline, as opposed
+         *      to a test failing.
+         */
+        public void onError(String errorText, boolean commandError);
+    }
+
+    /**
+     * Printer for the 'classic' text based status reporting.
+     */
+    private class TextStatusReporter implements StatusReporter {
+        private boolean mRawMode;
+
+        /**
+         * Human-ish readable output.
+         *
+         * @param rawMode   In "raw mode" (true), all bundles are dumped.
+         *                  In "pretty mode" (false), if a bundle includes
+         *                  Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
+         */
+        public TextStatusReporter(boolean rawMode) {
+            mRawMode = rawMode;
+        }
+
+        @Override
+        public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+                Bundle results) {
+            // pretty printer mode?
+            String pretty = null;
+            if (!mRawMode && results != null) {
+                pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+            }
+            if (pretty != null) {
+                System.out.print(pretty);
+            } else {
+                if (results != null) {
+                    for (String key : results.keySet()) {
+                        System.out.println(
+                                "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
+                    }
+                }
+                System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
+            }
+        }
+
+        @Override
+        public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+                Bundle results) {
+            // pretty printer mode?
+            String pretty = null;
+            if (!mRawMode && results != null) {
+                pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+            }
+            if (pretty != null) {
+                System.out.println(pretty);
+            } else {
+                if (results != null) {
+                    for (String key : results.keySet()) {
+                        System.out.println(
+                                "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
+                    }
+                }
+                System.out.println("INSTRUMENTATION_CODE: " + resultCode);
+            }
+        }
+
+        @Override
+        public void onError(String errorText, boolean commandError) {
+            // The regular BaseCommand error printing will print the commandErrors.
+            if (!commandError) {
+                System.out.println(errorText);
+            }
+        }
+    }
+
+    /**
+     * Printer for the protobuf based status reporting.
+     */
+    private class ProtoStatusReporter implements StatusReporter {
+        @Override
+        public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+                Bundle results) {
+            final ProtoOutputStream proto = new ProtoOutputStream();
+
+            final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
+
+            proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
+            writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
+
+            proto.endRepeatedObject(token);
+            writeProtoToStdout(proto);
+        }
+
+        @Override
+        public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+                Bundle results) {
+            final ProtoOutputStream proto = new ProtoOutputStream();
+
+            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
+
+            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+                    InstrumentationData.SESSION_FINISHED);
+            proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
+            writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
+
+            proto.endObject(token);
+            writeProtoToStdout(proto);
+        }
+
+        @Override
+        public void onError(String errorText, boolean commandError) {
+            final ProtoOutputStream proto = new ProtoOutputStream();
+
+            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
+
+            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+                    InstrumentationData.SESSION_ABORTED);
+            proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+
+            proto.endObject(token);
+            writeProtoToStdout(proto);
+        }
+
+        private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
+            final long bundleToken = proto.startObject(fieldId);
+
+            for (final String key: bundle.keySet()) {
+                final long entryToken = proto.startRepeatedObject(
+                        InstrumentationData.ResultsBundle.ENTRIES);
+
+                proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
+
+                final Object val = bundle.get(key);
+                if (val instanceof String) {
+                    proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
+                            (String)val);
+                } else if (val instanceof Byte) {
+                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+                            ((Byte)val).intValue());
+                } else if (val instanceof Double) {
+                    proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
+                            ((Double)val).doubleValue());
+                } else if (val instanceof Float) {
+                    proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
+                            ((Float)val).floatValue());
+                } else if (val instanceof Integer) {
+                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+                            ((Integer)val).intValue());
+                } else if (val instanceof Long) {
+                    proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
+                            ((Long)val).longValue());
+                } else if (val instanceof Short) {
+                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+                            ((Short)val).intValue());
+                } else if (val instanceof Bundle) {
+                    writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
+                            (Bundle)val);
+                }
+
+                proto.endRepeatedObject(entryToken);
+            }
+
+            proto.endObject(bundleToken);
+        }
+
+        private void writeProtoToStdout(ProtoOutputStream proto) {
+            try {
+                System.out.write(proto.getBytes());
+                System.out.flush();
+            } catch (IOException ex) {
+                System.err.println("Error writing finished response: ");
+                ex.printStackTrace(System.err);
+            }
+        }
+    }
+
+
+    /**
+     * Callbacks from the remote instrumentation instance.
+     */
+    private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
+        private final StatusReporter mReporter;
+
+        private boolean mFinished = false;
+
+        public InstrumentationWatcher(StatusReporter reporter) {
+            mReporter = reporter;
+        }
+
+        @Override
+        public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
+            synchronized (this) {
+                mReporter.onInstrumentationStatusLocked(name, resultCode, results);
+                notifyAll();
+            }
+        }
+
+        @Override
+        public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) {
+            synchronized (this) {
+                mReporter.onInstrumentationFinishedLocked(name, resultCode, results);
+                mFinished = true;
+                notifyAll();
+            }
+        }
+
+        public boolean waitForFinish() {
+            synchronized (this) {
+                while (!mFinished) {
+                    try {
+                        if (!mAm.asBinder().pingBinder()) {
+                            return false;
+                        }
+                        wait(1000);
+                    } catch (InterruptedException e) {
+                        throw new IllegalStateException(e);
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Figure out which component they really meant.
+     */
+    private ComponentName parseComponentName(String cnArg) throws Exception {
+        if (cnArg.contains("/")) {
+            ComponentName cn = ComponentName.unflattenFromString(cnArg);
+            if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
+            return cn;
+        } else {
+            List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
+
+            final int numInfos = infos == null ? 0: infos.size();
+            ArrayList<ComponentName> cns = new ArrayList<>();
+            for (int i = 0; i < numInfos; i++) {
+                InstrumentationInfo info = infos.get(i);
+
+                ComponentName c = new ComponentName(info.packageName, info.name);
+                if (cnArg.equals(info.packageName)) {
+                    cns.add(c);
+                }
+            }
+
+            if (cns.size() == 0) {
+                throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
+            } else if (cns.size() == 1) {
+                return cns.get(0);
+            } else {
+                StringBuilder cnsStr = new StringBuilder();
+                final int numCns = cns.size();
+                for (int i = 0; i < numCns; i++) {
+                    cnsStr.append(cns.get(i).flattenToString());
+                    cnsStr.append(", ");
+                }
+
+                // Remove last ", "
+                cnsStr.setLength(cnsStr.length() - 2);
+
+                throw new IllegalArgumentException("Found multiple instrumentations: "
+                        + cnsStr.toString());
+            }
+        }
+    }
+
+    /**
+     * Run the instrumentation.
+     */
+    public void run() throws Exception {
+        StatusReporter reporter = null;
+        float[] oldAnims = null;
+
+        try {
+            // Choose which output we will do.
+            if (proto) {
+                reporter = new ProtoStatusReporter();
+            } else if (wait) {
+                reporter = new TextStatusReporter(rawMode);
+            }
+
+            // Choose whether we have to wait for the results.
+            InstrumentationWatcher watcher = null;
+            UiAutomationConnection connection = null;
+            if (reporter != null) {
+                watcher = new InstrumentationWatcher(reporter);
+                connection = new UiAutomationConnection();
+            }
+
+            // Set the window animation if necessary
+            if (noWindowAnimation) {
+                oldAnims = mWm.getAnimationScales();
+                mWm.setAnimationScale(0, 0.0f);
+                mWm.setAnimationScale(1, 0.0f);
+            }
+
+            // Figure out which component we are tring to do.
+            final ComponentName cn = parseComponentName(componentNameArg);
+
+            // Choose an ABI if necessary
+            if (abi != null) {
+                final String[] supportedAbis = Build.SUPPORTED_ABIS;
+                boolean matched = false;
+                for (String supportedAbi : supportedAbis) {
+                    if (supportedAbi.equals(abi)) {
+                        matched = true;
+                        break;
+                    }
+                }
+                if (!matched) {
+                    throw new AndroidException(
+                            "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
+                }
+            }
+
+            // Start the instrumentation
+            if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId,
+                        abi)) {
+                throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
+            }
+
+            // If we have been requested to wait, do so until the instrumentation is finished.
+            if (watcher != null) {
+                if (!watcher.waitForFinish()) {
+                    reporter.onError("INSTRUMENTATION_ABORTED: System has crashed.", false);
+                    return;
+                }
+            }
+        } catch (Exception ex) {
+            // Report failures
+            if (reporter != null) {
+                reporter.onError(ex.getMessage(), true);
+            }
+
+            // And re-throw the exception
+            throw ex;
+        } finally {
+            // Clean up
+            if (oldAnims != null) {
+                mWm.setAnimationScales(oldAnims);
+            }
+        }
+    }
+}
+