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