Merge "Reimplement ApkVerityTest" into main
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index ea21d51..ab669cc 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -48,9 +48,6 @@
             "name":"CarrierAppIntegrationTestCases"
         },
         {
-            "name":"ApkVerityTest"
-        },
-        {
             "name":"CtsSilentUpdateHostTestCases"
         },
         {
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index 2d598dc..0af3b03 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -12,10 +12,6 @@
       ]
     },
     {
-      "name": "ApkVerityTest",
-      "file_patterns": ["VerityUtils\\.java"]
-    },
-    {
       "name": "UpdatableSystemFontTest",
       "file_patterns": ["VerityUtils\\.java"]
     },
@@ -23,5 +19,11 @@
       "name": "CtsApkVerityInstallHostTestCases",
       "file_patterns": ["VerityUtils\\.java"]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "FsVerityTest",
+      "file_patterns": ["VerityUtils\\.java"]
+    }
   ]
 }
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index f026bea..53606a3 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -22,7 +22,7 @@
 }
 
 java_test_host {
-    name: "ApkVerityTest",
+    name: "FsVerityTest",
     srcs: ["src/**/*.java"],
     libs: [
         "tradefed",
@@ -30,8 +30,10 @@
         "compatibility-host-util",
     ],
     static_libs: [
+        "android.security.flags-aconfig-java-host",
         "block_device_writer_jar",
         "frameworks-base-hostutils",
+        "flag-junit-host",
     ],
     test_suites: [
         "general-tests",
@@ -41,14 +43,6 @@
         "block_device_writer",
     ],
     data: [
-        ":ApkVerityTestCertDer",
-        ":ApkVerityTestApp",
-        ":ApkVerityTestAppFsvSig",
-        ":ApkVerityTestAppDm",
-        ":ApkVerityTestAppDmFsvSig",
-        ":ApkVerityTestAppSplit",
-        ":ApkVerityTestAppSplitFsvSig",
-        ":ApkVerityTestAppSplitDm",
-        ":ApkVerityTestAppSplitDmFsvSig",
+        ":FsVerityTestApp",
     ],
 }
diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml
index 4487cef..49cbde0 100644
--- a/tests/ApkVerityTest/AndroidTest.xml
+++ b/tests/ApkVerityTest/AndroidTest.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="APK fs-verity integration/regression test">
+<configuration description="fs-verity end-to-end test">
     <option name="test-suite-tag" value="apct" />
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController">
@@ -24,19 +24,9 @@
     <!-- This test requires root to write against block device. -->
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
 
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <!-- Disable package verifier prevents it holding the target APK's fd that prevents cache
-             eviction. -->
-        <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
-        <option name="restore-settings" value="true" />
-
-        <!-- Skip in order to prevent reboot that confuses the test flow. -->
-        <option name="force-skip-system-props" value="true" />
-    </target_preparer>
-
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="ApkVerityTestCert.der->/data/local/tmp/ApkVerityTestCert.der" />
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FsVerityTestApp.apk"/>
+        <option name="cleanup-apks" value="true"/>
     </target_preparer>
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
@@ -48,9 +38,7 @@
         <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
     </target_preparer>
 
-    <!-- Skip on HWASan. TODO(b/232288278): Re-enable -->
-    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.SkipHWASanModuleController" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
-        <option name="jar" value="ApkVerityTest.jar" />
+        <option name="jar" value="FsVerityTest.jar" />
     </test>
 </configuration>
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml
deleted file mode 100644
index 3f1a4f3..0000000
--- a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  * Copyright (C) 2019 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.apkverity"
-    android:isFeatureSplit="true"
-    split="feature_x">
-    <application>
-        <activity android:name=".feature_x.DummyActivity"/>
-    </application>
-</manifest>
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java
deleted file mode 100644
index fe91260..0000000
--- a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2019 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.apkverity.feature_x;
-
-import android.app.Activity;
-
-/** Placeholder class just to generate some dex */
-public class DummyActivity extends Activity {}
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java b/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java
deleted file mode 100644
index a7bd771..0000000
--- a/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2019 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.apkverity;
-
-import android.app.Activity;
-
-/** Placeholder class just to generate some dex */
-public class DummyActivity extends Activity {}
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/Android.bp b/tests/ApkVerityTest/FsVerityTestApp/Android.bp
similarity index 74%
rename from tests/ApkVerityTest/ApkVerityTestApp/Android.bp
rename to tests/ApkVerityTest/FsVerityTestApp/Android.bp
index adf8f9f..43da3ff 100644
--- a/tests/ApkVerityTest/ApkVerityTestApp/Android.bp
+++ b/tests/ApkVerityTest/FsVerityTestApp/Android.bp
@@ -22,17 +22,8 @@
 }
 
 android_test_helper_app {
-  name: "ApkVerityTestApp",
-  manifest: "AndroidManifest.xml",
-  srcs: ["src/**/*.java"],
-}
-
-android_test_helper_app {
-  name: "ApkVerityTestAppSplit",
-  manifest: "feature_split/AndroidManifest.xml",
-  srcs: ["src/**/*.java"],
-  aaptflags: [
-      "--custom-package com.android.apkverity.feature_x",
-      "--package-id 0x80",
-  ],
+    name: "FsVerityTestApp",
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.java"],
+    static_libs: ["compatibility-device-util-axt"],
 }
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml b/tests/ApkVerityTest/FsVerityTestApp/AndroidManifest.xml
similarity index 75%
rename from tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml
rename to tests/ApkVerityTest/FsVerityTestApp/AndroidManifest.xml
index 0b3ff77..42fe49b 100644
--- a/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml
+++ b/tests/ApkVerityTest/FsVerityTestApp/AndroidManifest.xml
@@ -16,8 +16,12 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.apkverity">
+    package="com.android.fsverity">
     <application>
         <activity android:name=".DummyActivity"/>
     </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.fsverity"
