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