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