+                     android:label="Helper app of fs-verity test">
+    </instrumentation>/>
 </manifest>
diff --git a/tests/ApkVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java b/tests/ApkVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java
new file mode 100644
index 0000000..2ed4fec
--- /dev/null
+++ b/tests/ApkVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java
@@ -0,0 +1,108 @@
+/*
+ * 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.fsverity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.security.FileIntegrityManager;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Test helper that works with the host-side test to set up a test file, and to verify fs-verity
+ * verification is done expectedly.
+ */
+public class Helper {
+    private static final String TAG = "FsVerityTest";
+
+    private static final String FILENAME = "test.file";
+
+    private static final long BLOCK_SIZE = 4096;
+
+    @Test
+    public void prepareTest() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+        android.os.Bundle testArgs = InstrumentationRegistry.getArguments();
+
+        String basename = testArgs.getString("basename");
+        context.deleteFile(basename);
+
+        assertThat(testArgs).isNotNull();
+        int fileSize = Integer.parseInt(testArgs.getString("fileSize"));
+        Log.d(TAG, "Preparing test file with size " + fileSize);
+
+        byte[] bytes = new byte[8192];
+        Arrays.fill(bytes, (byte) '1');
+        try (FileOutputStream os = context.openFileOutput(basename, Context.MODE_PRIVATE)) {
+            for (int i = 0; i < fileSize; i += bytes.length) {
+                if (i + bytes.length > fileSize) {
+                    os.write(bytes, 0, fileSize % bytes.length);
+                } else {
+                    os.write(bytes);
+                }
+            }
+        }
+
+        // Enable fs-verity
+        FileIntegrityManager fim = context.getSystemService(FileIntegrityManager.class);
+        fim.setupFsVerity(context.getFileStreamPath(basename));
+    }
+
+    @Test
+    public void verifyFileRead() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+
+        // Collect indices that the backing blocks are supposed to be corrupted.
+        android.os.Bundle testArgs = InstrumentationRegistry.getArguments();
+        assertThat(testArgs).isNotNull();
+        String filePath = testArgs.getString("filePath");
+        String csv = testArgs.getString("brokenBlockIndicesCsv");
+        Log.d(TAG, "brokenBlockIndicesCsv: " + csv);
+        String[] strings = csv.split(",");
+        var corrupted = new ArrayList(strings.length);
+        for (int i = 0; i < strings.length; i++) {
+            corrupted.add(Integer.parseInt(strings[i]));
+        }
+
+        // Expect the read to succeed or fail per the prior.
+        try (var file = new RandomAccessFile(filePath, "r")) {
+            long total_blocks = (file.length() + BLOCK_SIZE - 1) / BLOCK_SIZE;
+            for (int i = 0; i < (int) total_blocks; i++) {
+                file.seek(i * BLOCK_SIZE);
+                if (corrupted.contains(i)) {
+                    Log.d(TAG, "Expecting read at block #" + i + " to fail");
+                    assertThrows(IOException.class, () -> file.read());
+                } else {
+                    assertThat(file.readByte()).isEqualTo('1');
+                }
+            }
+        }
+    }
+}
diff --git a/tests/ApkVerityTest/TEST_MAPPING b/tests/ApkVerityTest/TEST_MAPPING
index 72d9614..39944be 100644
--- a/tests/ApkVerityTest/TEST_MAPPING
+++ b/tests/ApkVerityTest/TEST_MAPPING
@@ -1,11 +1,11 @@
 {
-  "presubmit": [
+  "postsubmit": [
     {
-      "name": "ApkVerityTest"
+      "name": "FsVerityTest"
     },
     // nextgen test only runs during postsubmit.
     {
-      "name": "ApkVerityTest",
+      "name": "FsVerityTest",
       "keywords": ["nextgen"]
     }
   ]
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
deleted file mode 100644
index 482f633..0000000
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ /dev/null
@@ -1,579 +0,0 @@
-/*
- * Copyright (C) 2019 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.apkverity;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.platform.test.annotations.RootPermissionTest;
-
-import com.android.blockdevicewriter.BlockDeviceWriter;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-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.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * This test makes sure app installs with fs-verity signature, and on-access verification works.
- *
- * <p>When an app is installed, all or none of the files should have their corresponding .fsv_sig
- * signature file. Otherwise, install will fail.
- *
- * <p>Once installed, file protected by fs-verity is verified by kernel every time a block is loaded
- * from disk to memory. The file is immutable by design, enforced by filesystem.
- *
- * <p>In order to make sure a block of the file is readable only if the underlying block on disk
- * stay intact, the test needs to bypass the filesystem and tampers with the corresponding physical
- * address against the block device.
- *
- * <p>Requirements to run this test:
- * <ul>
- *   <li>Device is rootable</li>
- *   <li>The filesystem supports fs-verity</li>
- *   <li>The feature flag is enabled</li>
- * </ul>
- */
-@RootPermissionTest
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class ApkVerityTest extends BaseHostJUnit4Test {
-    private static final String TARGET_PACKAGE = "com.android.apkverity";
-
-    private static final String BASE_APK = "ApkVerityTestApp.apk";
-    private static final String BASE_APK_DM = "ApkVerityTestApp.dm";
-    private static final String SPLIT_APK = "ApkVerityTestAppSplit.apk";
-    private static final String SPLIT_APK_DM = "ApkVerityTestAppSplit.dm";
-
-    private static final String INSTALLED_BASE_APK = "base.apk";
-    private static final String INSTALLED_BASE_DM = "base.dm";
-    private static final String INSTALLED_SPLIT_APK = "split_feature_x.apk";
-    private static final String INSTALLED_SPLIT_DM = "split_feature_x.dm";
-    private static final String INSTALLED_BASE_APK_FSV_SIG = "base.apk.fsv_sig";
-    private static final String INSTALLED_BASE_DM_FSV_SIG = "base.dm.fsv_sig";
-    private static final String INSTALLED_SPLIT_APK_FSV_SIG = "split_feature_x.apk.fsv_sig";
-    private static final String INSTALLED_SPLIT_DM_FSV_SIG = "split_feature_x.dm.fsv_sig";
-
-    private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";
-    private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der";
-
-    /** Only 4K page is supported by fs-verity currently. */
-    private static final int FSVERITY_PAGE_SIZE = 4096;
-
-    private ITestDevice mDevice;
-    private boolean mDmRequireFsVerity;
-
-    @Before
-    public void setUp() throws DeviceNotAvailableException {
-        mDevice = getDevice();
-        mDmRequireFsVerity = "true".equals(
-                mDevice.getProperty("pm.dexopt.dm.require_fsverity"));
-
-        expectRemoteCommandToSucceed("cmd file_integrity append-cert " + CERT_PATH);
-        uninstallPackage(TARGET_PACKAGE);
-    }
-
-    @After
-    public void tearDown() throws DeviceNotAvailableException {
-        expectRemoteCommandToSucceed("cmd file_integrity remove-last-cert");
-        uninstallPackage(TARGET_PACKAGE);
-    }
-
-    @Test
-    public void testFsverityKernelSupports() throws DeviceNotAvailableException {
-        ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
-        expectRemoteCommandToSucceed("test -f /sys/fs/" + mountPoint.type + "/features/verity");
-    }
-
-    @Test
-    public void testInstallBase() throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_APK_FSV_SIG);
-        verifyInstalledFilesHaveFsverity(INSTALLED_BASE_APK);
-    }
-
-    @Test
-    public void testInstallBaseWithWrongSignature()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFile(BASE_APK)
-                .addFile(SPLIT_APK_DM + ".fsv_sig",
-                        BASE_APK + ".fsv_sig")
-                .runExpectingFailure();
-    }
-
-    @Test
-    public void testInstallBaseWithSplit()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .addFileAndSignature(SPLIT_APK)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_APK_FSV_SIG,
-                INSTALLED_SPLIT_APK,
-                INSTALLED_SPLIT_APK_FSV_SIG);
-        verifyInstalledFilesHaveFsverity(
-                INSTALLED_BASE_APK,
-                INSTALLED_SPLIT_APK);
-    }
-
-    @Test
-    public void testInstallBaseWithDm() throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .addFileAndSignature(BASE_APK_DM)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_APK_FSV_SIG,
-                INSTALLED_BASE_DM,
-                INSTALLED_BASE_DM_FSV_SIG);
-        verifyInstalledFilesHaveFsverity(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_DM);
-    }
-
-    @Test
-    public void testInstallEverything() throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .addFileAndSignature(BASE_APK_DM)
-                .addFileAndSignature(SPLIT_APK)
-                .addFileAndSignature(SPLIT_APK_DM)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_APK_FSV_SIG,
-                INSTALLED_BASE_DM,
-                INSTALLED_BASE_DM_FSV_SIG,
-                INSTALLED_SPLIT_APK,
-                INSTALLED_SPLIT_APK_FSV_SIG,
-                INSTALLED_SPLIT_DM,
-                INSTALLED_SPLIT_DM_FSV_SIG);
-        verifyInstalledFilesHaveFsverity(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_DM,
-                INSTALLED_SPLIT_APK,
-                INSTALLED_SPLIT_DM);
-    }
-
-    @Test
-    public void testInstallSplitOnly()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_APK_FSV_SIG);
-
-        new InstallMultiple()
-                .inheritFrom(TARGET_PACKAGE)
-                .addFileAndSignature(SPLIT_APK)
-                .run();
-
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_APK_FSV_SIG,
-                INSTALLED_SPLIT_APK,
-                INSTALLED_SPLIT_APK_FSV_SIG);
-        verifyInstalledFilesHaveFsverity(
-                INSTALLED_BASE_APK,
-                INSTALLED_SPLIT_APK);
-    }
-
-    @Test
-    public void testInstallSplitOnlyMissingSignature()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_APK_FSV_SIG);
-
-        new InstallMultiple()
-                .inheritFrom(TARGET_PACKAGE)
-                .addFile(SPLIT_APK)
-                .runExpectingFailure();
-    }
-
-    @Test
-    public void testInstallSplitOnlyWithoutBaseSignature()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFile(BASE_APK)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-        verifyInstalledFiles(INSTALLED_BASE_APK);
-
-        new InstallMultiple()
-                .inheritFrom(TARGET_PACKAGE)
-                .addFileAndSignature(SPLIT_APK)
-                .run();
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_SPLIT_APK,
-                INSTALLED_SPLIT_APK_FSV_SIG);
-    }
-
-    @Test
-    public void testInstallOnlyDmHasFsvSig()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFile(BASE_APK)
-                .addFileAndSignature(BASE_APK_DM)
-                .addFile(SPLIT_APK)
-                .addFileAndSignature(SPLIT_APK_DM)
-                .run();
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_DM,
-                INSTALLED_BASE_DM_FSV_SIG,
-                INSTALLED_SPLIT_APK,
-                INSTALLED_SPLIT_DM,
-                INSTALLED_SPLIT_DM_FSV_SIG);
-        verifyInstalledFilesHaveFsverity(
-                INSTALLED_BASE_DM,
-                INSTALLED_SPLIT_DM);
-    }
-
-    @Test
-    public void testInstallDmWithoutFsvSig_Base()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        InstallMultiple installer = new InstallMultiple()
-                .addFile(BASE_APK)
-                .addFile(BASE_APK_DM)
-                .addFile(SPLIT_APK)
-                .addFileAndSignature(SPLIT_APK_DM);
-        if (mDmRequireFsVerity) {
-            installer.runExpectingFailure();
-        } else {
-            installer.run();
-            verifyInstalledFiles(
-                    INSTALLED_BASE_APK,
-                    INSTALLED_BASE_DM,
-                    INSTALLED_SPLIT_APK,
-                    INSTALLED_SPLIT_DM,
-                    INSTALLED_SPLIT_DM_FSV_SIG);
-            verifyInstalledFilesHaveFsverity(INSTALLED_SPLIT_DM);
-        }
-    }
-
-    @Test
-    public void testInstallDmWithoutFsvSig_Split()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        InstallMultiple installer = new InstallMultiple()
-                .addFile(BASE_APK)
-                .addFileAndSignature(BASE_APK_DM)
-                .addFile(SPLIT_APK)
-                .addFile(SPLIT_APK_DM);
-        if (mDmRequireFsVerity) {
-            installer.runExpectingFailure();
-        } else {
-            installer.run();
-            verifyInstalledFiles(
-                    INSTALLED_BASE_APK,
-                    INSTALLED_BASE_DM,
-                    INSTALLED_BASE_DM_FSV_SIG,
-                    INSTALLED_SPLIT_APK,
-                    INSTALLED_SPLIT_DM);
-            verifyInstalledFilesHaveFsverity(INSTALLED_BASE_DM);
-        }
-    }
-
-    @Test
-    public void testInstallSomeApkIsMissingFsvSig_Base()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .addFileAndSignature(BASE_APK_DM)
-                .addFile(SPLIT_APK)
-                .addFileAndSignature(SPLIT_APK_DM)
-                .runExpectingFailure();
-    }
-
-    @Test
-    public void testInstallSomeApkIsMissingFsvSig_Split()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFile(BASE_APK)
-                .addFileAndSignature(BASE_APK_DM)
-                .addFileAndSignature(SPLIT_APK)
-                .addFileAndSignature(SPLIT_APK_DM)
-                .runExpectingFailure();
-    }
-
-    @Test
-    public void testInstallBaseWithFsvSigThenSplitWithout()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-        verifyInstalledFiles(
-                INSTALLED_BASE_APK,
-                INSTALLED_BASE_APK_FSV_SIG);
-
-        new InstallMultiple()
-                .addFile(SPLIT_APK)
-                .runExpectingFailure();
-    }
-
-    @Test
-    public void testInstallBaseWithoutFsvSigThenSplitWith()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFile(BASE_APK)
-                .run();
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-        verifyInstalledFiles(INSTALLED_BASE_APK);
-
-        new InstallMultiple()
-                .addFileAndSignature(SPLIT_APK)
-                .runExpectingFailure();
-    }
-
-    @Test
-    public void testFsverityFileIsImmutableAndReadable() throws DeviceNotAvailableException {
-        new InstallMultiple().addFileAndSignature(BASE_APK).run();
-        String apkPath = getApkPath(TARGET_PACKAGE);
-
-        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
-        expectRemoteCommandToFail("echo -n '' >> " + apkPath);
-        expectRemoteCommandToSucceed("cat " + apkPath + " > /dev/null");
-    }
-
-    @Test
-    public void testFsverityFailToReadModifiedBlockAtFront() throws DeviceNotAvailableException {
-        new InstallMultiple().addFileAndSignature(BASE_APK).run();
-        String apkPath = getApkPath(TARGET_PACKAGE);
-
-        long apkSize = getFileSizeInBytes(apkPath);
-        long offsetFirstByte = 0;
-
-        // The first two pages should be both readable at first.
-        assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
-        if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
-            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
-                    offsetFirstByte + FSVERITY_PAGE_SIZE));
-        }
-
-        // Damage the file directly against the block device.
-        damageFileAgainstBlockDevice(apkPath, offsetFirstByte);
-
-        // Expect actual read from disk to fail but only at damaged page.
-        expectReadFromBlockDeviceToFail(apkPath, offsetFirstByte);
-        if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
-            long lastByteOfTheSamePage =
-                    offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1;
-            assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage));
-            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage + 1));
-        }
-    }
-
-    @Test
-    public void testFsverityFailToReadModifiedBlockAtBack() throws DeviceNotAvailableException {
-        new InstallMultiple().addFileAndSignature(BASE_APK).run();
-        String apkPath = getApkPath(TARGET_PACKAGE);
-
-        long apkSize = getFileSizeInBytes(apkPath);
-        long offsetOfLastByte = apkSize - 1;
-
-        // The first two pages should be both readable at first.
-        assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
-        if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
-            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
-                    offsetOfLastByte - FSVERITY_PAGE_SIZE));
-        }
-
-        // Damage the file directly against the block device.
-        damageFileAgainstBlockDevice(apkPath, offsetOfLastByte);
-
-        // Expect actual read from disk to fail but only at damaged page.
-        expectReadFromBlockDeviceToFail(apkPath, offsetOfLastByte);
-        if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
-            long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
-            assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage));
-            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage - 1));
-        }
-    }
-
-    private void verifyInstalledFilesHaveFsverity(String... filenames)
-            throws DeviceNotAvailableException {
-        // Verify that all files are protected by fs-verity
-        String apkPath = getApkPath(TARGET_PACKAGE);
-        String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
-        long kTargetOffset = 0;
-        for (String basename : filenames) {
-            String path = appDir + "/" + basename;
-            damageFileAgainstBlockDevice(path, kTargetOffset);
-
-            expectReadFromBlockDeviceToFail(path, kTargetOffset);
-        }
-    }
-
-    private void expectReadFromBlockDeviceToFail(String readPath, long offset)
-            throws DeviceNotAvailableException {
-        // Retry is sometimes needed to pass the test. Package manager may have FD leaks
-        // (see b/122744005 as example) that prevents the file in question to be evicted
-        // from filesystem cache. Forcing GC workarounds the problem.
-        int retry = 5;
-        for (; retry > 0; retry--) {
-            BlockDeviceWriter.dropCaches(mDevice);
-            if (!BlockDeviceWriter.canReadByte(mDevice, readPath, offset)) {
-                break;
-            }
-            try {
-                String openFiles = expectRemoteCommandToSucceed("lsof " + readPath);
-                CLog.d("lsof: " + openFiles);
-                Thread.sleep(1000);
-                forceGCOnOpenFilesProcess(getOpenFilesPIDs(openFiles));
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                return;
-            }
-        }
-        assertTrue("Read from " + readPath + " should fail", retry > 0);
-    }
-
-    /**
-     * This is a helper method that parses the lsof output to get PIDs of process holding FD.
-     * Here is an example output of lsof. This method extracts the second columns(PID).
-     *
-     * Example lsof output:
-     *  COMMAND      PID     USER    FD     TYPE   DEVICE   SIZE/OFF  NODE   NAME
-     * .example.app  1063    u0_a38  mem    REG    253,6    8599      12826  example.apk
-     * .example.app  1063    u0_a38  99r    REG    253,6    8599      12826  example.apk
-     */
-    private Set<String> getOpenFilesPIDs(String lsof) {
-        Set<String> openFilesPIDs = new HashSet<>();
-        String[] lines = lsof.split("\n");
-        for (int i = 1; i < lines.length; i++) {
-            openFilesPIDs.add(lines[i].split("\\s+")[1]);
-        }
-        return openFilesPIDs;
-    }
-
-    /**
-     * This is a helper method that forces GC on processes given their PIDs.
-     * That is to execute shell command "kill -10" on PIDs.
-     */
-    private void forceGCOnOpenFilesProcess(Set<String> openFilesPIDs)
-            throws DeviceNotAvailableException {
-        for (String openFilePID : openFilesPIDs) {
-            mDevice.executeShellV2Command("kill -10 " + openFilePID);
-        }
-    }
-
-    private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
-        String apkPath = getApkPath(TARGET_PACKAGE);
-        String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
-        // Exclude directories since we only care about files.
-        HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
-                expectRemoteCommandToSucceed("ls -p " + appDir + " | grep -v '/'").split("\n")));
-
-        HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
-        assertEquals(expectedFiles, actualFiles);
-    }
-
-    private void damageFileAgainstBlockDevice(String path, long offsetOfTargetingByte)
-            throws DeviceNotAvailableException {
-        assertTrue(path.startsWith("/data/"));
-        ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
-        ArrayList<String> args = new ArrayList<>();
-        args.add(DAMAGING_EXECUTABLE);
-        if ("f2fs".equals(mountPoint.type)) {
-            args.add("--use-f2fs-pinning");
-        }
-        args.add(mountPoint.filesystem);
-        args.add(path);
-        args.add(Long.toString(offsetOfTargetingByte));
-        expectRemoteCommandToSucceed(String.join(" ", args));
-    }
-
-    private String getApkPath(String packageName) throws DeviceNotAvailableException {
-        String line = expectRemoteCommandToSucceed("pm path " + packageName + " | grep base.apk");
-        int index = line.trim().indexOf(":");
-        assertTrue(index >= 0);
-        return line.substring(index + 1);
-    }
-
-    private long getFileSizeInBytes(String packageName) throws DeviceNotAvailableException {
-        return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim());
-    }
-
-    private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
-        CommandResult result = mDevice.executeShellV2Command(cmd);
-        assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
-                result.getStatus());
-        return result.getStdout();
-    }
-
-    private void expectRemoteCommandToFail(String cmd) throws DeviceNotAvailableException {
-        CommandResult result = mDevice.executeShellV2Command(cmd);
-        assertTrue("Unexpected success from `" + cmd + "`: " + result.getStderr(),
-                result.getStatus() != CommandStatus.SUCCESS);
-    }
-
-    private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
-        InstallMultiple() {
-            super(getDevice(), getBuild());
-        }
-
-        InstallMultiple addFileAndSignature(String filename) {
-            try {
-                addFile(filename);
-                addFile(filename + ".fsv_sig");
-            } catch (FileNotFoundException e) {
-                fail("Missing test file: " + e);
-            }
-            return this;
-        }
-    }
-}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java b/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java
deleted file mode 100644
index 02e73d1..0000000
--- a/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2019 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.apkverity;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-
-import junit.framework.TestCase;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Base class for invoking the install-multiple command via ADB. Subclass this for less typing:
- *
- * <code> private class InstallMultiple extends BaseInstallMultiple&lt;InstallMultiple&gt; { public
- * InstallMultiple() { super(getDevice(), null); } } </code>
- */
-/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
-
-    private final ITestDevice mDevice;
-    private final IBuildInfo mBuild;
-
-    private final List<String> mArgs = new ArrayList<>();
-    private final Map<File, String> mFileToRemoteMap = new HashMap<>();
-
-    /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) {
-        mDevice = device;
-        mBuild = buildInfo;
-        addArg("-g");
-    }
-
-    T addArg(String arg) {
-        mArgs.add(arg);
-        return (T) this;
-    }
-
-    T addFile(String filename) throws FileNotFoundException {
-        return addFile(filename, filename);
-    }
-
-    T addFile(String filename, String remoteName) throws FileNotFoundException {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
-        mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName);
-        return (T) this;
-    }
-
-    T inheritFrom(String packageName) {
-        addArg("-r");
-        addArg("-p " + packageName);
-        return (T) this;
-    }
-
-    void run() throws DeviceNotAvailableException {
-        run(true);
-    }
-
-    void runExpectingFailure() throws DeviceNotAvailableException {
-        run(false);
-    }
-
-    private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
-        final ITestDevice device = mDevice;
-
-        // Create an install session
-        final StringBuilder cmd = new StringBuilder();
-        cmd.append("pm install-create");
-        for (String arg : mArgs) {
-            cmd.append(' ').append(arg);
-        }
-
-        String result = device.executeShellCommand(cmd.toString());
-        TestCase.assertTrue(result, result.startsWith("Success"));
-
-        final int start = result.lastIndexOf("[");
-        final int end = result.lastIndexOf("]");
-        int sessionId = -1;
-        try {
-            if (start != -1 && end != -1 && start < end) {
-                sessionId = Integer.parseInt(result.substring(start + 1, end));
-            }
-        } catch (NumberFormatException e) {
-            throw new IllegalStateException("Failed to parse install session: " + result);
-        }
-        if (sessionId == -1) {
-            throw new IllegalStateException("Failed to create install session: " + result);
-        }
-
-        // Push our files into session. Ideally we'd use stdin streaming,
-        // but ddmlib doesn't support it yet.
-        for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) {
-            final File file = entry.getKey();
-            final String remoteName  = entry.getValue();
-            final String remotePath = "/data/local/tmp/" + file.getName();
-            if (!device.pushFile(file, remotePath)) {
-                throw new IllegalStateException("Failed to push " + file);
-            }
-
-            cmd.setLength(0);
-            cmd.append("pm install-write");
-            cmd.append(' ').append(sessionId);
-            cmd.append(' ').append(remoteName);
-            cmd.append(' ').append(remotePath);
-
-            result = device.executeShellCommand(cmd.toString());
-            TestCase.assertTrue(result, result.startsWith("Success"));
-        }
-
-        // Everything staged; let's pull trigger
-        cmd.setLength(0);
-        cmd.append("pm install-commit");
-        cmd.append(' ').append(sessionId);
-
-        result = device.executeShellCommand(cmd.toString());
-        if (expectingSuccess) {
-            TestCase.assertTrue(result, result.contains("Success"));
-        } else {
-            TestCase.assertFalse(result, result.contains("Success"));
-        }
-    }
-}
diff --git a/tests/ApkVerityTest/src/com/android/fsverity/FsVerityHostTest.java b/tests/ApkVerityTest/src/com/android/fsverity/FsVerityHostTest.java
new file mode 100644
index 0000000..be479f2
--- /dev/null
+++ b/tests/ApkVerityTest/src/com/android/fsverity/FsVerityHostTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.fsverity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.annotations.RootPermissionTest;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.host.HostFlagsValueProvider;
+import android.security.Flags;
+
+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.testtype.junit4.DeviceTestRunOptions;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This test verifies fs-verity works end-to-end. There is a corresponding helper app.
+ *
+ * <p>The helper app uses a FileIntegrityManager API to enable fs-verity to a file. The host test
+ * here * tampers with the file's backing storage, then tells the helper app to read and expect
+ * success/failure on read.
+ *
+ * <p>In order to make sure a block of the file is readable only if the underlying block on disk
+ * stay intact, the test needs to bypass the filesystem and tampers with the corresponding physical
+ * address against the block device.
+ */
+@RootPermissionTest
+@RunWith(DeviceJUnit4ClassRunner.class)
+@RequiresFlagsEnabled(Flags.FLAG_FSVERITY_API)
+public class FsVerityHostTest extends BaseHostJUnit4Test {
+    private static final String TARGET_PACKAGE = "com.android.fsverity";
+
+    private static final String BASENAME = "test.file";
+    private static final String TARGET_PATH = "/data/data/" + TARGET_PACKAGE + "/files/" + BASENAME;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
+
+    @Test
+    public void testFsVeritySmallFile() throws Exception {
+        prepareTest(10000);
+
+        ITestDevice device = getDevice();
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 0);
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 8192);
+        BlockDeviceWriter.dropCaches(device);
+
+        verifyRead(TARGET_PATH, "0,2");
+    }
+
+    @Test
+    public void testFsVerityLargerFileWithOneMoreMerkleTreeLevel() throws Exception {
+        prepareTest(128 * 4096 + 1);
+
+        ITestDevice device = getDevice();
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 4096);
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 100 * 4096);
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 128 * 4096 + 1);
+        BlockDeviceWriter.dropCaches(device);
+
+        verifyRead(TARGET_PATH, "1,100,128");
+    }
+
+    private void prepareTest(int fileSize) throws Exception {
+        DeviceTestRunOptions options = new DeviceTestRunOptions(TARGET_PACKAGE);
+        options.setTestClassName(TARGET_PACKAGE + ".Helper");
+        options.setTestMethodName("prepareTest");
+        options.addInstrumentationArg("basename", BASENAME);
+        options.addInstrumentationArg("fileSize", String.valueOf(fileSize));
+        assertThat(runDeviceTests(options)).isTrue();
+    }
+
+    private void verifyRead(String path, String indicesCsv) throws Exception {
+        DeviceTestRunOptions options = new DeviceTestRunOptions(TARGET_PACKAGE);
+        options.setTestClassName(TARGET_PACKAGE + ".Helper");
+        options.setTestMethodName("verifyFileRead");
+        options.addInstrumentationArg("brokenBlockIndicesCsv", indicesCsv);
+        options.addInstrumentationArg("filePath", TARGET_PATH);
+        assertThat(runDeviceTests(options)).isTrue();
+    }
+}
diff --git a/tests/ApkVerityTest/testdata/Android.bp b/tests/ApkVerityTest/testdata/Android.bp
index ccfc4c9..2d578d3 100644
--- a/tests/ApkVerityTest/testdata/Android.bp
+++ b/tests/ApkVerityTest/testdata/Android.bp
@@ -37,51 +37,3 @@
     name: "ApkVerityTestCertDer",
     srcs: ["ApkVerityTestCert.der"],
 }
