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 static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; 20 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE; 21 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS; 22 import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART; 23 24 import android.app.IActivityManager; 25 import android.app.IInstrumentationWatcher; 26 import android.app.Instrumentation; 27 import android.app.UiAutomationConnection; 28 import android.content.ComponentName; 29 import android.content.pm.IPackageManager; 30 import android.content.pm.InstrumentationInfo; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.os.Environment; 34 import android.os.ServiceManager; 35 import android.os.UserHandle; 36 import android.util.AndroidException; 37 import android.util.proto.ProtoOutputStream; 38 import android.view.IWindowManager; 39 40 import java.io.File; 41 import java.io.FileOutputStream; 42 import java.io.IOException; 43 import java.io.InputStreamReader; 44 import java.io.OutputStream; 45 import java.text.SimpleDateFormat; 46 import java.util.ArrayList; 47 import java.util.Collection; 48 import java.util.Collections; 49 import java.util.Date; 50 import java.util.List; 51 import java.util.Locale; 52 53 54 /** 55 * Runs the am instrument command 56 * 57 * Test Result Code: 58 * 1 - Test running 59 * 0 - Test passed 60 * -2 - assertion failure 61 * -1 - other exceptions 62 * 63 * Session Result Code: 64 * -1: Success 65 * other: Failure 66 */ 67 public class Instrument { 68 private static final String TAG = "am"; 69 70 public static final String DEFAULT_LOG_DIR = "instrument-logs"; 71 72 private static final int STATUS_TEST_PASSED = 0; 73 private static final int STATUS_TEST_STARTED = 1; 74 private static final int STATUS_TEST_FAILED_ASSERTION = -1; 75 private static final int STATUS_TEST_FAILED_OTHER = -2; 76 77 private final IActivityManager mAm; 78 private final IPackageManager mPm; 79 private final IWindowManager mWm; 80 81 // Command line arguments 82 public String profileFile = null; 83 public boolean wait = false; 84 public boolean rawMode = false; 85 boolean protoStd = false; // write proto to stdout 86 boolean protoFile = false; // write proto to a file 87 String logPath = null; 88 public boolean noWindowAnimation = false; 89 public boolean disableHiddenApiChecks = false; 90 public boolean disableTestApiChecks = true; 91 public boolean disableIsolatedStorage = false; 92 public String abi = null; 93 public boolean noRestart = false; 94 public int userId = UserHandle.USER_CURRENT; 95 public Bundle args = new Bundle(); 96 // Required 97 public String componentNameArg; 98 99 /** 100 * Construct the instrument command runner. 101 */ Instrument(IActivityManager am, IPackageManager pm)102 public Instrument(IActivityManager am, IPackageManager pm) { 103 mAm = am; 104 mPm = pm; 105 mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); 106 } 107 108 /** 109 * Base class for status reporting. 110 * 111 * All the methods on this interface are called within the synchronized block 112 * of the InstrumentationWatcher, so calls are in order. However, that means 113 * you must be careful not to do blocking operations because you don't know 114 * exactly the locking dependencies. 115 */ 116 private interface StatusReporter { 117 /** 118 * Status update for tests. 119 */ onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)120 public void onInstrumentationStatusLocked(ComponentName name, int resultCode, 121 Bundle results); 122 123 /** 124 * The tests finished. 125 */ onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)126 public void onInstrumentationFinishedLocked(ComponentName name, int resultCode, 127 Bundle results); 128 129 /** 130 * @param errorText a description of the error 131 * @param commandError True if the error is related to the commandline, as opposed 132 * to a test failing. 133 */ onError(String errorText, boolean commandError)134 public void onError(String errorText, boolean commandError); 135 } 136 sorted(Collection<String> list)137 private static Collection<String> sorted(Collection<String> list) { 138 final ArrayList<String> copy = new ArrayList<>(list); 139 Collections.sort(copy); 140 return copy; 141 } 142 143 /** 144 * Printer for the 'classic' text based status reporting. 145 */ 146 private class TextStatusReporter implements StatusReporter { 147 private boolean mRawMode; 148 149 /** 150 * Human-ish readable output. 151 * 152 * @param rawMode In "raw mode" (true), all bundles are dumped. 153 * In "pretty mode" (false), if a bundle includes 154 * Instrumentation.REPORT_KEY_STREAMRESULT, just print that. 155 */ TextStatusReporter(boolean rawMode)156 public TextStatusReporter(boolean rawMode) { 157 mRawMode = rawMode; 158 } 159 160 @Override onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)161 public void onInstrumentationStatusLocked(ComponentName name, int resultCode, 162 Bundle results) { 163 // pretty printer mode? 164 String pretty = null; 165 if (!mRawMode && results != null) { 166 pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); 167 } 168 if (pretty != null) { 169 System.out.print(pretty); 170 } else { 171 if (results != null) { 172 for (String key : sorted(results.keySet())) { 173 System.out.println( 174 "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key)); 175 } 176 } 177 System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode); 178 } 179 } 180 181 @Override onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)182 public void onInstrumentationFinishedLocked(ComponentName name, int resultCode, 183 Bundle results) { 184 // pretty printer mode? 185 String pretty = null; 186 if (!mRawMode && results != null) { 187 pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT); 188 } 189 if (pretty != null) { 190 System.out.println(pretty); 191 } else { 192 if (results != null) { 193 for (String key : sorted(results.keySet())) { 194 System.out.println( 195 "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key)); 196 } 197 } 198 System.out.println("INSTRUMENTATION_CODE: " + resultCode); 199 } 200 } 201 202 @Override onError(String errorText, boolean commandError)203 public void onError(String errorText, boolean commandError) { 204 if (mRawMode) { 205 System.out.println("onError: commandError=" + commandError + " message=" 206 + errorText); 207 } 208 // The regular BaseCommand error printing will print the commandErrors. 209 if (!commandError) { 210 System.out.println(errorText); 211 } 212 } 213 } 214 215 /** 216 * Printer for the protobuf based status reporting. 217 */ 218 private class ProtoStatusReporter implements StatusReporter { 219 220 private File mLog; 221 222 private long mTestStartMs; 223 ProtoStatusReporter()224 ProtoStatusReporter() { 225 if (protoFile) { 226 if (logPath == null) { 227 File logDir = new File(Environment.getLegacyExternalStorageDirectory(), 228 DEFAULT_LOG_DIR); 229 if (!logDir.exists() && !logDir.mkdirs()) { 230 System.err.format("Unable to create log directory: %s\n", 231 logDir.getAbsolutePath()); 232 protoFile = false; 233 return; 234 } 235 SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-hhmmss-SSS", Locale.US); 236 String fileName = String.format("log-%s.instrumentation_data_proto", 237 format.format(new Date())); 238 mLog = new File(logDir, fileName); 239 } else { 240 mLog = new File(Environment.getLegacyExternalStorageDirectory(), logPath); 241 File logDir = mLog.getParentFile(); 242 if (!logDir.exists() && !logDir.mkdirs()) { 243 System.err.format("Unable to create log directory: %s\n", 244 logDir.getAbsolutePath()); 245 protoFile = false; 246 return; 247 } 248 } 249 if (mLog.exists()) mLog.delete(); 250 } 251 } 252 253 @Override onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)254 public void onInstrumentationStatusLocked(ComponentName name, int resultCode, 255 Bundle results) { 256 final ProtoOutputStream proto = new ProtoOutputStream(); 257 258 final long testStatusToken = proto.start(InstrumentationData.Session.TEST_STATUS); 259 260 proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode); 261 writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results); 262 263 if (resultCode == STATUS_TEST_STARTED) { 264 // Logcat -T takes wall clock time (!?) 265 mTestStartMs = System.currentTimeMillis(); 266 } else { 267 if (mTestStartMs > 0) { 268 proto.write(InstrumentationData.TestStatus.LOGCAT, readLogcat(mTestStartMs)); 269 } 270 mTestStartMs = 0; 271 } 272 273 proto.end(testStatusToken); 274 275 outputProto(proto); 276 } 277 278 @Override onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)279 public void onInstrumentationFinishedLocked(ComponentName name, int resultCode, 280 Bundle results) { 281 final ProtoOutputStream proto = new ProtoOutputStream(); 282 283 final long sessionStatusToken = proto.start(InstrumentationData.Session.SESSION_STATUS); 284 proto.write(InstrumentationData.SessionStatus.STATUS_CODE, 285 InstrumentationData.SESSION_FINISHED); 286 proto.write(InstrumentationData.SessionStatus.RESULT_CODE, resultCode); 287 writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results); 288 proto.end(sessionStatusToken); 289 290 outputProto(proto); 291 } 292 293 @Override onError(String errorText, boolean commandError)294 public void onError(String errorText, boolean commandError) { 295 final ProtoOutputStream proto = new ProtoOutputStream(); 296 297 final long sessionStatusToken = proto.start(InstrumentationData.Session.SESSION_STATUS); 298 proto.write(InstrumentationData.SessionStatus.STATUS_CODE, 299 InstrumentationData.SESSION_ABORTED); 300 proto.write(InstrumentationData.SessionStatus.ERROR_TEXT, errorText); 301 proto.end(sessionStatusToken); 302 303 outputProto(proto); 304 } 305 writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle)306 private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) { 307 final long bundleToken = proto.start(fieldId); 308 309 for (final String key: sorted(bundle.keySet())) { 310 final long entryToken = proto.startRepeatedObject( 311 InstrumentationData.ResultsBundle.ENTRIES); 312 313 proto.write(InstrumentationData.ResultsBundleEntry.KEY, key); 314 315 final Object val = bundle.get(key); 316 if (val instanceof String) { 317 proto.write(InstrumentationData.ResultsBundleEntry.VALUE_STRING, 318 (String)val); 319 } else if (val instanceof Byte) { 320 proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, 321 ((Byte)val).intValue()); 322 } else if (val instanceof Double) { 323 proto.write(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE, (double)val); 324 } else if (val instanceof Float) { 325 proto.write(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT, (float)val); 326 } else if (val instanceof Integer) { 327 proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (int)val); 328 } else if (val instanceof Long) { 329 proto.write(InstrumentationData.ResultsBundleEntry.VALUE_LONG, (long)val); 330 } else if (val instanceof Short) { 331 proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (short)val); 332 } else if (val instanceof Bundle) { 333 writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE, 334 (Bundle)val); 335 } else if (val instanceof byte[]) { 336 proto.write(InstrumentationData.ResultsBundleEntry.VALUE_BYTES, (byte[])val); 337 } 338 339 proto.end(entryToken); 340 } 341 342 proto.end(bundleToken); 343 } 344 outputProto(ProtoOutputStream proto)345 private void outputProto(ProtoOutputStream proto) { 346 byte[] out = proto.getBytes(); 347 if (protoStd) { 348 try { 349 System.out.write(out); 350 System.out.flush(); 351 } catch (IOException ex) { 352 System.err.println("Error writing finished response: "); 353 ex.printStackTrace(System.err); 354 } 355 } 356 if (protoFile) { 357 try (OutputStream os = new FileOutputStream(mLog, true)) { 358 os.write(proto.getBytes()); 359 os.flush(); 360 } catch (IOException ex) { 361 System.err.format("Cannot write to %s:\n", mLog.getAbsolutePath()); 362 ex.printStackTrace(); 363 } 364 } 365 } 366 } 367 368 369 /** 370 * Callbacks from the remote instrumentation instance. 371 */ 372 private class InstrumentationWatcher extends IInstrumentationWatcher.Stub { 373 private final StatusReporter mReporter; 374 375 private boolean mFinished = false; 376 InstrumentationWatcher(StatusReporter reporter)377 public InstrumentationWatcher(StatusReporter reporter) { 378 mReporter = reporter; 379 } 380 381 @Override instrumentationStatus(ComponentName name, int resultCode, Bundle results)382 public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) { 383 synchronized (this) { 384 mReporter.onInstrumentationStatusLocked(name, resultCode, results); 385 notifyAll(); 386 } 387 } 388 389 @Override instrumentationFinished(ComponentName name, int resultCode, Bundle results)390 public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) { 391 synchronized (this) { 392 mReporter.onInstrumentationFinishedLocked(name, resultCode, results); 393 mFinished = true; 394 notifyAll(); 395 } 396 } 397 waitForFinish()398 public boolean waitForFinish() { 399 synchronized (this) { 400 while (!mFinished) { 401 try { 402 if (!mAm.asBinder().pingBinder()) { 403 return false; 404 } 405 wait(1000); 406 } catch (InterruptedException e) { 407 throw new IllegalStateException(e); 408 } 409 } 410 } 411 return true; 412 } 413 } 414 415 /** 416 * Figure out which component they really meant. 417 */ parseComponentName(String cnArg)418 private ComponentName parseComponentName(String cnArg) throws Exception { 419 if (cnArg.contains("/")) { 420 ComponentName cn = ComponentName.unflattenFromString(cnArg); 421 if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg); 422 return cn; 423 } else { 424 List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList(); 425 426 final int numInfos = infos == null ? 0: infos.size(); 427 ArrayList<ComponentName> cns = new ArrayList<>(); 428 for (int i = 0; i < numInfos; i++) { 429 InstrumentationInfo info = infos.get(i); 430 431 ComponentName c = new ComponentName(info.packageName, info.name); 432 if (cnArg.equals(info.packageName)) { 433 cns.add(c); 434 } 435 } 436 437 if (cns.size() == 0) { 438 throw new IllegalArgumentException("No instrumentation found for: " + cnArg); 439 } else if (cns.size() == 1) { 440 return cns.get(0); 441 } else { 442 StringBuilder cnsStr = new StringBuilder(); 443 final int numCns = cns.size(); 444 for (int i = 0; i < numCns; i++) { 445 cnsStr.append(cns.get(i).flattenToString()); 446 cnsStr.append(", "); 447 } 448 449 // Remove last ", " 450 cnsStr.setLength(cnsStr.length() - 2); 451 452 throw new IllegalArgumentException("Found multiple instrumentations: " 453 + cnsStr.toString()); 454 } 455 } 456 } 457 458 /** 459 * Run the instrumentation. 460 */ run()461 public void run() throws Exception { 462 StatusReporter reporter = null; 463 float[] oldAnims = null; 464 465 try { 466 // Choose which output we will do. 467 if (protoFile || protoStd) { 468 reporter = new ProtoStatusReporter(); 469 } else if (wait) { 470 reporter = new TextStatusReporter(rawMode); 471 } 472 473 // Choose whether we have to wait for the results. 474 InstrumentationWatcher watcher = null; 475 UiAutomationConnection connection = null; 476 if (reporter != null) { 477 watcher = new InstrumentationWatcher(reporter); 478 connection = new UiAutomationConnection(); 479 } 480 481 // Set the window animation if necessary 482 if (noWindowAnimation) { 483 oldAnims = mWm.getAnimationScales(); 484 mWm.setAnimationScale(0, 0.0f); 485 mWm.setAnimationScale(1, 0.0f); 486 mWm.setAnimationScale(2, 0.0f); 487 } 488 489 // Figure out which component we are trying to do. 490 final ComponentName cn = parseComponentName(componentNameArg); 491 492 // Choose an ABI if necessary 493 if (abi != null) { 494 final String[] supportedAbis = Build.SUPPORTED_ABIS; 495 boolean matched = false; 496 for (String supportedAbi : supportedAbis) { 497 if (supportedAbi.equals(abi)) { 498 matched = true; 499 break; 500 } 501 } 502 if (!matched) { 503 throw new AndroidException( 504 "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi); 505 } 506 } 507 508 // Start the instrumentation 509 int flags = 0; 510 if (disableHiddenApiChecks) { 511 flags |= INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; 512 } 513 if (disableTestApiChecks) { 514 flags |= INSTR_FLAG_DISABLE_TEST_API_CHECKS; 515 } 516 if (disableIsolatedStorage) { 517 flags |= INSTR_FLAG_DISABLE_ISOLATED_STORAGE; 518 } 519 if (noRestart) { 520 flags |= INSTR_FLAG_NO_RESTART; 521 } 522 if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId, 523 abi)) { 524 throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString()); 525 } 526 527 // If we have been requested to wait, do so until the instrumentation is finished. 528 if (watcher != null) { 529 if (!watcher.waitForFinish()) { 530 reporter.onError("INSTRUMENTATION_ABORTED: System has crashed.", false); 531 return; 532 } 533 } 534 } catch (Exception ex) { 535 // Report failures 536 if (reporter != null) { 537 reporter.onError(ex.getMessage(), true); 538 } 539 540 // And re-throw the exception 541 throw ex; 542 } finally { 543 // Clean up 544 if (oldAnims != null) { 545 mWm.setAnimationScales(oldAnims); 546 } 547 } 548 } 549 readLogcat(long startTimeMs)550 private static String readLogcat(long startTimeMs) { 551 try { 552 // Figure out the timestamp arg for logcat. 553 final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 554 final String timestamp = format.format(new Date(startTimeMs)); 555 556 // Start the process 557 final Process process = new ProcessBuilder() 558 .command("logcat", "-d", "-v", "threadtime,uid", "-T", timestamp) 559 .start(); 560 561 // Nothing to write. Don't let the command accidentally block. 562 process.getOutputStream().close(); 563 564 // Read the output 565 final StringBuilder str = new StringBuilder(); 566 final InputStreamReader reader = new InputStreamReader(process.getInputStream()); 567 char[] buffer = new char[4096]; 568 int amt; 569 while ((amt = reader.read(buffer, 0, buffer.length)) >= 0) { 570 if (amt > 0) { 571 str.append(buffer, 0, amt); 572 } 573 } 574 575 try { 576 process.waitFor(); 577 } catch (InterruptedException ex) { 578 // We already have the text, drop the exception. 579 } 580 581 return str.toString(); 582 583 } catch (IOException ex) { 584 return "Error reading logcat command:\n" + ex.toString(); 585 } 586 } 587 } 588 589