1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.commands.am;
18
19import android.app.IActivityManager;
20import android.app.IInstrumentationWatcher;
21import android.app.Instrumentation;
22import android.app.UiAutomationConnection;
23import android.content.ComponentName;
24import android.content.pm.IPackageManager;
25import android.content.pm.InstrumentationInfo;
26import android.os.Build;
27import android.os.Bundle;
28import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.UserHandle;
31import android.util.AndroidException;
32import android.util.proto.ProtoOutputStream;
33import android.view.IWindowManager;
34
35import java.io.IOException;
36import java.util.ArrayList;
37import java.util.List;
38
39
40/**
41 * Runs the am instrument command
42 */
43public class Instrument {
44    private final IActivityManager mAm;
45    private final IPackageManager mPm;
46    private final IWindowManager mWm;
47
48    // Command line arguments
49    public String profileFile = null;
50    public boolean wait = false;
51    public boolean rawMode = false;
52    public boolean proto = false;
53    public boolean noWindowAnimation = false;
54    public String abi = null;
55    public int userId = UserHandle.USER_CURRENT;
56    public Bundle args = new Bundle();
57    // Required
58    public String componentNameArg;
59
60    /**
61     * Construct the instrument command runner.
62     */
63    public Instrument(IActivityManager am, IPackageManager pm) {
64        mAm = am;
65        mPm = pm;
66        mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
67    }
68
69    /**
70     * Base class for status reporting.
71     *
72     * All the methods on this interface are called within the synchronized block
73     * of the InstrumentationWatcher, so calls are in order.  However, that means
74     * you must be careful not to do blocking operations because you don't know
75     * exactly the locking dependencies.
76     */
77    private interface StatusReporter {
78        /**
79         * Status update for tests.
80         */
81        public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
82                Bundle results);
83
84        /**
85         * The tests finished.
86         */
87        public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
88                Bundle results);
89
90        /**
91         * @param errorText a description of the error
92         * @param commandError True if the error is related to the commandline, as opposed
93         *      to a test failing.
94         */
95        public void onError(String errorText, boolean commandError);
96    }
97
98    /**
99     * Printer for the 'classic' text based status reporting.
100     */
101    private class TextStatusReporter implements StatusReporter {
102        private boolean mRawMode;
103
104        /**
105         * Human-ish readable output.
106         *
107         * @param rawMode   In "raw mode" (true), all bundles are dumped.
108         *                  In "pretty mode" (false), if a bundle includes
109         *                  Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
110         */
111        public TextStatusReporter(boolean rawMode) {
112            mRawMode = rawMode;
113        }
114
115        @Override
116        public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
117                Bundle results) {
118            // pretty printer mode?
119            String pretty = null;
120            if (!mRawMode && results != null) {
121                pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
122            }
123            if (pretty != null) {
124                System.out.print(pretty);
125            } else {
126                if (results != null) {
127                    for (String key : results.keySet()) {
128                        System.out.println(
129                                "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
130                    }
131                }
132                System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
133            }
134        }
135
136        @Override
137        public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
138                Bundle results) {
139            // pretty printer mode?
140            String pretty = null;
141            if (!mRawMode && results != null) {
142                pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
143            }
144            if (pretty != null) {
145                System.out.println(pretty);
146            } else {
147                if (results != null) {
148                    for (String key : results.keySet()) {
149                        System.out.println(
150                                "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
151                    }
152                }
153                System.out.println("INSTRUMENTATION_CODE: " + resultCode);
154            }
155        }
156
157        @Override
158        public void onError(String errorText, boolean commandError) {
159            // The regular BaseCommand error printing will print the commandErrors.
160            if (!commandError) {
161                System.out.println(errorText);
162            }
163        }
164    }
165
166    /**
167     * Printer for the protobuf based status reporting.
168     */
169    private class ProtoStatusReporter implements StatusReporter {
170        @Override
171        public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
172                Bundle results) {
173            final ProtoOutputStream proto = new ProtoOutputStream();
174
175            final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
176
177            proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
178            writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
179
180            proto.endRepeatedObject(token);
181            writeProtoToStdout(proto);
182        }
183
184        @Override
185        public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
186                Bundle results) {
187            final ProtoOutputStream proto = new ProtoOutputStream();
188
189            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
190
191            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
192                    InstrumentationData.SESSION_FINISHED);
193            proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
194            writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
195
196            proto.endObject(token);
197            writeProtoToStdout(proto);
198        }
199
200        @Override
201        public void onError(String errorText, boolean commandError) {
202            final ProtoOutputStream proto = new ProtoOutputStream();
203
204            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
205
206            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
207                    InstrumentationData.SESSION_ABORTED);
208            proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
209
210            proto.endObject(token);
211            writeProtoToStdout(proto);
212        }
213
214        private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
215            final long bundleToken = proto.startObject(fieldId);
216
217            for (final String key: bundle.keySet()) {
218                final long entryToken = proto.startRepeatedObject(
219                        InstrumentationData.ResultsBundle.ENTRIES);
220
221                proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
222
223                final Object val = bundle.get(key);
224                if (val instanceof String) {
225                    proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
226                            (String)val);
227                } else if (val instanceof Byte) {
228                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
229                            ((Byte)val).intValue());
230                } else if (val instanceof Double) {
231                    proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
232                            ((Double)val).doubleValue());
233                } else if (val instanceof Float) {
234                    proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
235                            ((Float)val).floatValue());
236                } else if (val instanceof Integer) {
237                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
238                            ((Integer)val).intValue());
239                } else if (val instanceof Long) {
240                    proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
241                            ((Long)val).longValue());
242                } else if (val instanceof Short) {
243                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
244                            ((Short)val).intValue());
245                } else if (val instanceof Bundle) {
246                    writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
247                            (Bundle)val);
248                }
249
250                proto.endRepeatedObject(entryToken);
251            }
252
253            proto.endObject(bundleToken);
254        }
255
256        private void writeProtoToStdout(ProtoOutputStream proto) {
257            try {
258                System.out.write(proto.getBytes());
259                System.out.flush();
260            } catch (IOException ex) {
261                System.err.println("Error writing finished response: ");
262                ex.printStackTrace(System.err);
263            }
264        }
265    }
266
267
268    /**
269     * Callbacks from the remote instrumentation instance.
270     */
271    private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
272        private final StatusReporter mReporter;
273
274        private boolean mFinished = false;
275
276        public InstrumentationWatcher(StatusReporter reporter) {
277            mReporter = reporter;
278        }
279
280        @Override
281        public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
282            synchronized (this) {
283                mReporter.onInstrumentationStatusLocked(name, resultCode, results);
284                notifyAll();
285            }
286        }
287
288        @Override
289        public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) {
290            synchronized (this) {
291                mReporter.onInstrumentationFinishedLocked(name, resultCode, results);
292                mFinished = true;
293                notifyAll();
294            }
295        }
296
297        public boolean waitForFinish() {
298            synchronized (this) {
299                while (!mFinished) {
300                    try {
301                        if (!mAm.asBinder().pingBinder()) {
302                            return false;
303                        }
304                        wait(1000);
305                    } catch (InterruptedException e) {
306                        throw new IllegalStateException(e);
307                    }
308                }
309            }
310            return true;
311        }
312    }
313
314    /**
315     * Figure out which component they really meant.
316     */
317    private ComponentName parseComponentName(String cnArg) throws Exception {
318        if (cnArg.contains("/")) {
319            ComponentName cn = ComponentName.unflattenFromString(cnArg);
320            if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
321            return cn;
322        } else {
323            List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
324
325            final int numInfos = infos == null ? 0: infos.size();
326            ArrayList<ComponentName> cns = new ArrayList<>();
327            for (int i = 0; i < numInfos; i++) {
328                InstrumentationInfo info = infos.get(i);
329
330                ComponentName c = new ComponentName(info.packageName, info.name);
331                if (cnArg.equals(info.packageName)) {
332                    cns.add(c);
333                }
334            }
335
336            if (cns.size() == 0) {
337                throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
338            } else if (cns.size() == 1) {
339                return cns.get(0);
340            } else {
341                StringBuilder cnsStr = new StringBuilder();
342                final int numCns = cns.size();
343                for (int i = 0; i < numCns; i++) {
344                    cnsStr.append(cns.get(i).flattenToString());
345                    cnsStr.append(", ");
346                }
347
348                // Remove last ", "
349                cnsStr.setLength(cnsStr.length() - 2);
350
351                throw new IllegalArgumentException("Found multiple instrumentations: "
352                        + cnsStr.toString());
353            }
354        }
355    }
356
357    /**
358     * Run the instrumentation.
359     */
360    public void run() throws Exception {
361        StatusReporter reporter = null;
362        float[] oldAnims = null;
363
364        try {
365            // Choose which output we will do.
366            if (proto) {
367                reporter = new ProtoStatusReporter();
368            } else if (wait) {
369                reporter = new TextStatusReporter(rawMode);
370            }
371
372            // Choose whether we have to wait for the results.
373            InstrumentationWatcher watcher = null;
374            UiAutomationConnection connection = null;
375            if (reporter != null) {
376                watcher = new InstrumentationWatcher(reporter);
377                connection = new UiAutomationConnection();
378            }
379
380            // Set the window animation if necessary
381            if (noWindowAnimation) {
382                oldAnims = mWm.getAnimationScales();
383                mWm.setAnimationScale(0, 0.0f);
384                mWm.setAnimationScale(1, 0.0f);
385                mWm.setAnimationScale(2, 0.0f);
386            }
387
388            // Figure out which component we are tring to do.
389            final ComponentName cn = parseComponentName(componentNameArg);
390
391            // Choose an ABI if necessary
392            if (abi != null) {
393                final String[] supportedAbis = Build.SUPPORTED_ABIS;
394                boolean matched = false;
395                for (String supportedAbi : supportedAbis) {
396                    if (supportedAbi.equals(abi)) {
397                        matched = true;
398                        break;
399                    }
400                }
401                if (!matched) {
402                    throw new AndroidException(
403                            "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
404                }
405            }
406
407            // Start the instrumentation
408            if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId,
409                        abi)) {
410                throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
411            }
412
413            // If we have been requested to wait, do so until the instrumentation is finished.
414            if (watcher != null) {
415                if (!watcher.waitForFinish()) {
416                    reporter.onError("INSTRUMENTATION_ABORTED: System has crashed.", false);
417                    return;
418                }
419            }
420        } catch (Exception ex) {
421            // Report failures
422            if (reporter != null) {
423                reporter.onError(ex.getMessage(), true);
424            }
425
426            // And re-throw the exception
427            throw ex;
428        } finally {
429            // Clean up
430            if (oldAnims != null) {
431                mWm.setAnimationScales(oldAnims);
432            }
433        }
434    }
435}
436
437