-
-filegroup {
-    name: "ApkVerityTestAppDm",
-    srcs: ["ApkVerityTestApp.dm"],
-}
-
-filegroup {
-    name: "ApkVerityTestAppSplitDm",
-    srcs: ["ApkVerityTestAppSplit.dm"],
-}
-
-genrule_defaults {
-    name: "apk_verity_sig_gen_default",
-    tools: ["fsverity"],
-    tool_files: [":ApkVerityTestKeyPem", ":ApkVerityTestCertPem"],
-    cmd: "$(location fsverity) sign $(in) $(out) " +
-        "--key=$(location :ApkVerityTestKeyPem) " +
-        "--cert=$(location :ApkVerityTestCertPem) " +
-        "> /dev/null",
-}
-
-genrule {
-    name: "ApkVerityTestAppFsvSig",
-    defaults: ["apk_verity_sig_gen_default"],
-    srcs: [":ApkVerityTestApp"],
-    out: ["ApkVerityTestApp.apk.fsv_sig"],
-}
-
-genrule {
-    name: "ApkVerityTestAppDmFsvSig",
-    defaults: ["apk_verity_sig_gen_default"],
-    srcs: [":ApkVerityTestAppDm"],
-    out: ["ApkVerityTestApp.dm.fsv_sig"],
-}
-
-genrule {
-    name: "ApkVerityTestAppSplitFsvSig",
-    defaults: ["apk_verity_sig_gen_default"],
-    srcs: [":ApkVerityTestAppSplit"],
-    out: ["ApkVerityTestAppSplit.apk.fsv_sig"],
-}
-
-genrule {
-    name: "ApkVerityTestAppSplitDmFsvSig",
-    defaults: ["apk_verity_sig_gen_default"],
-    srcs: [":ApkVerityTestAppSplitDm"],
-    out: ["ApkVerityTestAppSplit.dm.fsv_sig"],
-}
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm b/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm
deleted file mode 100644
index e53a861..0000000
--- a/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm
+++ /dev/null
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm b/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm
deleted file mode 100644
index 75396f1..0000000
--- a/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm
+++ /dev/null
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/README.md b/tests/ApkVerityTest/testdata/README.md
deleted file mode 100644
index 163cb18..0000000
--- a/tests/ApkVerityTest/testdata/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-This test only runs on rooted / debuggable device.
-
-The test tries to install subsets of base.{apk,dm}, split.{apk,dm} and their
-corresponding .fsv_sig files (generated by build rule). If installed, the
-tests also tries to tamper with the file at absolute disk offset to verify
-if fs-verity is effective.
-
-How to generate dex metadata (.dm)
-==================================
-
-  adb shell profman --generate-test-profile=/data/local/tmp/primary.prof
-  adb pull /data/local/tmp/primary.prof
-  zip foo.dm primary.prof