Home | History | Annotate | Download | only in appsecurity
      1 /*
      2  * Copyright (C) 2009 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.cts.appsecurity;
     18 
     19 import com.android.cts.tradefed.build.CtsBuildHelper;
     20 import com.android.ddmlib.IDevice;
     21 import com.android.ddmlib.Log;
     22 import com.android.ddmlib.testrunner.InstrumentationResultParser;
     23 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
     24 import com.android.ddmlib.testrunner.TestIdentifier;
     25 import com.android.tradefed.build.IBuildInfo;
     26 import com.android.tradefed.device.DeviceNotAvailableException;
     27 import com.android.tradefed.device.ITestDevice;
     28 import com.android.tradefed.result.CollectingTestListener;
     29 import com.android.tradefed.result.TestResult;
     30 import com.android.tradefed.result.TestResult.TestStatus;
     31 import com.android.tradefed.result.TestRunResult;
     32 import com.android.tradefed.testtype.DeviceTestCase;
     33 import com.android.tradefed.testtype.IBuildReceiver;
     34 
     35 import java.io.File;
     36 import java.io.FileNotFoundException;
     37 import java.util.Map;
     38 
     39 /**
     40  * Set of tests that verify various security checks involving multiple apps are properly enforced.
     41  */
     42 public class AppSecurityTests extends DeviceTestCase implements IBuildReceiver {
     43 
     44     // testSharedUidDifferentCerts constants
     45     private static final String SHARED_UI_APK = "CtsSharedUidInstall.apk";
     46     private static final String SHARED_UI_PKG = "com.android.cts.shareuidinstall";
     47     private static final String SHARED_UI_DIFF_CERT_APK = "CtsSharedUidInstallDiffCert.apk";
     48     private static final String SHARED_UI_DIFF_CERT_PKG =
     49         "com.android.cts.shareuidinstalldiffcert";
     50 
     51     // testAppUpgradeDifferentCerts constants
     52     private static final String SIMPLE_APP_APK = "CtsSimpleAppInstall.apk";
     53     private static final String SIMPLE_APP_PKG = "com.android.cts.simpleappinstall";
     54     private static final String SIMPLE_APP_DIFF_CERT_APK = "CtsSimpleAppInstallDiffCert.apk";
     55 
     56     // testAppFailAccessPrivateData constants
     57     private static final String APP_WITH_DATA_APK = "CtsAppWithData.apk";
     58     private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata";
     59     private static final String APP_WITH_DATA_CLASS =
     60             "com.android.cts.appwithdata.CreatePrivateDataTest";
     61     private static final String APP_WITH_DATA_CREATE_METHOD =
     62             "testCreatePrivateData";
     63     private static final String APP_WITH_DATA_CHECK_NOEXIST_METHOD =
     64             "testEnsurePrivateDataNotExist";
     65     private static final String APP_ACCESS_DATA_APK = "CtsAppAccessData.apk";
     66     private static final String APP_ACCESS_DATA_PKG = "com.android.cts.appaccessdata";
     67 
     68     // External storage constants
     69     private static final String EXTERNAL_STORAGE_APP_APK = "CtsExternalStorageApp.apk";
     70     private static final String EXTERNAL_STORAGE_APP_PKG = "com.android.cts.externalstorageapp";
     71     private static final String EXTERNAL_STORAGE_APP_CLASS = EXTERNAL_STORAGE_APP_PKG
     72             + ".ExternalStorageTest";
     73     private static final String WRITE_EXTERNAL_STORAGE_APP_APK = "CtsWriteExternalStorageApp.apk";
     74     private static final String
     75             WRITE_EXTERNAL_STORAGE_APP_PKG = "com.android.cts.writeexternalstorageapp";
     76     private static final String WRITE_EXTERNAL_STORAGE_APP_CLASS = WRITE_EXTERNAL_STORAGE_APP_PKG
     77             + ".WriteExternalStorageTest";
     78 
     79     // testInstrumentationDiffCert constants
     80     private static final String TARGET_INSTRUMENT_APK = "CtsTargetInstrumentationApp.apk";
     81     private static final String TARGET_INSTRUMENT_PKG = "com.android.cts.targetinstrumentationapp";
     82     private static final String INSTRUMENT_DIFF_CERT_APK = "CtsInstrumentationAppDiffCert.apk";
     83     private static final String INSTRUMENT_DIFF_CERT_PKG =
     84         "com.android.cts.instrumentationdiffcertapp";
     85 
     86     // testPermissionDiffCert constants
     87     private static final String DECLARE_PERMISSION_APK = "CtsPermissionDeclareApp.apk";
     88     private static final String DECLARE_PERMISSION_PKG = "com.android.cts.permissiondeclareapp";
     89     private static final String DECLARE_PERMISSION_COMPAT_APK = "CtsPermissionDeclareAppCompat.apk";
     90     private static final String DECLARE_PERMISSION_COMPAT_PKG = "com.android.cts.permissiondeclareappcompat";
     91 
     92     private static final String PERMISSION_DIFF_CERT_APK = "CtsUsePermissionDiffCert.apk";
     93     private static final String PERMISSION_DIFF_CERT_PKG =
     94         "com.android.cts.usespermissiondiffcertapp";
     95 
     96     private static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
     97 
     98     private static final String MULTIUSER_STORAGE_APK = "CtsMultiUserStorageApp.apk";
     99     private static final String MULTIUSER_STORAGE_PKG = "com.android.cts.multiuserstorageapp";
    100     private static final String MULTIUSER_STORAGE_CLASS = MULTIUSER_STORAGE_PKG
    101             + ".MultiUserStorageTest";
    102 
    103     private static final String LOG_TAG = "AppSecurityTests";
    104 
    105     private CtsBuildHelper mCtsBuild;
    106 
    107     /**
    108      * {@inheritDoc}
    109      */
    110     @Override
    111     public void setBuild(IBuildInfo buildInfo) {
    112         mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
    113     }
    114 
    115     private File getTestAppFile(String fileName) throws FileNotFoundException {
    116         return mCtsBuild.getTestApp(fileName);
    117     }
    118 
    119     @Override
    120     protected void setUp() throws Exception {
    121         super.setUp();
    122         // ensure build has been set before test is run
    123         assertNotNull(mCtsBuild);
    124     }
    125 
    126     /**
    127      * Test that an app that declares the same shared uid as an existing app, cannot be installed
    128      * if it is signed with a different certificate.
    129      */
    130     public void testSharedUidDifferentCerts() throws Exception {
    131         Log.i(LOG_TAG, "installing apks with shared uid, but different certs");
    132         try {
    133             // cleanup test apps that might be installed from previous partial test run
    134             getDevice().uninstallPackage(SHARED_UI_PKG);
    135             getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG);
    136 
    137             String installResult = getDevice().installPackage(getTestAppFile(SHARED_UI_APK),
    138                     false);
    139             assertNull(String.format("failed to install shared uid app, Reason: %s", installResult),
    140                     installResult);
    141             installResult = getDevice().installPackage(getTestAppFile(SHARED_UI_DIFF_CERT_APK),
    142                     false);
    143             assertNotNull("shared uid app with different cert than existing app installed " +
    144                     "successfully", installResult);
    145             assertEquals("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE", installResult);
    146         }
    147         finally {
    148             getDevice().uninstallPackage(SHARED_UI_PKG);
    149             getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG);
    150         }
    151     }
    152 
    153     /**
    154      * Test that an app update cannot be installed over an existing app if it has a different
    155      * certificate.
    156      */
    157     public void testAppUpgradeDifferentCerts() throws Exception {
    158         Log.i(LOG_TAG, "installing app upgrade with different certs");
    159         try {
    160             // cleanup test app that might be installed from previous partial test run
    161             getDevice().uninstallPackage(SIMPLE_APP_PKG);
    162 
    163             String installResult = getDevice().installPackage(getTestAppFile(SIMPLE_APP_APK),
    164                     false);
    165             assertNull(String.format("failed to install simple app. Reason: %s", installResult),
    166                     installResult);
    167             installResult = getDevice().installPackage(getTestAppFile(SIMPLE_APP_DIFF_CERT_APK),
    168                     true /* reinstall */);
    169             assertNotNull("app upgrade with different cert than existing app installed " +
    170                     "successfully", installResult);
    171             assertEquals("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES", installResult);
    172         }
    173         finally {
    174             getDevice().uninstallPackage(SIMPLE_APP_PKG);
    175         }
    176     }
    177 
    178     /**
    179      * Test that an app cannot access another app's private data.
    180      */
    181     public void testAppFailAccessPrivateData() throws Exception {
    182         Log.i(LOG_TAG, "installing app that attempts to access another app's private data");
    183         try {
    184             // cleanup test app that might be installed from previous partial test run
    185             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
    186             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
    187 
    188             String installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK),
    189                     false);
    190             assertNull(String.format("failed to install app with data. Reason: %s", installResult),
    191                     installResult);
    192             // run appwithdata's tests to create private data
    193             assertTrue("failed to create app's private data", runDeviceTests(APP_WITH_DATA_PKG,
    194                     APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD));
    195 
    196             installResult = getDevice().installPackage(getTestAppFile(APP_ACCESS_DATA_APK),
    197                     false);
    198             assertNull(String.format("failed to install app access data. Reason: %s",
    199                     installResult), installResult);
    200             // run appaccessdata's tests which attempt to access appwithdata's private data
    201             assertTrue("could access app's private data", runDeviceTests(APP_ACCESS_DATA_PKG));
    202         }
    203         finally {
    204             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
    205             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
    206         }
    207     }
    208 
    209     /**
    210      * Test behavior when
    211      * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} is unenforced.
    212      */
    213     public void testReadExternalStorageUnenforced() throws Exception {
    214         try {
    215             getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
    216             getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
    217 
    218             // stage test file on external storage
    219             getDevice().pushString("CAEK",
    220                     getDevice().getMountPoint(IDevice.MNT_EXTERNAL_STORAGE) + "/meow");
    221 
    222             // mark permission as not enforced
    223             setPermissionEnforced(getDevice(), READ_EXTERNAL_STORAGE, false);
    224 
    225             // install apps and run test
    226             assertNull(getDevice()
    227                     .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false));
    228             assertNull(getDevice()
    229                     .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false));
    230 
    231             // normal app should be able to read
    232             assertTrue("Normal app unable to read external storage", runDeviceTests(
    233                     EXTERNAL_STORAGE_APP_PKG, EXTERNAL_STORAGE_APP_CLASS,
    234                     "testReadExternalStorage"));
    235 
    236             // WRITE_EXTERNAL app should be able to read and write
    237             assertTrue("WRITE_EXTERNAL app unable to read external storage", runDeviceTests(
    238                     WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS,
    239                     "testReadExternalStorage"));
    240             assertTrue("WRITE_EXTERNAL app unable to write external storage", runDeviceTests(
    241                     WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS,
    242                     "testWriteExternalStorage"));
    243 
    244         } finally {
    245             getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
    246             getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
    247         }
    248     }
    249 
    250     /**
    251      * Verify that legacy filesystem paths continue working, and that they all
    252      * point to same location.
    253      */
    254     public void testExternalStorageLegacyPaths() throws Exception {
    255         try {
    256             getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
    257             assertNull(getDevice()
    258                     .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false));
    259 
    260             assertTrue("Failed to verify legacy filesystem paths", runDeviceTests(
    261                     WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS,
    262                     "testLegacyPaths"));
    263 
    264         } finally {
    265             getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
    266         }
    267     }
    268 
    269     /**
    270      * Test that uninstall of an app removes its private data.
    271      */
    272     public void testUninstallRemovesData() throws Exception {
    273         Log.i(LOG_TAG, "Uninstalling app, verifying data is removed.");
    274         try {
    275             // cleanup test app that might be installed from previous partial test run
    276             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
    277 
    278             String installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK),
    279                     false);
    280             assertNull(String.format("failed to install app with data. Reason: %s", installResult),
    281                     installResult);
    282             // run appwithdata's tests to create private data
    283             assertTrue("failed to create app's private data", runDeviceTests(APP_WITH_DATA_PKG,
    284                     APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD));
    285 
    286             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
    287 
    288             installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK),
    289                     false);
    290             assertNull(String.format("failed to install app with data second time. Reason: %s",
    291                     installResult), installResult);
    292             // run appwithdata's 'check if file exists' test
    293             assertTrue("app's private data still exists after install", runDeviceTests(
    294                     APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CHECK_NOEXIST_METHOD));
    295 
    296         }
    297         finally {
    298             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
    299         }
    300     }
    301 
    302     /**
    303      * Test that an app cannot instrument another app that is signed with different certificate.
    304      */
    305     public void testInstrumentationDiffCert() throws Exception {
    306         Log.i(LOG_TAG, "installing app that attempts to instrument another app");
    307         try {
    308             // cleanup test app that might be installed from previous partial test run
    309             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
    310             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
    311 
    312             String installResult = getDevice().installPackage(
    313                     getTestAppFile(TARGET_INSTRUMENT_APK), false);
    314             assertNull(String.format("failed to install target instrumentation app. Reason: %s",
    315                     installResult), installResult);
    316 
    317             // the app will install, but will get error at runtime when starting instrumentation
    318             installResult = getDevice().installPackage(getTestAppFile(INSTRUMENT_DIFF_CERT_APK),
    319                     false);
    320             assertNull(String.format(
    321                     "failed to install instrumentation app with diff cert. Reason: %s",
    322                     installResult), installResult);
    323             // run INSTRUMENT_DIFF_CERT_PKG tests
    324             // this test will attempt to call startInstrumentation directly and verify
    325             // SecurityException is thrown
    326             assertTrue("running instrumentation with diff cert unexpectedly succeeded",
    327                     runDeviceTests(INSTRUMENT_DIFF_CERT_PKG));
    328         }
    329         finally {
    330             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
    331             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
    332         }
    333     }
    334 
    335     /**
    336      * Test that an app cannot use a signature-enforced permission if it is signed with a different
    337      * certificate than the app that declared the permission.
    338      */
    339     public void testPermissionDiffCert() throws Exception {
    340         Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
    341         try {
    342             // cleanup test app that might be installed from previous partial test run
    343             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
    344             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
    345             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
    346 
    347             String installResult = getDevice().installPackage(
    348                     getTestAppFile(DECLARE_PERMISSION_APK), false);
    349             assertNull(String.format("failed to install declare permission app. Reason: %s",
    350                     installResult), installResult);
    351 
    352             installResult = getDevice().installPackage(
    353                     getTestAppFile(DECLARE_PERMISSION_COMPAT_APK), false);
    354             assertNull(String.format("failed to install declare permission compat app. Reason: %s",
    355                     installResult), installResult);
    356 
    357             // the app will install, but will get error at runtime
    358             installResult = getDevice().installPackage(getTestAppFile(PERMISSION_DIFF_CERT_APK),
    359                     false);
    360             assertNull(String.format("failed to install permission app with diff cert. Reason: %s",
    361                     installResult), installResult);
    362             // run PERMISSION_DIFF_CERT_PKG tests which try to access the permission
    363             TestRunResult result = doRunTests(PERMISSION_DIFF_CERT_PKG, null, null);
    364             assertDeviceTestsPass(result);
    365         }
    366         finally {
    367             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
    368             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
    369             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
    370         }
    371     }
    372 
    373     /**
    374      * Test multi-user emulated storage environment, ensuring that each user has
    375      * isolated storage.
    376      */
    377     public void testMultiUserStorage() throws Exception {
    378         final String PACKAGE = MULTIUSER_STORAGE_PKG;
    379         final String CLAZZ = MULTIUSER_STORAGE_CLASS;
    380 
    381         if (!isMultiUserSupportedOnDevice(getDevice())) {
    382             Log.d(LOG_TAG, "Single user device; skipping isolated storage tests");
    383             return;
    384         }
    385 
    386         int owner = 0;
    387         int secondary = -1;
    388         try {
    389             // Create secondary user
    390             secondary = createUserOnDevice(getDevice());
    391 
    392             // Install our test app
    393             getDevice().uninstallPackage(MULTIUSER_STORAGE_PKG);
    394             final String installResult = getDevice()
    395                     .installPackage(getTestAppFile(MULTIUSER_STORAGE_APK), false);
    396             assertNull("Failed to install: " + installResult, installResult);
    397 
    398             // Clear data from previous tests
    399             assertDeviceTestsPass(
    400                     doRunTestsAsUser(PACKAGE, CLAZZ, "cleanIsolatedStorage", owner));
    401             assertDeviceTestsPass(
    402                     doRunTestsAsUser(PACKAGE, CLAZZ, "cleanIsolatedStorage", secondary));
    403 
    404             // Have both users try writing into isolated storage
    405             assertDeviceTestsPass(
    406                     doRunTestsAsUser(PACKAGE, CLAZZ, "writeIsolatedStorage", owner));
    407             assertDeviceTestsPass(
    408                     doRunTestsAsUser(PACKAGE, CLAZZ, "writeIsolatedStorage", secondary));
    409 
    410             // Verify they both have isolated view of storage
    411             assertDeviceTestsPass(
    412                     doRunTestsAsUser(PACKAGE, CLAZZ, "readIsolatedStorage", owner));
    413             assertDeviceTestsPass(
    414                     doRunTestsAsUser(PACKAGE, CLAZZ, "readIsolatedStorage", secondary));
    415         } finally {
    416             getDevice().uninstallPackage(MULTIUSER_STORAGE_PKG);
    417             if (secondary != -1) {
    418                 removeUserOnDevice(getDevice(), secondary);
    419             }
    420         }
    421     }
    422 
    423     /**
    424      * Helper method that checks that all tests in given result passed, and attempts to generate
    425      * a meaningful error message if they failed.
    426      *
    427      * @param result
    428      */
    429     private void assertDeviceTestsPass(TestRunResult result) {
    430         // TODO: consider rerunning if this occurred
    431         assertFalse(String.format("Failed to successfully run device tests for %s. Reason: %s",
    432                 result.getName(), result.getRunFailureMessage()), result.isRunFailure());
    433 
    434         if (result.hasFailedTests()) {
    435             // build a meaningful error message
    436             StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
    437             for (Map.Entry<TestIdentifier, TestResult> resultEntry :
    438                 result.getTestResults().entrySet()) {
    439                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
    440                     errorBuilder.append(resultEntry.getKey().toString());
    441                     errorBuilder.append(":\n");
    442                     errorBuilder.append(resultEntry.getValue().getStackTrace());
    443                 }
    444             }
    445             fail(errorBuilder.toString());
    446         }
    447     }
    448 
    449     /**
    450      * Helper method that will the specified packages tests on device.
    451      *
    452      * @param pkgName Android application package for tests
    453      * @return <code>true</code> if all tests passed.
    454      * @throws DeviceNotAvailableException if connection to device was lost.
    455      */
    456     private boolean runDeviceTests(String pkgName) throws DeviceNotAvailableException {
    457         return runDeviceTests(pkgName, null, null);
    458     }
    459 
    460     /**
    461      * Helper method that will the specified packages tests on device.
    462      *
    463      * @param pkgName Android application package for tests
    464      * @return <code>true</code> if all tests passed.
    465      * @throws DeviceNotAvailableException if connection to device was lost.
    466      */
    467     private boolean runDeviceTests(String pkgName, String testClassName, String testMethodName)
    468             throws DeviceNotAvailableException {
    469         TestRunResult runResult = doRunTests(pkgName, testClassName, testMethodName);
    470         return !runResult.hasFailedTests();
    471     }
    472 
    473     /**
    474      * Helper method to run tests and return the listener that collected the results.
    475      *
    476      * @param pkgName Android application package for tests
    477      * @return the {@link TestRunResult}
    478      * @throws DeviceNotAvailableException if connection to device was lost.
    479      */
    480     private TestRunResult doRunTests(String pkgName, String testClassName,
    481             String testMethodName) throws DeviceNotAvailableException {
    482 
    483         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName,
    484                 getDevice().getIDevice());
    485         if (testClassName != null && testMethodName != null) {
    486             testRunner.setMethodName(testClassName, testMethodName);
    487         }
    488         CollectingTestListener listener = new CollectingTestListener();
    489         getDevice().runInstrumentationTests(testRunner, listener);
    490         return listener.getCurrentRunResults();
    491     }
    492 
    493     private static boolean isMultiUserSupportedOnDevice(ITestDevice device)
    494             throws DeviceNotAvailableException {
    495         // TODO: move this to ITestDevice once it supports users
    496         final String output = device.executeShellCommand("pm get-max-users");
    497         try {
    498             return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim()) > 1;
    499         } catch (NumberFormatException e) {
    500             fail("Failed to parse result: " + output);
    501         }
    502         return false;
    503     }
    504 
    505     private static int createUserOnDevice(ITestDevice device) throws DeviceNotAvailableException {
    506         // TODO: move this to ITestDevice once it supports users
    507         final String name = "CTS_" + System.currentTimeMillis();
    508         final String output = device.executeShellCommand("pm create-user " + name);
    509         if (output.startsWith("Success")) {
    510             try {
    511                 return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
    512             } catch (NumberFormatException e) {
    513                 fail("Failed to parse result: " + output);
    514             }
    515         } else {
    516             fail("Failed to create user: " + output);
    517         }
    518         throw new IllegalStateException();
    519     }
    520 
    521     private static void removeUserOnDevice(ITestDevice device, int userId)
    522             throws DeviceNotAvailableException {
    523         // TODO: move this to ITestDevice once it supports users
    524         final String output = device.executeShellCommand("pm remove-user " + userId);
    525         if (output.startsWith("Error")) {
    526             fail("Failed to remove user: " + output);
    527         }
    528     }
    529 
    530     private TestRunResult doRunTestsAsUser(
    531             String pkgName, String testClassName, String testMethodName, int userId)
    532             throws DeviceNotAvailableException {
    533         // TODO: move this to RemoteAndroidTestRunner once it supports users
    534         final String cmd = "am instrument --user " + userId + " -w -r -e class " + testClassName
    535                 + "#" + testMethodName + " " + pkgName + "/android.test.InstrumentationTestRunner";
    536         Log.i(LOG_TAG, "Running " + cmd + " on " + getDevice().getSerialNumber());
    537 
    538         CollectingTestListener listener = new CollectingTestListener();
    539         InstrumentationResultParser parser = new InstrumentationResultParser(pkgName, listener);
    540 
    541         getDevice().executeShellCommand(cmd, parser);
    542         return listener.getCurrentRunResults();
    543     }
    544 
    545     private static void setPermissionEnforced(
    546             ITestDevice device, String permission, boolean enforced)
    547             throws DeviceNotAvailableException {
    548         device.executeShellCommand("pm set-permission-enforced " + permission + " "
    549                 + Boolean.toString(enforced));
    550     }
    551 }
    552