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