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