1 /* 2 * Copyright (C) 2020 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.tests.apex; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import static org.junit.Assume.assumeTrue; 23 24 import android.cts.install.lib.host.InstallUtilsHost; 25 26 import com.android.apex.ApexInfo; 27 import com.android.apex.XmlParser; 28 import com.android.tests.rollback.host.AbandonSessionsRule; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 31 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 32 33 import org.junit.After; 34 import org.junit.Before; 35 import org.junit.Rule; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 39 import java.io.File; 40 import java.io.FileInputStream; 41 import java.time.Duration; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.stream.Collectors; 45 46 /** 47 * Host side integration tests for apexd. 48 */ 49 @RunWith(DeviceJUnit4ClassRunner.class) 50 public class ApexdHostTest extends BaseHostJUnit4Test { 51 52 private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex"; 53 54 private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this); 55 56 @Rule 57 public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this); 58 59 private boolean mWasAdbRoot = false; 60 61 @Before setUp()62 public void setUp() throws Exception { 63 mHostUtils.uninstallShimApexIfNecessary(); 64 mWasAdbRoot = getDevice().isAdbRoot(); 65 if (!mWasAdbRoot) { 66 assumeTrue("Device requires root", getDevice().enableAdbRoot()); 67 } 68 } 69 70 @After tearDown()71 public void tearDown() throws Exception { 72 mHostUtils.uninstallShimApexIfNecessary(); 73 if (!mWasAdbRoot) { 74 getDevice().disableAdbRoot(); 75 } 76 } 77 deviceHasActiveApex(String apexName)78 private boolean deviceHasActiveApex(String apexName) throws Exception { 79 return getDevice().getActiveApexes().stream().anyMatch( 80 apex -> apex.name.equals(apexName)); 81 } 82 83 @Test testOrphanedApexIsNotActivated()84 public void testOrphanedApexIsNotActivated() throws Exception { 85 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 86 assumeTrue("Device requires root", getDevice().isAdbRoot()); 87 try { 88 assertThat(getDevice().pushFile(mHostUtils.getTestFile("apex.apexd_test_v2.apex"), 89 "/data/apex/active/apexd_test_v2.apex")).isTrue(); 90 getDevice().reboot(); 91 assertWithMessage("Timed out waiting for device to boot").that( 92 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 93 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 94 ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo( 95 "com.android.apex.test_package", 2L); 96 assertThat(activeApexes).doesNotContain(testApex); 97 mHostUtils.waitForFileDeleted("/data/apex/active/apexd_test_v2.apex", 98 Duration.ofMinutes(3)); 99 } finally { 100 getDevice().executeShellV2Command("rm /data/apex/active/apexd_test_v2.apex"); 101 } 102 } 103 @Test testApexWithoutPbIsNotActivated()104 public void testApexWithoutPbIsNotActivated() throws Exception { 105 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 106 assumeTrue("Device requires root", getDevice().isAdbRoot()); 107 final String testApexFile = "com.android.apex.cts.shim.v2_no_pb.apex"; 108 try { 109 assertThat(getDevice().pushFile(mHostUtils.getTestFile(testApexFile), 110 "/data/apex/active/" + testApexFile)).isTrue(); 111 getDevice().reboot(); 112 assertWithMessage("Timed out waiting for device to boot").that( 113 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 114 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 115 ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo( 116 "com.android.apex.cts.shim", 2L); 117 assertThat(activeApexes).doesNotContain(testApex); 118 mHostUtils.waitForFileDeleted("/data/apex/active/" + testApexFile, 119 Duration.ofMinutes(3)); 120 } finally { 121 getDevice().executeShellV2Command("rm /data/apex/active/" + testApexFile); 122 } 123 } 124 125 @Test testRemountApex()126 public void testRemountApex() throws Exception { 127 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 128 assumeTrue("Device requires root", getDevice().isAdbRoot()); 129 final File oldFile = getDevice().pullFile(SHIM_APEX_PATH); 130 try { 131 getDevice().remountSystemWritable(); 132 // In case remount requires a reboot, wait for boot to complete. 133 getDevice().waitForBootComplete(Duration.ofMinutes(3).toMillis()); 134 final File newFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex"); 135 // Stop framework 136 getDevice().executeShellV2Command("stop"); 137 // Push new shim APEX. This simulates adb sync. 138 getDevice().pushFile(newFile, SHIM_APEX_PATH); 139 // Ask apexd to remount packages 140 getDevice().executeShellV2Command("cmd -w apexservice remountPackages"); 141 // Start framework 142 getDevice().executeShellV2Command("start"); 143 // Give enough time for system_server to boot. 144 Thread.sleep(Duration.ofSeconds(15).toMillis()); 145 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 146 ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo( 147 "com.android.apex.cts.shim", 2L); 148 assertThat(activeApexes).contains(testApex); 149 } finally { 150 getDevice().pushFile(oldFile, SHIM_APEX_PATH); 151 getDevice().reboot(); 152 } 153 } 154 155 @Test testApexWithoutPbIsNotActivated_ProductPartitionHasOlderVersion()156 public void testApexWithoutPbIsNotActivated_ProductPartitionHasOlderVersion() 157 throws Exception { 158 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 159 assumeTrue("Device requires root", getDevice().isAdbRoot()); 160 161 try { 162 getDevice().remountSystemWritable(); 163 // In case remount requires a reboot, wait for boot to complete. 164 assertWithMessage("Timed out waiting for device to boot").that( 165 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 166 167 final File v1 = mHostUtils.getTestFile("apex.apexd_test.apex"); 168 getDevice().pushFile(v1, "/product/apex/apex.apexd_test.apex"); 169 170 final File v2_no_pb = mHostUtils.getTestFile("apex.apexd_test_v2_no_pb.apex"); 171 getDevice().pushFile(v2_no_pb, "/data/apex/active/apex.apexd_test_v2_no_pb.apex"); 172 173 getDevice().reboot(); 174 assertWithMessage("Timed out waiting for device to boot").that( 175 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 176 177 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 178 assertThat(activeApexes).contains(new ITestDevice.ApexInfo( 179 "com.android.apex.test_package", 1L)); 180 assertThat(activeApexes).doesNotContain(new ITestDevice.ApexInfo( 181 "com.android.apex.test_package", 2L)); 182 183 // v2_no_pb should be deleted 184 mHostUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex", 185 Duration.ofMinutes(3)); 186 } finally { 187 getDevice().remountSystemWritable(); 188 assertWithMessage("Timed out waiting for device to boot").that( 189 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 190 191 getDevice().executeShellV2Command("rm /product/apex/apex.apexd_test.apex"); 192 getDevice().executeShellV2Command("rm /data/apex/active/apex.apexd_test_v2_no_pb.apex"); 193 } 194 } 195 196 @Test testApexWithoutPbIsNotActivated_ProductPartitionHasNewerVersion()197 public void testApexWithoutPbIsNotActivated_ProductPartitionHasNewerVersion() 198 throws Exception { 199 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 200 assumeTrue("Device requires root", getDevice().isAdbRoot()); 201 202 try { 203 getDevice().remountSystemWritable(); 204 // In case remount requires a reboot, wait for boot to complete. 205 assertWithMessage("Timed out waiting for device to boot").that( 206 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 207 208 final File v3 = mHostUtils.getTestFile("apex.apexd_test_v3.apex"); 209 getDevice().pushFile(v3, "/product/apex/apex.apexd_test_v3.apex"); 210 211 final File v2_no_pb = mHostUtils.getTestFile("apex.apexd_test_v2_no_pb.apex"); 212 getDevice().pushFile(v2_no_pb, "/data/apex/active/apex.apexd_test_v2_no_pb.apex"); 213 214 getDevice().reboot(); 215 assertWithMessage("Timed out waiting for device to boot").that( 216 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 217 218 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 219 assertThat(activeApexes).contains(new ITestDevice.ApexInfo( 220 "com.android.apex.test_package", 3L)); 221 assertThat(activeApexes).doesNotContain(new ITestDevice.ApexInfo( 222 "com.android.apex.test_package", 2L)); 223 224 // v2_no_pb should be deleted 225 mHostUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex", 226 Duration.ofMinutes(3)); 227 } finally { 228 getDevice().remountSystemWritable(); 229 assertWithMessage("Timed out waiting for device to boot").that( 230 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 231 232 getDevice().executeShellV2Command("rm /product/apex/apex.apexd_test_v3.apex"); 233 getDevice().executeShellV2Command("rm /data/apex/active/apex.apexd_test_v2_no_pb.apex"); 234 } 235 } 236 237 @Test testApexInfoListIsValid()238 public void testApexInfoListIsValid() throws Exception { 239 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 240 assumeTrue("Device requires root", getDevice().isAdbRoot()); 241 242 try (FileInputStream fis = new FileInputStream( 243 getDevice().pullFile("/apex/apex-info-list.xml"))) { 244 // #1 Data got from apexd via binder 245 Set<ITestDevice.ApexInfo> fromApexd = getDevice().getActiveApexes(); 246 // #2 Data got from the xml file (key is the path) 247 Map<String, ApexInfo> fromXml = XmlParser.readApexInfoList(fis).getApexInfo().stream() 248 .collect(Collectors.toMap(ApexInfo::getModulePath, ai -> ai)); 249 250 // Make sure that all items in #1 are also in #2 and they are identical 251 for (ITestDevice.ApexInfo ai : fromApexd) { 252 ApexInfo apexFromXml = fromXml.get(ai.sourceDir); 253 assertWithMessage("APEX (" + ai.toString() + ") is not found in the list") 254 .that(apexFromXml).isNotNull(); 255 assertWithMessage("Version mismatch for APEX (" + ai.toString() + ")") 256 .that(ai.versionCode).isEqualTo(apexFromXml.getVersionCode()); 257 assertWithMessage("APEX (" + ai.toString() + ") is not active") 258 .that(apexFromXml.getIsActive()).isTrue(); 259 } 260 } 261 } 262 263 /** 264 * Test to verify that the state of a staged session does not change if apexd is stopped and 265 * restarted while a session is staged. 266 */ 267 @Test testApexSessionStateUnchangedBeforeReboot()268 public void testApexSessionStateUnchangedBeforeReboot() throws Exception { 269 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 270 assumeTrue("Device requires root", getDevice().isAdbRoot()); 271 272 File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex"); 273 String error = mHostUtils.installStagedPackage(apexFile); 274 assertThat(error).isNull(); 275 String sessionId = getDevice().executeShellCommand( 276 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim(); 277 assertThat(sessionId).isNotEmpty(); 278 String sessionStateCmd = "cmd -w apexservice getStagedSessionInfo " + sessionId; 279 String initialState = getDevice().executeShellV2Command(sessionStateCmd).getStdout(); 280 assertThat(initialState).isNotEmpty(); 281 282 // Kill apexd. This means apexd will perform its start logic when the second install 283 // is staged. 284 getDevice().executeShellV2Command("kill `pidof apexd`"); 285 286 // Verify that the session state remains consistent after apexd has restarted. 287 String updatedState = getDevice().executeShellV2Command(sessionStateCmd).getStdout(); 288 assertThat(updatedState).isEqualTo(initialState); 289 } 290 291 /** 292 * Verifies that content of {@code /data/apex/sessions/} is migrated to the {@code 293 * /metadata/apex/sessions}. 294 */ 295 @Test testSessionsDirMigrationToMetadata()296 public void testSessionsDirMigrationToMetadata() throws Exception { 297 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 298 assumeTrue("Device requires root", getDevice().isAdbRoot()); 299 300 try { 301 getDevice().executeShellV2Command("mkdir -p /data/apex/sessions/1543"); 302 File file = File.createTempFile("foo", "bar"); 303 getDevice().pushFile(file, "/data/apex/sessions/1543/file"); 304 305 // During boot sequence apexd will move /data/apex/sessions/1543/file to 306 // /metadata/apex/sessions/1543/file. 307 getDevice().reboot(); 308 assertWithMessage("Timed out waiting for device to boot").that( 309 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 310 311 assertThat(getDevice().doesFileExist("/metadata/apex/sessions/1543/file")).isTrue(); 312 assertThat(getDevice().doesFileExist("/data/apex/sessions/1543/file")).isFalse(); 313 } finally { 314 getDevice().executeShellV2Command("rm -R /data/apex/sessions/1543"); 315 getDevice().executeShellV2Command("rm -R /metadata/apex/sessions/1543"); 316 } 317 } 318 319 @Test testFailsToActivateApexOnDataFallbacksToPreInstalled()320 public void testFailsToActivateApexOnDataFallbacksToPreInstalled() throws Exception { 321 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 322 assumeTrue("Device requires root", getDevice().isAdbRoot()); 323 324 try { 325 final File file = 326 mHostUtils.getTestFile("com.android.apex.cts.shim.v2_additional_file.apex"); 327 getDevice().pushFile(file, "/data/apex/active/com.android.apex.cts.shim@2.apex"); 328 329 getDevice().reboot(); 330 assertWithMessage("Timed out waiting for device to boot").that( 331 getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); 332 333 // After reboot pre-installed version of shim apex should be activated, and corrupted 334 // version on /data should be deleted. 335 final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes(); 336 ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo( 337 "com.android.apex.cts.shim", 1L); 338 assertThat(activeApexes).contains(testApex); 339 assertThat( 340 getDevice() 341 .doesFileExist("/data/apex/active/com.android.apex.cts.shim@2.apex")) 342 .isFalse(); 343 } finally { 344 getDevice().deleteFile("/data/apex/active/com.android.apex.cts.shim@2.apex"); 345 } 346 } 347 348 /** 349 * Test to verify that apexd will reject a vendor apex update with an 350 * updatable-via-apex value that doesn't match the apex's interface. 351 * Install method is reboot-needing (staged) installation. 352 */ 353 @Test testRejectsStagedApexWithIncorrectUpdatableViaApexValue()354 public void testRejectsStagedApexWithIncorrectUpdatableViaApexValue() throws Exception { 355 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 356 assumeTrue("Device requires root", getDevice().isAdbRoot()); 357 assumeTrue("Device test requires wifi hardware", 358 getDevice().hasFeature("android.hardware.wifi")); 359 assumeTrue("Device test requires an active wifi apex", 360 deviceHasActiveApex("com.android.hardware.wifi")); 361 362 String apex_filename = "test.bad1.com.android.hardware.wifi.apex"; 363 364 File apexFile = mHostUtils.getTestFile(apex_filename); 365 366 // Try to install it, we should get an error 367 String error = mHostUtils.installStagedPackage(apexFile); 368 assertThat(error).isNotNull(); 369 // Verify error text involves the device manifest (Note the actual 370 // parser error is visible in the log, but it doesn't get passed 371 // up through libvintf, so we end up with a manifest-related error) 372 assertThat(error).contains("No device manifest"); 373 } 374 375 /** 376 * Test to verify that apexd will reject a vendor apex update with a 377 * vintf fragment containing syntax-invalid XML. Staged installation. 378 */ 379 @Test testRejectsStagedApexWithInvalidSyntaxVintfFragment()380 public void testRejectsStagedApexWithInvalidSyntaxVintfFragment() throws Exception { 381 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 382 assumeTrue("Device requires root", getDevice().isAdbRoot()); 383 assumeTrue("Device test requires wifi hardware", 384 getDevice().hasFeature("android.hardware.wifi")); 385 assumeTrue("Device test requires an active wifi apex", 386 deviceHasActiveApex("com.android.hardware.wifi")); 387 388 String apex_filename = "test.bad2.com.android.hardware.wifi.apex"; 389 390 File apexFile = mHostUtils.getTestFile(apex_filename); 391 392 // Try to install it, we should get our device manifest error 393 String error = mHostUtils.installStagedPackage(apexFile); 394 assertThat(error).isNotNull(); 395 assertThat(error).contains("No device manifest"); 396 } 397 398 /** 399 * Test to verify that apexd will reject a vendor apex that tries to 400 * update an unrelated hardware interface. Staged installation. 401 */ 402 @Test testRejectsStagedApexThatUpdatesUnrelatedHardware()403 public void testRejectsStagedApexThatUpdatesUnrelatedHardware() throws Exception { 404 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 405 assumeTrue("Device requires root", getDevice().isAdbRoot()); 406 assumeTrue("Device test requires wifi hardware", 407 getDevice().hasFeature("android.hardware.wifi")); 408 assumeTrue("Device test requires an active wifi apex", 409 deviceHasActiveApex("com.android.hardware.wifi")); 410 411 String apex_filename = "test.bad3.com.android.hardware.wifi.apex"; 412 413 File apexFile = mHostUtils.getTestFile(apex_filename); 414 415 // Try to install it, we should get a manifest/matix compatibility error 416 String error = mHostUtils.installStagedPackage(apexFile); 417 assertThat(error).isNotNull(); 418 assertThat(error).contains( 419 "Device manifest and framework compatibility matrix are incompatible"); 420 } 421 422 /** 423 * Test to verify that apexd will accept a good vendor apex update 424 * Install method is immediate (rebootless) (non-staged) installation. 425 */ 426 @Test testAcceptsGoodRebootlessApex()427 public void testAcceptsGoodRebootlessApex() throws Exception { 428 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 429 assumeTrue("Device requires root", getDevice().isAdbRoot()); 430 assumeTrue("Device test requires wifi hardware", 431 getDevice().hasFeature("android.hardware.wifi")); 432 assumeTrue("Device test requires an active wifi apex", 433 deviceHasActiveApex("com.android.hardware.wifi")); 434 435 String apex_filename = "test.good1.com.android.hardware.wifi.apex"; 436 437 File apexFile = mHostUtils.getTestFile(apex_filename); 438 439 // Try to install it, we should get an error 440 String error = mHostUtils.installRebootlessPackage(apexFile); 441 assertThat(error).isNull(); 442 } 443 444 /** 445 * Test to verify that apexd will reject a vendor apex update with an 446 * updatable-via-apex value that doesn't match the apex's interface. 447 * Install method is immediate (rebootless) (non-staged) installation. 448 */ 449 @Test testRejectsRebootlessApexWithIncorrectUpdatableViaApexValue()450 public void testRejectsRebootlessApexWithIncorrectUpdatableViaApexValue() throws Exception { 451 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 452 assumeTrue("Device requires root", getDevice().isAdbRoot()); 453 assumeTrue("Device test requires wifi hardware", 454 getDevice().hasFeature("android.hardware.wifi")); 455 assumeTrue("Device test requires an active wifi apex", 456 deviceHasActiveApex("com.android.hardware.wifi")); 457 458 String apex_filename = "test.bad1.com.android.hardware.wifi.apex"; 459 460 File apexFile = mHostUtils.getTestFile(apex_filename); 461 462 // Try to install it, we should get an error 463 String error = mHostUtils.installRebootlessPackage(apexFile); 464 assertThat(error).isNotNull(); 465 // Verify error text involves the device manifest (Note the actual 466 // parser error is visible in the log, but it doesn't get passed 467 // up through libvintf, so we end up with a manifest-related error) 468 assertThat(error).contains("No device manifest"); 469 } 470 471 /** 472 * Test to verify that apexd will reject a vendor apex update with a 473 * vintf fragment containing syntax-invalid XML. 474 */ 475 @Test testRejectsRebootlessApexWithInvalidSyntaxVintfFragment()476 public void testRejectsRebootlessApexWithInvalidSyntaxVintfFragment() throws Exception { 477 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 478 assumeTrue("Device requires root", getDevice().isAdbRoot()); 479 assumeTrue("Device test requires wifi hardware", 480 getDevice().hasFeature("android.hardware.wifi")); 481 assumeTrue("Device test requires an active wifi apex", 482 deviceHasActiveApex("com.android.hardware.wifi")); 483 484 String apex_filename = "test.bad2.com.android.hardware.wifi.apex"; 485 486 File apexFile = mHostUtils.getTestFile(apex_filename); 487 488 // Try to install it, we should get our device manifest error 489 String error = mHostUtils.installRebootlessPackage(apexFile); 490 assertThat(error).isNotNull(); 491 assertThat(error).contains("No device manifest"); 492 } 493 494 /** 495 * Test to verify that apexd will reject a vendor apex tries to 496 * update an unrelated hardware interface. 497 */ 498 @Test testRejectsRebootlessApexThatUpdatesUnrelatedHardware()499 public void testRejectsRebootlessApexThatUpdatesUnrelatedHardware() throws Exception { 500 assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported()); 501 assumeTrue("Device requires root", getDevice().isAdbRoot()); 502 assumeTrue("Device test requires wifi hardware", 503 getDevice().hasFeature("android.hardware.wifi")); 504 assumeTrue("Device test requires an active wifi apex", 505 deviceHasActiveApex("com.android.hardware.wifi")); 506 507 String apex_filename = "test.bad3.com.android.hardware.wifi.apex"; 508 509 File apexFile = mHostUtils.getTestFile(apex_filename); 510 511 // Try to install it, we should get a manifest/matix compatibility error 512 String error = mHostUtils.installRebootlessPackage(apexFile); 513 assertThat(error).isNotNull(); 514 assertThat(error).contains( 515 "Device manifest and framework compatibility matrix are incompatible"); 516 } 517 } 518