Prepare AppsIndexer CTS Tests for GMS-AppSearch.

- Rename AppIndexerCtsTest to AppsIndexerCtsTest
- Move AppFunction tests to AppFunctionCtsTest
- Move all common utils to AppsIndexerTestUtils

Bug: 413737868
Flag: EXEMPT TEST_ONLY
Test: Presubmits
Change-Id: Idb1896030e9b97facf24353debc6d7b5ee91b5e5
diff --git a/tests/appsearch/indexer-test-app/AndroidManifest_A_v1.xml b/tests/appsearch/indexer-test-app/AndroidManifest_A_v1.xml
index f84efa1..c6ebf50 100644
--- a/tests/appsearch/indexer-test-app/AndroidManifest_A_v1.xml
+++ b/tests/appsearch/indexer-test-app/AndroidManifest_A_v1.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,13 +13,18 @@
   ~ 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 for App A v1 with label "App A [V1]" and MainActivity. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.appsearch.indexertestapp.a">
 
+    <!-- TODO: b/413737868 - Lower minSdkVersion to 24 once the remaining GMS Core
+        targets are supported. -->
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="34" />
+
     <application android:label="App A [v1]">
-        <activity android:name="com.android.cts.appsearch.helper.MainActivity"
+        <activity android:name=".MainActivity"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/tests/appsearch/indexer-test-app/AndroidManifest_A_v2.xml b/tests/appsearch/indexer-test-app/AndroidManifest_A_v2.xml
index eb4e971..aaca59d 100644
--- a/tests/appsearch/indexer-test-app/AndroidManifest_A_v2.xml
+++ b/tests/appsearch/indexer-test-app/AndroidManifest_A_v2.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
   ~ Copyright (C) 2024 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,13 +13,18 @@
   ~ 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 for App A v2 with label "App A [V2]" and an added AppFunctionService. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.appsearch.indexertestapp.a">
 
+    <!-- TODO: b/413737868 - Lower minSdkVersion to 24 once the remaining GMS Core
+        targets are supported. -->
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="34" />
+
     <application android:label="App A [v2]">
-        <activity android:name="com.android.cts.appsearch.helper.MainActivity"
+        <activity android:name=".MainActivity"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -27,7 +33,7 @@
         </activity>
 
         <service
-            android:name="com.android.cts.appsearch.helper.AppFunctionService"
+            android:name=".AppFunctionService"
             android:exported="true">
             <property
                 android:name="android.app.appfunctions"
@@ -37,4 +43,4 @@
             </intent-filter>
         </service>
     </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/appsearch/src/com/android/cts/appsearch/appindexer/AppIndexerCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/appsindexer/AppFunctionCtsTest.java
similarity index 70%
rename from tests/appsearch/src/com/android/cts/appsearch/appindexer/AppIndexerCtsTest.java
rename to tests/appsearch/src/com/android/cts/appsearch/appsindexer/AppFunctionCtsTest.java
index 9dadd4f3..cd79915 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/appindexer/AppIndexerCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/appsindexer/AppFunctionCtsTest.java
@@ -13,41 +13,57 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.appsearch.cts.appindexer;
+package android.app.appsearch.cts.appsindexer;
 
-import static android.app.appsearch.testutil.AppFunctionConstants.APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PRINT_APP_FUNCTION;
-import static android.app.appsearch.testutil.AppFunctionConstants.APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_COMMON_SCHEMA_METADATA;
-import static android.app.appsearch.testutil.AppFunctionConstants.APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_PRINT_APP_FUNCTION;
-import static android.app.appsearch.testutil.AppFunctionConstants.APP_A_DYNAMIC_SCHEMA_PRINT_APP_FUNCTION;
-import static android.app.appsearch.testutil.AppFunctionConstants.APP_A_V2_PRINT_APP_FUNCTION;
-import static android.app.appsearch.testutil.AppFunctionConstants.APP_B_DYNAMIC_SCHEMA_PRINT_APP_FUNCTION;
-import static android.app.appsearch.testutil.AppFunctionConstants.APP_B_PRINT_APP_FUNCTION;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PRINT_APP_FUNCTION;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_COMMON_SCHEMA_METADATA;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_PRINT_APP_FUNCTION;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.APP_A_DYNAMIC_SCHEMA_PRINT_APP_FUNCTION;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.APP_A_V2_PRINT_APP_FUNCTION;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.APP_B_DYNAMIC_SCHEMA_PRINT_APP_FUNCTION;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.APP_B_PRINT_APP_FUNCTION;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.PROPERTY_DISPLAY_NAME_STRING_RES;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.PROPERTY_ENABLED_BY_DEFAULT;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.PROPERTY_FUNCTION_ID;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.PROPERTY_PACKAGE_NAME;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.PROPERTY_SCHEMA_CATEGORY;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.PROPERTY_SCHEMA_NAME;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.PROPERTY_SCHEMA_VERSION;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_APP_FUNCTION_SERVICE_DISABLED;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_DYNAMIC_SCHEMA_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_PKG;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_V1_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_V2_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_V3_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_B_DYNAMIC_SCHEMA_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_B_PKG;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_B_V1_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.clearTimestampsAndParentTypesInDocument;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.installPackage;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.retryAssert;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.searchAppFunctionDocumentsIntoMap;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.searchAppFunctionsWithPackageName;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.searchMobileApplicationWithId;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.uninstallPackage;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.updateAppFunctionServiceEnabledState;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.Manifest;
 import android.app.appsearch.GenericDocument;
