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 
17 package com.android.commands.am;
18 
19 import android.app.IActivityManager;
20 import android.app.IInstrumentationWatcher;
21 import android.app.Instrumentation;
22 import android.app.UiAutomationConnection;
23 import android.content.ComponentName;
24 import android.content.pm.IPackageManager;
25 import android.content.pm.InstrumentationInfo;
26 import android.os.Build;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.os.UserHandle;
31 import android.util.AndroidException;
32 import android.util.proto.ProtoOutputStream;
33 import android.view.IWindowManager;
34 
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 
40 /**
41  * Runs the am instrument command
42  */
43 public 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      */
Instrument(IActivityManager am, IPackageManager pm)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          */
onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)81         public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
82                 Bundle results);
83 
84         /**
85          * The tests finished.
86          */
onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)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          */
onError(String errorText, boolean commandError)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          */
TextStatusReporter(boolean rawMode)111         public TextStatusReporter(boolean rawMode) {
112             mRawMode = rawMode;
113         }
114 
115         @Override
onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)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
onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)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
onError(String errorText, boolean commandError)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
onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)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
onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)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
onError(String errorText, boolean commandError)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 
writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle)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 
writeProtoToStdout(ProtoOutputStream proto)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 
InstrumentationWatcher(StatusReporter reporter)276         public InstrumentationWatcher(StatusReporter reporter) {
277             mReporter = reporter;
278         }
279 
280         @Override
instrumentationStatus(ComponentName name, int resultCode, Bundle results)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
instrumentationFinished(ComponentName name, int resultCode, Bundle results)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 
waitForFinish()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      */
parseComponentName(String cnArg)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      */
run()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