Home | History | Annotate | Download | only in am
      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      */
     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          */
    108         public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
    109                 Bundle results);
    110 
    111         /**
    112          * The tests finished.
    113          */
    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          */
    122         public void onError(String errorText, boolean commandError);
    123     }
    124 
    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          */
    144         public TextStatusReporter(boolean rawMode) {
    145             mRawMode = rawMode;
    146         }
    147 
    148         @Override
    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
    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
    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 
    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
    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
    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
    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 
    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 
    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 
    351         public InstrumentationWatcher(StatusReporter reporter) {
    352             mReporter = reporter;
    353         }
    354 
    355         @Override
    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
    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 
    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      */
    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      */
    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