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