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