diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 47e7a37e9352b..d9467a5e1712b 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -23,7 +23,10 @@ package {
java_test_host {
name: "PackageManagerServiceHostTests",
- srcs: ["src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
libs: [
"tradefed",
"junit",
@@ -34,11 +37,17 @@ java_test_host {
"cts-host-utils",
"frameworks-base-hostutils",
"PackageManagerServiceHostTestsIntentVerifyUtils",
+ "block_device_writer_jar",
],
test_suites: ["general-tests"],
data: [
":PackageManagerTestApex",
":PackageManagerTestApexApp",
+ ":PackageManagerServiceServerTests",
+ ],
+ data_device_bins_both: [
+ "block_device_writer",
+ "fsverity_multilib",
],
java_resources: [
":PackageManagerTestOverlayActor",
diff --git a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
index f584599e81006..2382548192ad3 100644
--- a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
+++ b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
@@ -27,6 +27,22 @@
value="pm uninstall com.android.cts.install.lib.testapp.A" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SettingsTest.java b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SettingsTest.java
new file mode 100644
index 0000000000000..a66906e5cd1eb
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SettingsTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.blockdevicewriter.BlockDeviceWriter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * atest com.android.server.pm.test.SettingsTest
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SettingsTest extends BaseHostJUnit4Test {
+ private static final String DEVICE_TEST_PACKAGE_NAME =
+ "com.android.server.pm.test.service.server";
+ private static final String TEST_CLASS_NAME =
+ "com.android.server.pm.PackageManagerSettingsDeviceTests";
+
+ private static final String FSVERITY_EXECUTABLE = "/data/local/tmp/fsverity_multilib";
+ private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";
+
+ @Test
+ public void testWriteCorruptHeaderBinaryXml() throws Exception {
+ // Corrupt 1st page, this will trigger error in initial resolve* call.
+ performTest("testWriteBinaryXmlSettings", 0);
+ }
+
+ @Test
+ public void testWriteCorruptHeaderTextXml() throws Exception {
+ // Corrupt 1st page, this will trigger error in initial resolve* call.
+ performTest("testWriteTextXmlSettings", 0);
+ }
+
+ @Test
+ public void testWriteCorruptDataBinaryXml() throws Exception {
+ // Corrupt 2nd page, this will trigger error in the XML parser.
+ performTest("testWriteBinaryXmlSettings", 1);
+ }
+
+ @Test
+ public void testWriteCorruptDataTextXml() throws Exception {
+ // Corrupt 2nd page, this will trigger error in the XML parser.
+ performTest("testWriteTextXmlSettings", 1);
+ }
+
+ private void performTest(String writeTest, int pageToCorrupt) throws Exception {
+ assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+ writeTest));
+
+ int userId = getDevice().getCurrentUser();
+ File filesDir = new File(
+ "/data/user/" + userId + "/" + DEVICE_TEST_PACKAGE_NAME + "/files");
+ File packagesXml = new File(filesDir, "system/packages.xml");
+
+ // Make sure fs-verity is enabled.
+ enableFsVerity(packagesXml.getAbsolutePath());
+
+ // Damage the file directly against the block device.
+ BlockDeviceWriter.damageFileAgainstBlockDevice(getDevice(), packagesXml.getAbsolutePath(),
+ pageToCorrupt * 4096 + 1);
+ BlockDeviceWriter.dropCaches(getDevice());
+
+ assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+ "testReadSettings"));
+ }
+
+ private void enableFsVerity(String path) throws Exception {
+ ITestDevice device = getDevice();
+ ArrayList args = new ArrayList<>();
+ args.add(FSVERITY_EXECUTABLE);
+ args.add("enable");
+ args.add(path);
+
+ String cmd = String.join(" ", args);
+ CommandResult result = device.executeShellV2Command(cmd);
+ assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
+ result.getStatus());
+ }
+
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsDeviceTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsDeviceTests.java
new file mode 100644
index 0000000000000..e43f40d7dfe10
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsDeviceTests.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.PropertyInvalidatedCache;
+import android.os.Message;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.permission.persistence.RuntimePermissionsPersistence;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.LegacyPermissionDataProvider;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.UUID;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PackageManagerSettingsDeviceTests {
+ @Mock
+ RuntimePermissionsPersistence mRuntimePermissionsPersistence;
+ @Mock
+ LegacyPermissionDataProvider mPermissionDataProvider;
+ @Mock
+ DomainVerificationManagerInternal mDomainVerificationManager;
+ @Mock
+ Computer mComputer;
+
+ final TestHandler mHandler = new TestHandler((@NonNull Message msg) -> {
+ return true;
+ });
+
+ @Before
+ public void initializeMocks() {
+ MockitoAnnotations.initMocks(this);
+ when(mDomainVerificationManager.generateNewId())
+ .thenAnswer(invocation -> UUID.randomUUID());
+ }
+
+ @Before
+ public void setup() {
+ // Disable binder caches in this process.
+ PropertyInvalidatedCache.disableForTestMode();
+ }
+
+ @Before
+ public void createUserManagerServiceRef() throws ReflectiveOperationException {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync((Runnable) () -> {
+ try {
+ // unregister the user manager from the local service
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ new UserManagerService(InstrumentationRegistry.getContext());
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Could not create user manager service; " + e);
+ }
+ });
+ }
+
+ // Write valid packages.xml, compare with the reserve copy.
+ @Test
+ public void testWriteBinaryXmlSettings() throws Exception {
+ // write out files and read
+ PackageManagerSettingsTests.writeOldFiles();
+ Settings settings = makeSettings();
+ assertTrue(settings.readLPw(mComputer, PackageManagerSettingsTests.createFakeUsers()));
+
+ // write out
+ settings.writeLPr(mComputer, /*sync=*/true);
+
+ File filesDir = InstrumentationRegistry.getContext().getFilesDir();
+ File packageXml = new File(filesDir, "system/packages.xml");
+ File packagesReserveCopyXml = new File(filesDir, "system/packages.xml.reservecopy");
+ // Primary.
+ assertTrue(packageXml.exists());
+ // For the test, we need a file with at least 2 pages.
+ assertThat(packageXml.length(), greaterThan(4096L));
+ // Reserve copy.
+ assertTrue(packagesReserveCopyXml.exists());
+ // Temporary backup.
+ assertFalse(new File(filesDir, "packages-backup.xml").exists());
+
+ // compare two copies, make sure they are the same
+ assertTrue(Arrays.equals(Files.readAllBytes(Path.of(packageXml.getAbsolutePath())),
+ Files.readAllBytes(Path.of(packagesReserveCopyXml.getAbsolutePath()))));
+ }
+
+ @Test
+ public void testWriteTextXmlSettings() throws Exception {
+ testWriteBinaryXmlSettings();
+
+ PackageManagerSettingsTests.writePackagesXml("system/packages.xml");
+
+ File filesDir = InstrumentationRegistry.getContext().getFilesDir();
+ File packageXml = new File(filesDir, "system/packages.xml");
+
+ assertTrue(packageXml.exists());
+ // For the test, we need a file with at least 2 pages.
+ assertThat(packageXml.length(), greaterThan(4096L));
+ }
+
+ // Read settings, verify.
+ @Test
+ public void testReadSettings() throws Exception {
+ // This test can be run separately. In this case packages.xml is missing.
+ assumeTrue(new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "system/packages.xml").exists());
+ Settings settings = makeSettings();
+ assertTrue(settings.readLPw(mComputer, PackageManagerSettingsTests.createFakeUsers()));
+ PackageManagerSettingsTests.verifyKeySetMetaData(settings);
+ }
+
+ private Settings makeSettings() {
+ return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
+ mRuntimePermissionsPersistence, mPermissionDataProvider,
+ mDomainVerificationManager, mHandler,
+ new PackageManagerTracedLock());
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 6a1ccc50df584..75484d162282c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -56,7 +56,6 @@ import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.Log;
import android.util.LongSparseArray;
import androidx.test.InstrumentationRegistry;
@@ -108,7 +107,6 @@ import java.util.concurrent.CountDownLatch;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PackageManagerSettingsTests {
- private static final String TAG = "PackageManagerSettingsTests";
private static final String PACKAGE_NAME_1 = "com.android.app1";
private static final String PACKAGE_NAME_2 = "com.android.app2";
private static final String PACKAGE_NAME_3 = "com.android.app3";
@@ -142,6 +140,25 @@ public class PackageManagerSettingsTests {
PropertyInvalidatedCache.disableForTestMode();
}
+ @Before
+ public void createUserManagerServiceRef() throws ReflectiveOperationException {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync((Runnable) () -> {
+ try {
+ // unregister the user manager from the local service
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ new UserManagerService(InstrumentationRegistry.getContext());
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Could not create user manager service; " + e);
+ }
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ deleteFolder(InstrumentationRegistry.getContext().getFilesDir());
+ }
+
/** make sure our initialized KeySetManagerService metadata matches packages.xml */
@Test
public void testReadKeySetSettings() throws Exception {
@@ -1612,13 +1629,13 @@ public class PackageManagerSettingsTests {
UUID.randomUUID());
}
- private @NonNull List createFakeUsers() {
+ static @NonNull List createFakeUsers() {
ArrayList users = new ArrayList<>();
users.add(new UserInfo(UserHandle.USER_SYSTEM, "test user", UserInfo.FLAG_INITIALIZED));
return users;
}
- private void writeFile(File file, byte[] data) {
+ private static void writeFile(File file, byte[] data) {
file.mkdirs();
try {
AtomicFile aFile = new AtomicFile(file);
@@ -1626,7 +1643,7 @@ public class PackageManagerSettingsTests {
fos.write(data);
aFile.finishWrite(fos);
} catch (IOException ioe) {
- Log.e(TAG, "Cannot write file " + file.getPath());
+ throw new RuntimeException("Cannot write file " + file.getPath(), ioe);
}
}
@@ -1640,7 +1657,7 @@ public class PackageManagerSettingsTests {
).getBytes());
}
- private void writePackagesXml(String fileName) {
+ static void writePackagesXml(String fileName) {
writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), fileName),
(""
+ ""
@@ -1748,7 +1765,7 @@ public class PackageManagerSettingsTests {
.getBytes());
}
- private void writeStoppedPackagesXml() {
+ private static void writeStoppedPackagesXml() {
writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"),
( ""
+ ""
@@ -1758,7 +1775,7 @@ public class PackageManagerSettingsTests {
.getBytes());
}
- private void writePackagesList() {
+ private static void writePackagesList() {
writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.list"),
( "com.android.app1 11000 0 /data/data/com.android.app1 seinfo1"
+ "com.android.app2 11001 0 /data/data/com.android.app2 seinfo2"
@@ -1766,7 +1783,7 @@ public class PackageManagerSettingsTests {
.getBytes());
}
- private void deleteSystemFolder() {
+ private static void deleteSystemFolder() {
File systemFolder = new File(InstrumentationRegistry.getContext().getFilesDir(), "system");
deleteFolder(systemFolder);
}
@@ -1781,7 +1798,7 @@ public class PackageManagerSettingsTests {
folder.delete();
}
- private void writeOldFiles() {
+ static void writeOldFiles() {
deleteSystemFolder();
writePackagesXml("system/packages.xml");
writeStoppedPackagesXml();
@@ -1795,25 +1812,6 @@ public class PackageManagerSettingsTests {
writePackagesList();
}
- @Before
- public void createUserManagerServiceRef() throws ReflectiveOperationException {
- InstrumentationRegistry.getInstrumentation().runOnMainSync((Runnable) () -> {
- try {
- // unregister the user manager from the local service
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- new UserManagerService(InstrumentationRegistry.getContext());
- } catch (Exception e) {
- e.printStackTrace();
- fail("Could not create user manager service; " + e);
- }
- });
- }
-
- @After
- public void tearDown() throws Exception {
- deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
- }
-
private Settings makeSettings() {
return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider,
@@ -1821,7 +1819,7 @@ public class PackageManagerSettingsTests {
new PackageManagerTracedLock());
}
- private void verifyKeySetMetaData(Settings settings)
+ static void verifyKeySetMetaData(Settings settings)
throws ReflectiveOperationException, IllegalAccessException {
WatchedArrayMap packages = settings.mPackages;
KeySetManagerService ksms = settings.getKeySetManagerService();