-import android.app.appsearch.GlobalSearchSessionShim;
-import android.app.appsearch.SearchResult;
-import android.app.appsearch.SearchResultsShim;
-import android.app.appsearch.SearchSpec;
 import android.app.appsearch.testutil.AppSearchTestUtils;
-import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Build;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.util.ArrayMap;
 
-import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.appsearch.flags.Flags;
-import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -58,53 +74,13 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
 
 @RequiresFlagsEnabled(Flags.FLAG_APPS_INDEXER_ENABLED)
-public class AppIndexerCtsTest {
-    public static final String INDEXER_PACKAGE_NAME = "android";
+public class AppFunctionCtsTest {
 
     @Rule public final RuleChain mRuleChain = AppSearchTestUtils.createCommonTestRules();
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
-    private static final String TEST_APP_ROOT_FOLDER = "/data/local/tmp/cts/appsearch/";
-    private static final String TEST_APP_A_V1_PATH =
-            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppAV1.apk";
-    private static final String TEST_APP_A_V2_PATH =
-            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppAV2.apk";
-    private static final String TEST_APP_A_V3_PATH =
-            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppAV3.apk";
-    private static final String TEST_APP_B_V1_PATH =
-            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppBV1.apk";
-    private static final String TEST_APP_A_DYNAMIC_SCHEMA_PATH =
-            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppADynamicSchema.apk";
-    private static final String TEST_APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PATH =
-            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppADynamicSchemaFewerTypes.apk";
-    private static final String TEST_APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_PATH =
-            TEST_APP_ROOT_FOLDER
-                    + "CtsAppSearchIndexerTestAppADynamicSchemaMultipleRootSchemas.apk";
-    private static final String TEST_APP_B_DYNAMIC_SCHEMA_PATH =
-            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppBDynamicSchema.apk";
-
-    private static final String TEST_APP_A_APP_FUNCTION_SERVICE_DISABLED =
-            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppAAppFunctionServiceDisabled.apk";
-    private static final String TEST_APP_A_PKG = "com.android.cts.appsearch.indexertestapp.a";
-    private static final String TEST_APP_B_PKG = "com.android.cts.appsearch.indexertestapp.b";
-    private static final String NAMESPACE_MOBILE_APPLICATION = "apps";
-    private static final String NAMESPACE_APP_FUNCTIONS = "app_functions";
-    private static final String APP_PROPERTY_DISPLAY_NAME = "displayName";
-    private static final String PROPERTY_FUNCTION_ID = "functionId";
-    private static final String PROPERTY_PACKAGE_NAME = "packageName";
-    private static final String PROPERTY_SCHEMA_NAME = "schemaName";
-    private static final String PROPERTY_SCHEMA_VERSION = "schemaVersion";
-    private static final String PROPERTY_SCHEMA_CATEGORY = "schemaCategory";
-    private static final String PROPERTY_DISPLAY_NAME_STRING_RES = "displayNameStringRes";
-    private static final String PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
-    private static final String PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS =
-            "restrictCallersWithExecuteAppFunctions";
-
-    private static final long RETRY_CHECK_INTERVAL_MILLIS = 500;
-    private static final long RETRY_MAX_INTERVALS = 10;
 
     @Before
     @After
@@ -120,53 +96,10 @@
     }
 
     @Test
-    public void indexMobileApplications_packageChanges() throws Throwable {
-        {
-            // Install a new app
-            installPackage(TEST_APP_A_V1_PATH);
-
-            retryAssert(
-                    () -> {
-                        GenericDocument mobileApplication =
-                                searchMobileApplicationWithId(TEST_APP_A_PKG);
-                        assertThat(mobileApplication).isNotNull();
-                        assertThat(mobileApplication.getPropertyString(APP_PROPERTY_DISPLAY_NAME))
-                                .isEqualTo("App A [v1]");
-                    });
-        }
-
-        {
-            // Update it
-            installPackage(TEST_APP_A_V2_PATH);
-
-            retryAssert(
-                    () -> {
-                        GenericDocument mobileApplication =
-                                searchMobileApplicationWithId(TEST_APP_A_PKG);
-                        assertThat(mobileApplication).isNotNull();
-                        assertThat(mobileApplication.getPropertyString(APP_PROPERTY_DISPLAY_NAME))
-                                .isEqualTo("App A [v2]");
-                    });
-        }
-
-        {
-            // Uninstall it
-            uninstallPackage(TEST_APP_A_PKG);
-
-            retryAssert(
-                    () -> {
-                        GenericDocument mobileApplication =
-                                searchMobileApplicationWithId(TEST_APP_A_PKG);
-                        assertThat(mobileApplication).isNull();
-                    });
-        }
-    }
-
-    @Test
     public void indexAppFunctions_packageChanges() throws Throwable {
         {
             // Install A V1 which does not have app functions.
-            installPackage(TEST_APP_A_V1_PATH);
+            installPackage(mContext, TEST_APP_A_V1_PATH);
 
             retryAssert(
                     () -> {
@@ -178,7 +111,7 @@
 
         {
             // Update to v2 which has one app function
-            installPackage(TEST_APP_A_V2_PATH);
+            installPackage(mContext, TEST_APP_A_V2_PATH);
             retryAssert(
                     () -> {
                         List<GenericDocument> appFunctions =
@@ -194,7 +127,7 @@
 
         {
             // Update to v3 which no longer has print1 but has print2 and print3.
-            installPackage(TEST_APP_A_V3_PATH);
+            installPackage(mContext, TEST_APP_A_V3_PATH);
             retryAssert(
                     () -> {
                         List<GenericDocument> appFunctions =
@@ -231,7 +164,7 @@
     public void indexAppFunctions_fullXml() throws Throwable {
         // The XML in A v2 has the full XML which specifies all the properties. Here we verify
         // all the properties are being indexed properly.
-        installPackage(TEST_APP_A_V2_PATH);
+        installPackage(mContext, TEST_APP_A_V2_PATH);
         retryAssert(
                 () -> {
                     List<GenericDocument> appFunctions =
@@ -262,7 +195,7 @@
     public void indexAppFunctions_defaultValue() throws Throwable {
         // The XML in B V1 only have functionId, schema_name, schema_version and schema_category.
         // Here, we check the default value of the optional properties are set properly.
-        installPackage(TEST_APP_B_V1_PATH);
+        installPackage(mContext, TEST_APP_B_V1_PATH);
         retryAssert(
                 () -> {
                     List<GenericDocument> appFunctions =
@@ -285,7 +218,7 @@
             throws Throwable {
         // Install the test app B V1 which has one app function. That function should be indexed.
         {
-            installPackage(TEST_APP_B_V1_PATH);
+            installPackage(mContext, TEST_APP_B_V1_PATH);
             retryAssert(
                     () -> {
                         List<GenericDocument> appFunctions =
@@ -300,7 +233,7 @@
         // Install test app A v1 which does not have any app function. The functions from B
         // should be retained.
         {
-            installPackage(TEST_APP_A_V1_PATH);
+            installPackage(mContext, TEST_APP_A_V1_PATH);
             retryAssert(
                     () -> {
                         // Ensure the app A is indexed before checking if the function is retained.
@@ -324,7 +257,7 @@
     public void indexAppFunctionsFromTwoApps() throws Throwable {
         // Install the test app B V1 which has one app function. That function should be indexed.
         {
-            installPackage(TEST_APP_B_V1_PATH);
+            installPackage(mContext, TEST_APP_B_V1_PATH);
             retryAssert(
                     () -> {
                         List<GenericDocument> appFunctions =
@@ -339,7 +272,7 @@
         // Install test app A v2 which also has one app function. The function from B should be
         // retained and the new function from A should be indexed.
         {
-            installPackage(TEST_APP_A_V2_PATH);
+            installPackage(mContext, TEST_APP_A_V2_PATH);
             retryAssert(
                     () -> {
                         List<GenericDocument> appFunctionsFromB =
@@ -363,7 +296,7 @@
     public void indexMobileApplicationAndAppFunction_withoutLauncherIcon() throws Throwable {
         {
             // Install B V1 which does not have a launcher icon but have app functions.
-            installPackage(TEST_APP_B_V1_PATH);
+            installPackage(mContext, TEST_APP_B_V1_PATH);
 
             retryAssert(
                     () -> {
@@ -388,7 +321,7 @@
     @Test
     public void indexAppWithDynamicSchema_dynamicSchemasDisabled_indexesPredefinedSchemaFieldsOnly()
             throws Throwable {
-        installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+        installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
         // Retry till the indexer has completed a run.
         retryAssert(
@@ -411,7 +344,7 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     public void indexAppWithDynamicSchema() throws Throwable {
-        installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+        installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
         // Retry till the indexer has completed a run.
         retryAssert(
@@ -434,7 +367,7 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     public void indexAppWithDynamicSchema_multipleRootSchemas() throws Throwable {
-        installPackage(TEST_APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_PATH);
+        installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_PATH);
 
         // Retry till the indexer has completed a run.
         retryAssert(
@@ -463,8 +396,8 @@
     @Test
     public void indexMultipleAppsWithDynamicSchema() throws Throwable {
 
-        installPackage(TEST_APP_B_DYNAMIC_SCHEMA_PATH);
-        installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+        installPackage(mContext, TEST_APP_B_DYNAMIC_SCHEMA_PATH);
+        installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
         retryAssert(
                 () -> {
@@ -495,8 +428,8 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     public void indexAppsWithAndWithoutDynamicSchema() throws Throwable {
-        installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
-        installPackage(TEST_APP_B_V1_PATH);
+        installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+        installPackage(mContext, TEST_APP_B_V1_PATH);
 
         // Retry till the indexer has completed a run.
         retryAssert(
@@ -525,7 +458,7 @@
     @Test
     public void indexApp_updateToDynamicSchema() throws Throwable {
         {
-            installPackage(TEST_APP_A_V2_PATH);
+            installPackage(mContext, TEST_APP_A_V2_PATH);
 
             // Retry till the indexer has completed a run.
             retryAssert(
@@ -544,7 +477,7 @@
         }
 
         {
-            installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+            installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
             // Retry till the indexer has completed a run.
             retryAssert(
@@ -568,7 +501,7 @@
     @Test
     public void indexApp_updateToWithoutDynamicSchema() throws Throwable {
         {
-            installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+            installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
             // Retry till the indexer has completed a run.
             retryAssert(
@@ -589,7 +522,7 @@
         }
 
         {
-            installPackage(TEST_APP_A_V2_PATH);
+            installPackage(mContext, TEST_APP_A_V2_PATH);
 
             // Retry till the indexer has completed another run.
             retryAssert(
@@ -612,7 +545,7 @@
     @Test
     public void indexApp_updateToDynamicSchemaWithFewerTypes() throws Throwable {
         {
-            installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+            installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
             // Retry till the indexer has completed a run.
             retryAssert(
@@ -633,7 +566,7 @@
         }
 
         {
-            installPackage(TEST_APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PATH);
+            installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PATH);
 
             // Retry till the indexer has completed another run.
             retryAssert(
@@ -656,7 +589,7 @@
     @Test
     public void indexApp_updateToDynamicSchemaWithMoreTypesThanBefore() throws Throwable {
         {
-            installPackage(TEST_APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PATH);
+            installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PATH);
 
             // Retry till the indexer has completed a run.
             retryAssert(
@@ -677,7 +610,7 @@
         }
 
         {
-            installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+            installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
             // Retry till the indexer has completed another run.
             retryAssert(
@@ -700,8 +633,8 @@
     @Test
     public void indexApp_appFunctionServiceEnabledInRuntime_functionsIndexed() throws Throwable {
         {
-            installPackage(TEST_APP_A_APP_FUNCTION_SERVICE_DISABLED);
-            installPackage(TEST_APP_B_V1_PATH);
+            installPackage(mContext, TEST_APP_A_APP_FUNCTION_SERVICE_DISABLED);
+            installPackage(mContext, TEST_APP_B_V1_PATH);
 
             // Retry till the indexer has completed a run.
             retryAssert(
@@ -719,7 +652,7 @@
 
         {
             updateAppFunctionServiceEnabledState(
-                    TEST_APP_A_PKG, PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+                    mContext, TEST_APP_A_PKG, PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
 
             // Retry till the indexer has completed another run.
             retryAssert(
@@ -732,28 +665,12 @@
         }
     }
 
-    private void updateAppFunctionServiceEnabledState(String packageName, int newState) {
-        InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity(Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
-
-        mContext.getPackageManager()
-                .setComponentEnabledSetting(
-                        new ComponentName(
-                                packageName, "com.android.cts.appsearch.helper.AppFunctionService"),
-                        newState,
-                        /* flags= */ 0);
-        InstrumentationRegistry.getInstrumentation()
-                .getUiAutomation()
-                .dropShellPermissionIdentity();
-    }
-
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_INDEXER_RUN_ON_APP_FUNCTION_COMPONENT_CHANGE)
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     public void indexApp_appFunctionServiceDisabledInRuntime_functionsRemoved() throws Throwable {
         {
-            installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+            installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
             // Retry till the indexer has completed a run.
             retryAssert(
@@ -767,7 +684,7 @@
 
         {
             updateAppFunctionServiceEnabledState(
-                    TEST_APP_A_PKG, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+                    mContext, TEST_APP_A_PKG, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
 
             // Retry till the indexer has completed another run.
             retryAssert(
@@ -787,7 +704,7 @@
             indexApp_compChangeFlagDisabled_appFunctionServiceDisabledInRuntime_functionNotRemoved()
                     throws Throwable {
         {
-            installPackage(TEST_APP_A_DYNAMIC_SCHEMA_PATH);
+            installPackage(mContext, TEST_APP_A_DYNAMIC_SCHEMA_PATH);
 
             // Retry till the indexer has completed a run.
             retryAssert(
@@ -801,7 +718,7 @@
 
         {
             updateAppFunctionServiceEnabledState(
-                    TEST_APP_A_PKG, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+                    mContext, TEST_APP_A_PKG, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
 
             // Retry till the indexer has completed another run.
             retryAssert(
@@ -814,127 +731,4 @@
                     });
         }
     }
-
-    private GenericDocument clearTimestampsAndParentTypesInDocument(
-            @NonNull GenericDocument document) {
-        GenericDocument.Builder<?> builder =
-                new GenericDocument.Builder<>(document)
-                        .setCreationTimestampMillis(0)
-                        // GenericDocument#PARENT_TYPES_SYNTHETIC_PROPERTY is hidden
-                        .clearProperty("$$__AppSearch__parentTypes");
-
-        for (String propertyName : document.getPropertyNames()) {
-            Object property = document.getProperty(propertyName);
-            if (property instanceof GenericDocument[] nestedDocuments) {
-                GenericDocument[] clearedNestedDocuments =
-                        new GenericDocument[nestedDocuments.length];
-
-                for (int i = 0; i < nestedDocuments.length; i++) {
-                    clearedNestedDocuments[i] =
-                            clearTimestampsAndParentTypesInDocument(nestedDocuments[i]);
-                }
-
-                builder.setPropertyDocument(propertyName, clearedNestedDocuments);
-            }
-        }
-
-        return builder.build();
-    }
-
-    private GenericDocument searchMobileApplicationWithId(String id)
-            throws ExecutionException, InterruptedException {
-        GlobalSearchSessionShim globalSearchSession =
-                GlobalSearchSessionShimImpl.createGlobalSearchSessionAsync().get();
-
-        SearchResultsShim searchResults =
-                globalSearchSession.search(
-                        "",
-                        new SearchSpec.Builder()
-                                .addFilterNamespaces(NAMESPACE_MOBILE_APPLICATION)
-                                .addFilterPackageNames(INDEXER_PACKAGE_NAME)
-                                .build());
-        List<GenericDocument> genericDocuments = collectAllResults(searchResults);
-        for (int i = 0; i < genericDocuments.size(); i++) {
-            GenericDocument genericDocument = genericDocuments.get(i);
-            if (genericDocument.getId().equals(id)) {
-                return genericDocument;
-            }
-        }
-        return null;
-    }
-
-    private List<GenericDocument> searchAppFunctionsWithPackageName(String packageName)
-            throws ExecutionException, InterruptedException {
-        GlobalSearchSessionShim globalSearchSession =
-                GlobalSearchSessionShimImpl.createGlobalSearchSessionAsync().get();
-
-        SearchResultsShim searchResults =
-                globalSearchSession.search(
-                        String.format("packageName:\"%s\"", packageName),
-                        new SearchSpec.Builder()
-                                .addFilterNamespaces(NAMESPACE_APP_FUNCTIONS)
-                                .addFilterPackageNames(INDEXER_PACKAGE_NAME)
-                                .setVerbatimSearchEnabled(true)
-                                .build());
-        return collectAllResults(searchResults);
-    }
-
-    private Map<String, GenericDocument> searchAppFunctionDocumentsIntoMap(String packageName)
-            throws ExecutionException, InterruptedException {
-        Map<String, GenericDocument> appFns = new ArrayMap<>();
-        for (GenericDocument document : searchAppFunctionsWithPackageName(packageName)) {
-            appFns.put(document.getId(), document);
-        }
-
-        return appFns;
-    }
-
-    private List<GenericDocument> collectAllResults(SearchResultsShim searchResults)
-            throws ExecutionException, InterruptedException {
-        List<GenericDocument> documents = new ArrayList<>();
-        List<SearchResult> results;
-        do {
-            results = searchResults.getNextPageAsync().get();
-            for (SearchResult result : results) {
-                documents.add(result.getGenericDocument());
-            }
-        } while (results.size() > 0);
-        return documents;
-    }
-
-    private void installPackage(@NonNull String path) {
-        assertThat(
-                        SystemUtil.runShellCommand(
-                                String.format(
-                                        "pm install -r -i %s -t -g %s",
-                                        mContext.getPackageName(), path)))
-                .isEqualTo("Success\n");
-    }
-
-    private void uninstallPackage(@NonNull String packageName) {
-        SystemUtil.runShellCommand("pm uninstall " + packageName);
-    }
-
-    /** Retries an assertion with a delay between attempts. */
-    private static void retryAssert(ThrowRunnable runnable) throws Throwable {
-        Throwable lastError = null;
-
-        for (int attempt = 0; attempt < RETRY_MAX_INTERVALS; attempt++) {
-            try {
-                runnable.run();
-                return;
-            } catch (Throwable e) {
-                lastError = e;
-                if (attempt < RETRY_MAX_INTERVALS) {
-                    Thread.sleep(RETRY_CHECK_INTERVAL_MILLIS);
-                }
-            }
-        }
-        throw lastError;
-    }
-
-    /** Runnable that throws. */
-    public interface ThrowRunnable {
-        void run() throws Throwable;
-    }
 }
diff --git a/tests/appsearch/src/com/android/cts/appsearch/appsindexer/AppsIndexerCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/appsindexer/AppsIndexerCtsTest.java
new file mode 100644
index 0000000..4afdb15
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/appsindexer/AppsIndexerCtsTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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 android.app.appsearch.cts.appsindexer;
+
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.APP_PROPERTY_DISPLAY_NAME;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_PKG;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_V1_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.TEST_APP_A_V2_PATH;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.installPackage;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.retryAssert;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.searchMobileApplicationWithId;
+import static android.app.appsearch.testutil.AppsIndexerTestUtils.uninstallPackage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.testutil.AppSearchTestUtils;
+import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.appsearch.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+@RequiresFlagsEnabled(Flags.FLAG_APPS_INDEXER_ENABLED)
+public class AppsIndexerCtsTest {
+
+    @Rule public final RuleChain mRuleChain = AppSearchTestUtils.createCommonTestRules();
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    @Before
+    @After
+    public void uninstallTestApks() throws Throwable {
+        uninstallPackage(TEST_APP_A_PKG);
+
+        retryAssert(
+                () -> {
+                    assertThat(searchMobileApplicationWithId(TEST_APP_A_PKG)).isNull();
+                });
+    }
+
+    @Test
+    public void indexMobileApplications_packageChanges() throws Throwable {
+        {
+            // Install a new app
+            installPackage(mContext, TEST_APP_A_V1_PATH);
+
+            retryAssert(
+                    () -> {
+                        GenericDocument mobileApplication =
+                                searchMobileApplicationWithId(TEST_APP_A_PKG);
+                        assertThat(mobileApplication).isNotNull();
+                        assertThat(mobileApplication.getPropertyString(APP_PROPERTY_DISPLAY_NAME))
+                                .isEqualTo("App A [v1]");
+                    });
+        }
+
+        {
+            // Update it
+            installPackage(mContext, TEST_APP_A_V2_PATH);
+
+            retryAssert(
+                    () -> {
+                        GenericDocument mobileApplication =
+                                searchMobileApplicationWithId(TEST_APP_A_PKG);
+                        assertThat(mobileApplication).isNotNull();
+                        assertThat(mobileApplication.getPropertyString(APP_PROPERTY_DISPLAY_NAME))
+                                .isEqualTo("App A [v2]");
+                    });
+        }
+
+        {
+            // Uninstall it
+            uninstallPackage(TEST_APP_A_PKG);
+
+            retryAssert(
+                    () -> {
+                        GenericDocument mobileApplication =
+                                searchMobileApplicationWithId(TEST_APP_A_PKG);
+                        assertThat(mobileApplication).isNull();
+                    });
+        }
+    }
+}
diff --git a/tests/appsearch/testutils/Android.bp b/tests/appsearch/testutils/Android.bp
index 3a67c7a..3b03650 100644
--- a/tests/appsearch/testutils/Android.bp
+++ b/tests/appsearch/testutils/Android.bp
@@ -26,13 +26,14 @@
     libs: [
         "androidx.test.ext.junit",
         "appsearch_flags_java_lib",
+        "compatibility-device-util-axt",
         "framework-annotations-lib",
         "framework-appsearch.impl",
         "guava",
         "service-appsearch-for-tests",
         "truth",
     ],
-    sdk_version: "system_server_current",
+    platform_apis: true,
     visibility: [
         "//cts/hostsidetests/appsearch",
         "//cts/tests:__subpackages__",
diff --git a/tests/appsearch/testutils/src/android/app/appsearch/testutil/AppFunctionConstants.java b/tests/appsearch/testutils/src/android/app/appsearch/testutil/AppsIndexerTestUtils.java
similarity index 63%
rename from tests/appsearch/testutils/src/android/app/appsearch/testutil/AppFunctionConstants.java
rename to tests/appsearch/testutils/src/android/app/appsearch/testutil/AppsIndexerTestUtils.java
index 92379b4..389dad6 100644
--- a/tests/appsearch/testutils/src/android/app/appsearch/testutil/AppFunctionConstants.java
+++ b/tests/appsearch/testutils/src/android/app/appsearch/testutil/AppsIndexerTestUtils.java
@@ -16,13 +16,71 @@
 
 package android.app.appsearch.testutil;
 
-import android.app.appsearch.GenericDocument;
+import static com.google.common.truth.Truth.assertThat;
 
-/** Constants for AppFunction indexer tests. */
-public final class AppFunctionConstants {
+import android.Manifest;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GlobalSearchSessionShim;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+/** Utility class providing constants and helper methods for AppsIndexer tests. */
+public final class AppsIndexerTestUtils {
+    private static final String INDEXER_PACKAGE_NAME = "android";
+    private static final String TEST_APP_ROOT_FOLDER = "/data/local/tmp/cts/appsearch/";
+    private static final String NAMESPACE_MOBILE_APPLICATION = "apps";
     private static final String NAMESPACE_APP_FUNCTIONS = "app_functions";
-    private static final String TEST_APP_A_PKG = "com.android.cts.appsearch.indexertestapp.a";
-    private static final String TEST_APP_B_PKG = "com.android.cts.appsearch.indexertestapp.b";
+
+    private static final long RETRY_CHECK_INTERVAL_MILLIS = 500;
+    private static final long RETRY_MAX_INTERVALS = 10;
+
+    public static final String TEST_APP_A_V1_PATH =
+            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppAV1.apk";
+    public static final String TEST_APP_A_V2_PATH =
+            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppAV2.apk";
+    public static final String TEST_APP_A_V3_PATH =
+            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppAV3.apk";
+    public static final String TEST_APP_B_V1_PATH =
+            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppBV1.apk";
+    public static final String TEST_APP_A_DYNAMIC_SCHEMA_PATH =
+            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppADynamicSchema.apk";
+    public static final String TEST_APP_A_DYNAMIC_SCHEMA_FEWER_TYPES_PATH =
+            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppADynamicSchemaFewerTypes.apk";
+    public static final String TEST_APP_A_DYNAMIC_SCHEMA_MULTIPLE_ROOT_SCHEMAS_PATH =
+            TEST_APP_ROOT_FOLDER
+                    + "CtsAppSearchIndexerTestAppADynamicSchemaMultipleRootSchemas.apk";
+    public static final String TEST_APP_B_DYNAMIC_SCHEMA_PATH =
+            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppBDynamicSchema.apk";
+    public static final String TEST_APP_A_APP_FUNCTION_SERVICE_DISABLED =
+            TEST_APP_ROOT_FOLDER + "CtsAppSearchIndexerTestAppAAppFunctionServiceDisabled.apk";
+
+    public static final String TEST_APP_A_PKG = "com.android.cts.appsearch.indexertestapp.a";
+    public static final String TEST_APP_B_PKG = "com.android.cts.appsearch.indexertestapp.b";
+
+    public static final String APP_PROPERTY_DISPLAY_NAME = "displayName";
+    public static final String PROPERTY_FUNCTION_ID = "functionId";
+    public static final String PROPERTY_PACKAGE_NAME = "packageName";
+    public static final String PROPERTY_SCHEMA_NAME = "schemaName";
+    public static final String PROPERTY_SCHEMA_VERSION = "schemaVersion";
+    public static final String PROPERTY_SCHEMA_CATEGORY = "schemaCategory";
+    public static final String PROPERTY_DISPLAY_NAME_STRING_RES = "displayNameStringRes";
+    public static final String PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
+    public static final String PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS =
+            "restrictCallersWithExecuteAppFunctions";
 
     /** Print app function generic document as defined in the appfunctions.xml of App A V2. */
     public static final GenericDocument APP_A_V2_PRINT_APP_FUNCTION =
@@ -120,6 +178,148 @@
     public static final GenericDocument APP_B_DYNAMIC_SCHEMA_PRINT_APP_FUNCTION =
             buildPrintAppFunctionDocument(TEST_APP_B_PKG);
 
+    /** Updates the enabled state of the AppFunctionService for a given package. */
+    public static void updateAppFunctionServiceEnabledState(
+            Context context, String packageName, int newState) {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+
+        context.getPackageManager()
+                .setComponentEnabledSetting(
+                        new ComponentName(
+                                packageName, "com.android.cts.appsearch.helper.AppFunctionService"),
+                        newState,
+                        /* flags= */ 0);
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /** Recursively removes timestamps and parent types from a document. */
+    public static GenericDocument clearTimestampsAndParentTypesInDocument(
+            @NonNull GenericDocument document) {
+        GenericDocument.Builder<?> builder =
+                new GenericDocument.Builder<>(document)
+                        .setCreationTimestampMillis(0)
+                        // GenericDocument#PARENT_TYPES_SYNTHETIC_PROPERTY is hidden
+                        .clearProperty("$$__AppSearch__parentTypes");
+
+        for (String propertyName : document.getPropertyNames()) {
+            Object property = document.getProperty(propertyName);
+            if (property instanceof GenericDocument[] nestedDocuments) {
+                GenericDocument[] clearedNestedDocuments =
+                        new GenericDocument[nestedDocuments.length];
+
+                for (int i = 0; i < nestedDocuments.length; i++) {
+                    clearedNestedDocuments[i] =
+                            clearTimestampsAndParentTypesInDocument(nestedDocuments[i]);
+                }
+
+                builder.setPropertyDocument(propertyName, clearedNestedDocuments);
+            }
+        }
+
+        return builder.build();
+    }
+
+    /** Queries GlobalSearchSession for a MobileApplication by its document ID. */
+    public static GenericDocument searchMobileApplicationWithId(String id)
+            throws ExecutionException, InterruptedException {
+        GlobalSearchSessionShim globalSearchSession =
+                GlobalSearchSessionShimImpl.createGlobalSearchSessionAsync().get();
+
+        SearchResultsShim searchResults =
+                globalSearchSession.search(
+                        "",
+                        new SearchSpec.Builder()
+                                .addFilterNamespaces(NAMESPACE_MOBILE_APPLICATION)
+                                .addFilterPackageNames(INDEXER_PACKAGE_NAME)
+                                .build());
+        List<GenericDocument> genericDocuments = collectAllResults(searchResults);
+        for (int i = 0; i < genericDocuments.size(); i++) {
+            GenericDocument genericDocument = genericDocuments.get(i);
+            if (genericDocument.getId().equals(id)) {
+                return genericDocument;
+            }
+        }
+        return null;
+    }
+
+    /** Queries GlobalSearchSession for AppFunction documents by package name. */
+    public static List<GenericDocument> searchAppFunctionsWithPackageName(String packageName)
+            throws ExecutionException, InterruptedException {
+        GlobalSearchSessionShim globalSearchSession =
+                GlobalSearchSessionShimImpl.createGlobalSearchSessionAsync().get();
+
+        SearchResultsShim searchResults =
+                globalSearchSession.search(
+                        String.format("packageName:\"%s\"", packageName),
+                        new SearchSpec.Builder()
+                                .addFilterNamespaces(NAMESPACE_APP_FUNCTIONS)
+                                .addFilterPackageNames(INDEXER_PACKAGE_NAME)
+                                .setVerbatimSearchEnabled(true)
+                                .build());
+        return collectAllResults(searchResults);
+    }
+
+    /** Returns a map of AppFunction documents for a package name keyed by the document ID. */
+    public static Map<String, GenericDocument> searchAppFunctionDocumentsIntoMap(String packageName)
+            throws ExecutionException, InterruptedException {
+        Map<String, GenericDocument> appFns = new ArrayMap<>();
+        for (GenericDocument document : searchAppFunctionsWithPackageName(packageName)) {
+            appFns.put(document.getId(), document);
+        }
+
+        return appFns;
+    }
+
+    /** Installs an APK from a given path and asserts success. */
+    public static void installPackage(@NonNull Context context, @NonNull String path) {
+        assertThat(
+                        SystemUtil.runShellCommand(
+                                String.format(
+                                        "pm install -r -i %s -t -g %s",
+                                        context.getPackageName(), path)))
+                .isEqualTo("Success\n");
+    }
+
+    /** Uninstalls an Android package by package name. */
+    public static void uninstallPackage(@NonNull String packageName) {
+        SystemUtil.runShellCommand("pm uninstall " + packageName);
+    }
+
+    /** Retries an assertion with a delay between attempts. */
+    public static void retryAssert(ThrowRunnable runnable) throws Throwable {
+        Throwable lastError = null;
+
+        for (int attempt = 0; attempt < RETRY_MAX_INTERVALS; attempt++) {
+            try {
+                runnable.run();
+                return;
+            } catch (Throwable e) {
+                lastError = e;
+                if (attempt < RETRY_MAX_INTERVALS) {
+                    Thread.sleep(RETRY_CHECK_INTERVAL_MILLIS);
+                }
+            }
+        }
+        throw lastError;
+    }
+
+    private static List<GenericDocument> collectAllResults(SearchResultsShim searchResults)
+            throws ExecutionException, InterruptedException {
+        List<GenericDocument> documents = new ArrayList<>();
+        List<SearchResult> results;
+        do {
+            results = searchResults.getNextPageAsync().get();
+            for (SearchResult result : results) {
+                documents.add(result.getGenericDocument());
+            }
+        } while (!results.isEmpty());
+        return documents;
+    }
+
     /**
      * Builds the generic document for print app function defined in app A with dynamic schema.
      *
@@ -294,7 +494,8 @@
                 new GenericDocument.Builder<>(
                                 NAMESPACE_APP_FUNCTIONS,
                                 packageName
-                                        + "/com.example.utils#print/response/schema/properties0/schema",
+                                        + "/com.example.utils#print/response"
+                                        + "/schema/properties0/schema",
                                 "AppFunctionSchema-" + packageName)
                         .setCreationTimestampMillis(0)
                         .setPropertyLong("dataType", 8)
@@ -338,7 +539,8 @@
                 new GenericDocument.Builder<>(
                                 NAMESPACE_APP_FUNCTIONS,
                                 packageName
-                                        + "/com.example.utils#print/components0/schema/properties0/schema",
+                                        + "/com.example.utils#print/components0"
+                                        + "/schema/properties0/schema",
                                 "AppFunctionSchema-" + packageName)
                         .setCreationTimestampMillis(0)
                         .setPropertyLong("dataType", 8)
@@ -381,5 +583,11 @@
         return builder.build();
     }
 
-    private AppFunctionConstants() {}
+    /** Runnable that throws. */
+    public interface ThrowRunnable {
+        /** Executes the action. */
+        void run() throws Throwable;
+    }
+
+    private AppsIndexerTestUtils() {}
 }