Extend Rb integration with AppSearch to consent data.

Test: atest AppSearchConsentManagerTest AppSearchDaoTest AppSearchConsentDaoTest AppSearchConsentWorkerTest ConsentManagerTest AppSearchAppConsentDaoTest
Bug: b/263297331
Change-Id: Ic5fde2b51a627a4225f12f6a19c8e326efd4d70e
diff --git a/adservices/apk/tests/Settings/src/com/android/adservices/ui/settings/AppConsentSettingsUiAutomatorTest.java b/adservices/apk/tests/Settings/src/com/android/adservices/ui/settings/AppConsentSettingsUiAutomatorTest.java
index cb7df7a..a03b998 100644
--- a/adservices/apk/tests/Settings/src/com/android/adservices/ui/settings/AppConsentSettingsUiAutomatorTest.java
+++ b/adservices/apk/tests/Settings/src/com/android/adservices/ui/settings/AppConsentSettingsUiAutomatorTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -37,6 +38,7 @@
 import com.android.adservices.api.R;
 import com.android.adservices.common.AdservicesTestHelper;
 import com.android.adservices.common.CompatAdServicesTestUtils;
+import com.android.adservices.service.Flags;
 import com.android.compatibility.common.util.ShellUtils;
 import com.android.modules.utils.build.SdkLevel;
 
@@ -61,8 +63,6 @@
     private static final String PRIVACY_SANDBOX_TEST_PACKAGE = "android.test.adservices.ui.MAIN";
     private static final int LAUNCH_TIMEOUT = 5000;
     private static UiDevice sDevice;
-    private static final String ADEXTSERVICES_PACKAGE_NAME =
-            "com.google.android.ext.adservices.api";
 
     @Before
     public void setup() throws UiObjectNotFoundException {
@@ -126,6 +126,27 @@
 
     @Test
     @Ignore("Flaky test. (b/268351419)")
+    public void consentAppSearchOnlyTest() throws UiObjectNotFoundException, InterruptedException {
+        ShellUtils.runShellCommand(
+                "device_config put adservices enable_appsearch_consent_data true");
+        ShellUtils.runShellCommand("device_config put adservices consent_source_of_truth 3");
+        appConsentTest(Flags.APPSEARCH_ONLY, false);
+        ShellUtils.runShellCommand("device_config put adservices consent_source_of_truth null");
+    }
+
+    @Test
+    @Ignore("Flaky test. (b/268351419)")
+    public void consentAppSearchOnlyDialogsOnTest()
+            throws UiObjectNotFoundException, InterruptedException {
+        ShellUtils.runShellCommand(
+                "device_config put adservices enable_appsearch_consent_data true");
+        ShellUtils.runShellCommand("device_config put adservices consent_source_of_truth 3");
+        appConsentTest(Flags.APPSEARCH_ONLY, true);
+        ShellUtils.runShellCommand("device_config put adservices consent_source_of_truth null");
+    }
+
+    @Test
+    @Ignore("Flaky test. (b/268351419)")
     public void consentSystemServerOnlyDialogsOnTest()
             throws UiObjectNotFoundException, InterruptedException {
         // System server is not available on S-, skip this test for S-
@@ -291,6 +312,8 @@
         ShellUtils.runShellCommand(
                 "device_config put adservices"
                         + " fledge_custom_audience_service_kill_switch false");
+        ShellUtils.runShellCommand(
+                "device_config put adservices disable_fledge_enrollment_check true");
 
         ShellUtils.runShellCommand("device_config put adservices ppapi_app_allow_list *");
 
@@ -306,5 +329,7 @@
         ShellUtils.runShellCommand("device_config set_sync_disabled_for_tests none");
         ShellUtils.runShellCommand(
                 "am force-stop com.example.adservices.samples.ui.consenttestapp");
+        ShellUtils.runShellCommand(
+                "device_config put adservices disable_fledge_enrollment_check null");
     }
 }
diff --git a/adservices/apk/tests/Settings/src/com/android/adservices/ui/settings/ConsentSettingsUiAutomatorTest.java b/adservices/apk/tests/Settings/src/com/android/adservices/ui/settings/ConsentSettingsUiAutomatorTest.java
index b1000d6..22e29d1 100644
--- a/adservices/apk/tests/Settings/src/com/android/adservices/ui/settings/ConsentSettingsUiAutomatorTest.java
+++ b/adservices/apk/tests/Settings/src/com/android/adservices/ui/settings/ConsentSettingsUiAutomatorTest.java
@@ -161,10 +161,30 @@
     @Test
     public void consentAppSearchOnlyTest() throws UiObjectNotFoundException {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
-            doReturn(3).when(mMockFlags).getConsentSourceOfTruth();
+            doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+            doReturn(Flags.APPSEARCH_ONLY).when(mMockFlags).getConsentSourceOfTruth();
+            consentTest(true);
+        } else {
+            ShellUtils.runShellCommand(
+                    "device_config put adservices enable_appsearch_consent_data true");
+            ShellUtils.runShellCommand("device_config put adservices consent_source_of_truth 3");
+            consentTest(true);
+            ShellUtils.runShellCommand("device_config put adservices consent_source_of_truth null");
+            ShellUtils.runShellCommand(
+                    "device_config put adservices enable_appsearch_consent_data null");
+        }
+    }
+
+    @Test
+    public void consentAppSearchOnlyDialogsOnTest() throws UiObjectNotFoundException {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+            doReturn(Flags.APPSEARCH_ONLY).when(mMockFlags).getConsentSourceOfTruth();
             doReturn(true).when(mPhFlags).getUIDialogsFeatureEnabled();
             consentTest(true);
         } else {
+            ShellUtils.runShellCommand(
+                    "device_config put adservices enable_appsearch_consent_data true");
             ShellUtils.runShellCommand("device_config put adservices consent_source_of_truth 3");
             ShellUtils.runShellCommand(
                     "device_config put adservices ui_dialogs_feature_enabled true");
@@ -174,7 +194,6 @@
     }
 
     private void consentTest(boolean dialogsOn) throws UiObjectNotFoundException {
-
         ApkTestUtil.launchSettingView(
                 ApplicationProvider.getApplicationContext(), sDevice, LAUNCH_TIMEOUT);
 
diff --git a/adservices/service-core/java/com/android/adservices/service/Flags.java b/adservices/service-core/java/com/android/adservices/service/Flags.java
index dac66b8..484c11e 100644
--- a/adservices/service-core/java/com/android/adservices/service/Flags.java
+++ b/adservices/service-core/java/com/android/adservices/service/Flags.java
@@ -26,6 +26,7 @@
 import com.android.adservices.data.adselection.DBRegisteredAdInteraction;
 import com.android.adservices.service.adselection.AdOutcomeSelectorImpl;
 import com.android.adservices.service.common.cache.FledgeHttpCache;
+import com.android.modules.utils.build.SdkLevel;
 
 import com.google.common.collect.ImmutableList;
 
@@ -881,11 +882,21 @@
     int PPAPI_ONLY = 1;
     /** Write consent to both PPAPI and system server. Read consent from system server only. */
     int PPAPI_AND_SYSTEM_SERVER = 2;
-    /** Write consent data to AppSearch only. */
+    /**
+     * Write consent data to AppSearch only. To store consent data in AppSearch the flag
+     * enable_appsearch_consent_data must also be true. This ensures that both writes and reads can
+     * happen to/from AppSearch. The writes are done by code on S-, while reads are done from code
+     * running on S- for all consent requests and on T+ once after OTA.
+     */
     int APPSEARCH_ONLY = 3;
 
-    /* Consent source of truth intended to be used by default. */
-    @ConsentSourceOfTruth int DEFAULT_CONSENT_SOURCE_OF_TRUTH = PPAPI_AND_SYSTEM_SERVER;
+    /**
+     * Consent source of truth intended to be used by default. On S- devices, there is no AdServices
+     * code running in the system server, so the default for those is PPAPI_ONLY.
+     */
+    @ConsentSourceOfTruth
+    int DEFAULT_CONSENT_SOURCE_OF_TRUTH =
+            SdkLevel.isAtLeastT() ? PPAPI_AND_SYSTEM_SERVER : PPAPI_ONLY;
 
     /** Returns the consent source of truth currently used for PPAPI. */
     @ConsentSourceOfTruth
diff --git a/adservices/service-core/java/com/android/adservices/service/PhFlags.java b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
index af06f20..a1fd454 100644
--- a/adservices/service-core/java/com/android/adservices/service/PhFlags.java
+++ b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
@@ -2954,13 +2954,11 @@
 
     @Override
     public boolean getEnableAppsearchConsentData() {
-        // Check if enable Back compat is true first and then check flag value
         // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value.
-        return getEnableBackCompat()
-                && DeviceConfig.getBoolean(
-                        NAMESPACE_ADSERVICES,
-                        /* flagName */ KEY_ENABLE_APPSEARCH_CONSENT_DATA,
-                        /* defaultValue */ ENABLE_APPSEARCH_CONSENT_DATA);
+        return DeviceConfig.getBoolean(
+                NAMESPACE_ADSERVICES,
+                /* flagName */ KEY_ENABLE_APPSEARCH_CONSENT_DATA,
+                /* defaultValue */ ENABLE_APPSEARCH_CONSENT_DATA);
     }
 
     @Override
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchAppConsentDao.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchAppConsentDao.java
new file mode 100644
index 0000000..71be5c0
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchAppConsentDao.java
@@ -0,0 +1,216 @@
+/*
+ * 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.adservices.service.appsearch;
+
+import android.annotation.NonNull;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.appsearch.annotation.Document;
+import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig;
+import androidx.appsearch.app.GlobalSearchSession;
+
+import com.android.adservices.LogUtil;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/** This class represents the data access object for the app consent data written to AppSearch. */
+// TODO(b/269798827): Enable for R.
+@RequiresApi(Build.VERSION_CODES.S)
+@Document
+class AppSearchAppConsentDao extends AppSearchDao {
+    /**
+     * Identifier of the Consent Document; must be unique within the Document's `namespace`. This is
+     * the row ID for consent data. It is a combination of user ID and consent type.
+     */
+    @Document.Id private final String mId;
+
+    @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+    private final String mUserId;
+
+    /** Namespace of the Consent Document. Used to group documents during querying or deletion. */
+    @Document.Namespace private final String mNamespace;
+
+    /**
+     * Consent type for this table. Possible values are: APPS_WITH_CONSENT,
+     * APPS_WITH_REVOKED_CONSENT, APPS_WITH_FLEDGE_CONSENT and APPS_WITH_FLEDGE_REVOKED_CONSENT.
+     */
+    @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+    private final String mConsentType;
+
+    /** List of apps. */
+    @Document.StringProperty private List<String> mApps;
+
+    // Column names used for preparing the query string, are not part of the @Document.
+    private static final String USER_ID_COLNAME = "userId";
+    private static final String CONSENT_TYPE_COLNAME = "consentType";
+    public static final String NAMESPACE = "appConsent";
+
+    // Consent types we store with this DAO.
+    public static final String APPS_WITH_CONSENT = "APPS_WITH_CONSENT";
+    public static final String APPS_WITH_REVOKED_CONSENT = "APPS_WITH_REVOKED_CONSENT";
+
+    /**
+     * Create an AppSearchConsentDao instance.
+     *
+     * @param id is a combination of the user ID and apiType
+     * @param userId is the user ID for this user
+     * @param namespace (required by AppSearch)
+     * @param consentType is the consentType for which we are storing consent data
+     * @param apps list of apps
+     */
+    AppSearchAppConsentDao(
+            String id, String userId, String namespace, String consentType, List<String> apps) {
+        this.mId = id;
+        this.mUserId = userId;
+        this.mNamespace = namespace;
+        this.mConsentType = consentType;
+        this.mApps = apps;
+    }
+
+    /**
+     * Get the row ID for this row.
+     *
+     * @return ID
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Get the user ID for this row.
+     *
+     * @return user ID
+     */
+    public String getUserId() {
+        return mUserId;
+    }
+
+    /**
+     * Get the namespace for this row.
+     *
+     * @return nameespace
+     */
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    /**
+     * Get the apiType for this row.
+     *
+     * @return apiType
+     */
+    public String getConsentType() {
+        return mConsentType;
+    }
+
+    /**
+     * Gets the list of apps.
+     *
+     * @return List of app package names.
+     */
+    public List<String> getApps() {
+        return mApps;
+    }
+
+    /** Sets the apps. */
+    public void setApps(List<String> apps) {
+        mApps = apps;
+    }
+
+    /** Returns the row ID that should be unique for the consent namespace. */
+    public static String getRowId(@NonNull String uid, @NonNull String consentType) {
+        Objects.requireNonNull(uid);
+        Objects.requireNonNull(consentType);
+        return uid + "_" + consentType;
+    }
+
+    /**
+     * Converts the DAO to a string.
+     *
+     * @return string representing the DAO.
+     */
+    public String toString() {
+        return "id="
+                + mId
+                + "; userId="
+                + mUserId
+                + "; consentType="
+                + mConsentType
+                + "; namespace="
+                + mNamespace
+                + "; apps="
+                + (mApps == null ? "null" : Arrays.toString(mApps.toArray()));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId, mUserId, mNamespace, mApps, mConsentType);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AppSearchAppConsentDao)) return false;
+        AppSearchAppConsentDao obj = (AppSearchAppConsentDao) o;
+        return (Objects.equals(this.mId, obj.mId))
+                && (Objects.equals(this.mUserId, obj.mUserId))
+                && (Objects.equals(this.mConsentType, obj.mConsentType))
+                && (Objects.equals(this.mNamespace, obj.mNamespace))
+                && (Objects.equals(this.mApps, obj.mApps));
+    }
+
+    /**
+     * Read the consent data from AppSearch.
+     *
+     * @param searchSession we use GlobalSearchSession here to allow AdServices to read.
+     * @param executor the Executor to use.
+     * @param userId the user ID for the query.
+     * @param apiType the API type for the query.
+     * @return whether the row is consented for this user ID and apiType.
+     */
+    static AppSearchAppConsentDao readConsentData(
+            @NonNull ListenableFuture<GlobalSearchSession> searchSession,
+            @NonNull Executor executor,
+            @NonNull String userId,
+            @NonNull String apiType) {
+        Objects.requireNonNull(searchSession);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(userId);
+        Objects.requireNonNull(apiType);
+
+        String query = getQuery(userId, apiType);
+        AppSearchAppConsentDao dao =
+                AppSearchDao.readConsentData(
+                        AppSearchAppConsentDao.class, searchSession, executor, NAMESPACE, query);
+        LogUtil.d("AppSearch app consent data read: " + dao + " [ query: " + query + "]");
+        return dao;
+    }
+
+    // Get the search query for AppSearch. Format specified at http://shortn/_RwVKmB74f3.
+    // Note: AND as an operator is not supported by AppSearch on S or T.
+    @VisibleForTesting
+    static String getQuery(String userId, String consentType) {
+        return USER_ID_COLNAME + ":" + userId + " " + CONSENT_TYPE_COLNAME + ":" + consentType;
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentDao.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentDao.java
index 75aba03..095c28a 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentDao.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentDao.java
@@ -16,6 +16,7 @@
 
 package com.android.adservices.service.appsearch;
 
+import android.annotation.NonNull;
 import android.os.Build;
 
 import androidx.annotation.RequiresApi;
@@ -35,7 +36,7 @@
 // TODO(b/269798827): Enable for R.
 @RequiresApi(Build.VERSION_CODES.S)
 @Document
-public class AppSearchConsentDao extends AppSearchDao {
+class AppSearchConsentDao extends AppSearchDao {
     /**
      * Identifier of the Consent Document; must be unique within the Document's `namespace`. This is
      * the row ID for consent data. It is a combination of user ID and api type.
@@ -49,8 +50,9 @@
     @Document.Namespace private final String mNamespace;
 
     /**
-     * API type for this consent. Possible values are CONSENT, CONSENT-FLEDGE, CONSENT-MEASUREMENT
-     * and CONSENT-TOPICS.
+     * API type for this consent. Possible values are a) CONSENT, CONSENT-FLEDGE,
+     * CONSENT-MEASUREMENT, CONSENT-TOPICS, b) DEFAULT_CONSENT, TOPICS_DEFAULT_CONSENT,
+     * FLEDGE_DEFAULT_CONSENT, MEASUREMENT_DEFAULT_CONSENT and c) DEFAULT_AD_ID_STATE.
      */
     @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
     private final String mApiType;
@@ -131,6 +133,11 @@
         return mConsent.equals("true");
     }
 
+    /** Returns the row ID that should be unique for the consent namespace. */
+    public static String getRowId(@NonNull String uid, @NonNull String apiType) {
+        return uid + "_" + apiType;
+    }
+
     /**
      * Converts the DAO to a string.
      *
@@ -175,18 +182,21 @@
      * @param apiType the API type for the query.
      * @return whether the row is consented for this user ID and apiType.
      */
-    public static boolean readConsentData(
-            ListenableFuture<GlobalSearchSession> searchSession,
-            Executor executor,
-            String userId,
-            String apiType) {
+    static boolean readConsentData(
+            @NonNull ListenableFuture<GlobalSearchSession> searchSession,
+            @NonNull Executor executor,
+            @NonNull String userId,
+            @NonNull String apiType) {
+        Objects.requireNonNull(searchSession);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(userId);
+        Objects.requireNonNull(apiType);
+
+        String query = getQuery(userId, apiType);
         AppSearchConsentDao dao =
                 AppSearchDao.readConsentData(
-                        AppSearchConsentDao.class,
-                        searchSession,
-                        executor,
-                        getQuery(userId, apiType));
-        LogUtil.d("AppSearch consent data read: " + dao + " [" + userId + ":" + apiType + "]");
+                        AppSearchConsentDao.class, searchSession, executor, NAMESPACE, query);
+        LogUtil.d("AppSearch app consent data read: " + dao + " [ query: " + query + "]");
         if (dao == null) {
             return false;
         }
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentManager.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentManager.java
new file mode 100644
index 0000000..42fc7cd
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentManager.java
@@ -0,0 +1,308 @@
+/*
+ * 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.adservices.service.appsearch;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.service.common.compat.PackageManagerCompatUtils;
+import com.android.adservices.service.common.feature.PrivacySandboxFeatureType;
+import com.android.adservices.service.consent.App;
+import com.android.adservices.service.consent.ConsentManager;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * This class manages the interface to AppSearch for reading/writing all AdServices consent data on
+ * S- devices. This is needed because AdServices does not run any code in the system server on S-
+ * devices, so consent data is rollback safe by storing it in AppSearch.
+ */
+// TODO(b/269798827): Enable for R.
+@RequiresApi(Build.VERSION_CODES.S)
+public class AppSearchConsentManager {
+    private Context mContext;
+    private AppSearchConsentWorker mAppSearchConsentWorker;
+
+    private AppSearchConsentManager(
+            @NonNull Context context, @NonNull AppSearchConsentWorker appSearchConsentWorker) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(appSearchConsentWorker);
+        mContext = context;
+        mAppSearchConsentWorker = appSearchConsentWorker;
+    }
+
+    /** Returns an instance of AppSearchConsentManager. */
+    public static AppSearchConsentManager getInstance(@NonNull Context context) {
+        Objects.requireNonNull(context);
+        return new AppSearchConsentManager(context, AppSearchConsentWorker.getInstance(context));
+    }
+
+    /**
+     * Get the consent for this user ID for this API type, as stored in AppSearch. Returns false if
+     * the database doesn't exist in AppSearch.
+     */
+    public boolean getConsent(@NonNull String apiType) {
+        Objects.requireNonNull(apiType);
+        return mAppSearchConsentWorker.getConsent(apiType);
+    }
+
+    /**
+     * Sets the consent for this user ID for this API type in AppSearch. If we do not get
+     * confirmation that the write was successful, then we throw an exception so that user does not
+     * incorrectly think that the consent is updated.
+     */
+    public void setConsent(@NonNull String apiType, @NonNull Boolean consented) {
+        Objects.requireNonNull(apiType);
+        Objects.requireNonNull(consented);
+        mAppSearchConsentWorker.setConsent(apiType, consented);
+    }
+
+    /**
+     * Get known apps with consent as stored in AppSearch.
+     *
+     * @return an {@link ImmutableList} of all known apps in the database that have not had user
+     *     consent revoked.
+     */
+    public ImmutableList<App> getKnownAppsWithConsent() {
+        List<String> apps =
+                mAppSearchConsentWorker.getAppsWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_CONSENT);
+        Set<String> installedPackages = getInstalledPackages();
+        List<App> result = new ArrayList<>();
+        for (String app : apps) {
+            if (installedPackages.contains(app)) {
+                result.add(App.create(app));
+            }
+        }
+        return ImmutableList.copyOf(result);
+    }
+
+    /**
+     * Get apps with consent revoked, as stored in AppSearch.
+     *
+     * @return an {@link ImmutableList} of all known apps in the database that have had user consent
+     *     revoked.
+     */
+    public ImmutableList<App> getAppsWithRevokedConsent() {
+        List<String> apps =
+                mAppSearchConsentWorker.getAppsWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT);
+        Set<String> installedPackages = getInstalledPackages();
+        List<App> result = new ArrayList<>();
+        for (String app : apps) {
+            if (installedPackages.contains(app)) {
+                result.add(App.create(app));
+            }
+        }
+        return ImmutableList.copyOf(result);
+    }
+
+    /**
+     * Clears all app data related to the provided {@link App}.
+     *
+     * @param app {@link App} to block.
+     */
+    public void revokeConsentForApp(@NonNull App app) {
+        Objects.requireNonNull(app);
+        mAppSearchConsentWorker.addAppWithConsent(
+                AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT, app.getPackageName());
+        mAppSearchConsentWorker.removeAppWithConsent(
+                AppSearchAppConsentDao.APPS_WITH_CONSENT, app.getPackageName());
+    }
+
+    /**
+     * Restore consent for provided {@link App}.
+     *
+     * @param app {@link App} to restore consent for.
+     */
+    public void restoreConsentForApp(@NonNull App app) {
+        Objects.requireNonNull(app);
+        mAppSearchConsentWorker.removeAppWithConsent(
+                AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT, app.getPackageName());
+        mAppSearchConsentWorker.addAppWithConsent(
+                AppSearchAppConsentDao.APPS_WITH_CONSENT, app.getPackageName());
+    }
+
+    /**
+     * Deletes all app consent data and all app data gathered or generated by the Privacy Sandbox.
+     *
+     * <p>This should be called when the Privacy Sandbox has been disabled.
+     */
+    public void clearAllAppConsentData() {
+        mAppSearchConsentWorker.clearAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT);
+        mAppSearchConsentWorker.clearAppsWithConsent(
+                AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT);
+    }
+
+    /**
+     * Deletes the list of known allowed apps as well as all app data from the Privacy Sandbox.
+     *
+     * <p>The list of blocked apps is not reset.
+     */
+    public void clearKnownAppsWithConsent() throws IOException {
+        mAppSearchConsentWorker.clearAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT);
+    }
+
+    /**
+     * Checks whether a single given installed application (identified by its package name) has had
+     * user consent to use the FLEDGE APIs revoked.
+     *
+     * <p>This method also checks whether a user has opted out of the FLEDGE Privacy Sandbox
+     * initiative.
+     *
+     * @param packageName String package name that uniquely identifies an installed application to
+     *     check
+     * @return {@code true} if either the FLEDGE Privacy Sandbox initiative has been opted out or if
+     *     the user has revoked consent for the given application to use the FLEDGE APIs
+     * @throws IllegalArgumentException if the package name is invalid or not found as an installed
+     *     application
+     */
+    public boolean isFledgeConsentRevokedForApp(@NonNull String packageName) {
+        Objects.requireNonNull(packageName);
+
+        boolean isConsented =
+                mAppSearchConsentWorker
+                        .getAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT)
+                        .contains(packageName);
+        boolean isRevoked =
+                mAppSearchConsentWorker
+                        .getAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT)
+                        .contains(packageName);
+        return isRevoked || !isConsented;
+    }
+
+    /**
+     * Persists the use of a FLEDGE API by a single given installed application (identified by its
+     * package name) if the app has not already had its consent revoked.
+     *
+     * <p>This method also checks whether a user has opted out of the FLEDGE Privacy Sandbox
+     * initiative.
+     *
+     * <p>This is only meant to be called by the FLEDGE APIs.
+     *
+     * @param packageName String package name that uniquely identifies an installed application that
+     *     has used a FLEDGE API
+     * @return {@code true} if user consent has been revoked for the application or API, {@code
+     *     false} otherwise
+     * @throws IllegalArgumentException if the package name is invalid or not found as an installed
+     *     application
+     */
+    public boolean isFledgeConsentRevokedForAppAfterSettingFledgeUse(@NonNull String packageName) {
+        Objects.requireNonNull(packageName);
+
+        return !mAppSearchConsentWorker.addAppWithConsent(
+                AppSearchAppConsentDao.APPS_WITH_CONSENT, packageName);
+    }
+
+    /**
+     * Clear consent data after an app was uninstalled, but the package Uid is unavailable. This
+     * happens because the INTERACT_ACROSS_USERS_FULL permission is not available on Android
+     * versions prior to T.
+     *
+     * <p><strong>This method should only be used for R/S back-compat scenarios.</strong>
+     *
+     * @param packageName the package name that had been uninstalled.
+     */
+    public void clearConsentForUninstalledApp(@NonNull String packageName) {
+        Objects.requireNonNull(packageName);
+
+        mAppSearchConsentWorker.removeAppWithConsent(
+                AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT, packageName);
+        mAppSearchConsentWorker.removeAppWithConsent(
+                AppSearchAppConsentDao.APPS_WITH_CONSENT, packageName);
+    }
+
+    /**
+     * Saves information to the storage that notification was displayed for the first time to the
+     * user.
+     */
+    public void recordNotificationDisplayed() {
+        mAppSearchConsentWorker.recordNotificationDisplayed();
+    }
+
+    /**
+     * Retrieves if notification has been displayed.
+     *
+     * @return true if Consent Notification was displayed, otherwise false.
+     */
+    public Boolean wasNotificationDisplayed() {
+        return mAppSearchConsentWorker.wasNotificationDisplayed();
+    }
+
+    /**
+     * Saves information to the storage that GA UX notification was displayed for the first time to
+     * the user.
+     */
+    public void recordGaUxNotificationDisplayed() {
+        mAppSearchConsentWorker.recordGaUxNotificationDisplayed();
+    }
+
+    /**
+     * Retrieves if GA UX notification has been displayed.
+     *
+     * @return true if GA UX Consent Notification was displayed, otherwise false.
+     */
+    public Boolean wasGaUxNotificationDisplayed() {
+        return mAppSearchConsentWorker.wasGaUxNotificationDisplayed();
+    }
+
+    /** Get the current privacy sandbox feature. */
+    public PrivacySandboxFeatureType getCurrentPrivacySandboxFeature() {
+        return mAppSearchConsentWorker.getPrivacySandboxFeature();
+    }
+
+    /** Set the current privacy sandbox feature. */
+    public void setCurrentPrivacySandboxFeature(
+            @NonNull PrivacySandboxFeatureType currentFeatureType) {
+        Objects.requireNonNull(currentFeatureType);
+        mAppSearchConsentWorker.setCurrentPrivacySandboxFeature(currentFeatureType);
+    }
+
+    /**
+     * Returns information whether user interacted with consent manually.
+     *
+     * @return true if the user interacted with the consent manually, otherwise false.
+     */
+    public @ConsentManager.UserManualInteraction int getUserManualInteractionWithConsent() {
+        return mAppSearchConsentWorker.getUserManualInteractionWithConsent();
+    }
+
+    /** Saves information to the storage that user interacted with consent manually. */
+    public void recordUserManualInteractionWithConsent(
+            @ConsentManager.UserManualInteraction int interaction) {
+        mAppSearchConsentWorker.recordUserManualInteractionWithConsent(interaction);
+    }
+
+    /** Returns the list of packages installed on the device of the user. */
+    @NonNull
+    private Set<String> getInstalledPackages() {
+        return PackageManagerCompatUtils.getInstalledApplications(mContext.getPackageManager(), 0)
+                .stream()
+                .map(applicationInfo -> applicationInfo.packageName)
+                .collect(Collectors.toSet());
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentService.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentService.java
deleted file mode 100644
index f247da1..0000000
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentService.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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.adservices.service.appsearch;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
-import android.os.Binder;
-import android.os.Build;
-import android.os.UserHandle;
-
-import androidx.annotation.RequiresApi;
-import androidx.appsearch.app.AppSearchSession;
-import androidx.appsearch.app.GlobalSearchSession;
-import androidx.appsearch.app.PackageIdentifier;
-import androidx.appsearch.platformstorage.PlatformStorage;
-
-import com.android.adservices.AdServicesCommon;
-import com.android.adservices.LogUtil;
-import com.android.adservices.concurrency.AdServicesExecutors;
-import com.android.adservices.service.consent.ConsentConstants;
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * This class provides an interface to read/write consent data to AppSearch. This is used as the
- * source of truth for S-. When a device upgrades from S- to T+, the consent is initialized from
- * AppSearch.
- */
-// TODO(b/269798827): Enable for R.
-@RequiresApi(Build.VERSION_CODES.S)
-public class AppSearchConsentService {
-    // Timeout for AppSearch write query in milliseconds.
-    private static final int TIMEOUT = 2000;
-    // This is used to convert the current package name belonging to AdExtServices to the
-    // corresponding package name for AdServices.
-    private static final String EXTSERVICES_PACKAGE_NAME_SUBSTRING = "ext.";
-    private static final String DATABASE_NAME = "adservices_consent";
-
-    // Required for allowing AdServices apk access to read consent written by ExtServices module.
-    private String mAdservicesPackageName;
-    private static final String ADSERVICES_SHA =
-            "686d5c450e00ebe600f979300a29234644eade42f24ede07a073f2bc6b94a3a2";
-    private Context mContext;
-
-    private ListenableFuture<AppSearchSession> mSearchSession;
-
-    // When reading across APKs, a GlobalSearchSession is needed, hence we use it when reading.
-    private ListenableFuture<GlobalSearchSession> mGlobalSearchSession;
-    private Executor mExecutor = AdServicesExecutors.getBackgroundExecutor();
-
-    private PackageIdentifier mPackageIdentifier;
-
-    private AppSearchConsentService(Context context) {
-        mContext = context;
-        mSearchSession =
-                PlatformStorage.createSearchSessionAsync(
-                        new PlatformStorage.SearchContext.Builder(mContext, DATABASE_NAME).build());
-        mGlobalSearchSession =
-                PlatformStorage.createGlobalSearchSessionAsync(
-                        new PlatformStorage.GlobalSearchContext.Builder(mContext).build());
-        mAdservicesPackageName = getAdServicesPackageName(mContext);
-        mPackageIdentifier =
-                new PackageIdentifier(
-                        mAdservicesPackageName, new Signature(ADSERVICES_SHA).toByteArray());
-    }
-
-    /** Get an instance of AppSearchConsentService. */
-    public static AppSearchConsentService getInstance(@NonNull Context context) {
-        return new AppSearchConsentService(context);
-    }
-
-    /**
-     * Get the consent for this user ID for this API type, as stored in AppSearch. Returns false if
-     * the database doesn't exist in AppSearch.
-     */
-    public boolean getConsent(@NonNull String apiType) {
-        return AppSearchConsentDao.readConsentData(
-                mGlobalSearchSession, mExecutor, getUserIdentifierFromBinderCallingUid(), apiType);
-    }
-
-    /**
-     * Sets the consent for this user ID for this API type in AppSearch. If we do not get
-     * confirmation that the write was successful, then we throw an exception so that user does not
-     * incorrectly think that the consent is updated.
-     */
-    public void setConsent(@NonNull String apiType, @NonNull Boolean consented) {
-        String uid = getUserIdentifierFromBinderCallingUid();
-        // The ID of the row needs to unique per row. For a given user, we store multiple rows, one
-        // per each apiType.
-        AppSearchConsentDao dao =
-                new AppSearchConsentDao(
-                        getRowId(uid, apiType),
-                        uid,
-                        AppSearchConsentDao.NAMESPACE,
-                        apiType,
-                        consented.toString());
-        try {
-            dao.writeConsentData(mSearchSession, mPackageIdentifier, mExecutor)
-                    .get(TIMEOUT, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException | TimeoutException | ExecutionException e) {
-            LogUtil.e("Failed to write consent to AppSearch ", e);
-            throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
-        }
-    }
-
-    /** Returns the row ID that should be unique for the consent namespace. */
-    @VisibleForTesting
-    String getRowId(@NonNull String uid, @NonNull String apiType) {
-        return uid + "_" + apiType;
-    }
-
-    /** Returns the User Identifier from the CallingUid. */
-    @VisibleForTesting
-    String getUserIdentifierFromBinderCallingUid() {
-        return "" + UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier();
-    }
-
-    /**
-     * This method returns the package name of the AdServices APK from AdServices apex (T+). On an
-     * S- device, it removes the "ext." substring from the package name.
-     */
-    @VisibleForTesting
-    static String getAdServicesPackageName(Context context) {
-        Intent serviceIntent = new Intent(AdServicesCommon.ACTION_TOPICS_SERVICE);
-        List<ResolveInfo> resolveInfos =
-                context.getPackageManager()
-                        .queryIntentServices(
-                                serviceIntent,
-                                PackageManager.GET_SERVICES
-                                        | PackageManager.MATCH_SYSTEM_ONLY
-                                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-        final ServiceInfo serviceInfo =
-                AdServicesCommon.resolveAdServicesService(resolveInfos, serviceIntent.getAction());
-        if (serviceInfo != null) {
-            // Return the AdServices package name based on the current package name.
-            String packageName = serviceInfo.packageName;
-            if (packageName == null || packageName.isEmpty()) {
-                throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
-            }
-            return packageName.replace(EXTSERVICES_PACKAGE_NAME_SUBSTRING, "");
-        }
-        throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
-    }
-}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java
new file mode 100644
index 0000000..2b381ba
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchConsentWorker.java
@@ -0,0 +1,369 @@
+/*
+ * 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.adservices.service.appsearch;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+
+import androidx.annotation.RequiresApi;
+import androidx.appsearch.app.AppSearchSession;
+import androidx.appsearch.app.GlobalSearchSession;
+import androidx.appsearch.app.PackageIdentifier;
+import androidx.appsearch.platformstorage.PlatformStorage;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.LogUtil;
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.service.common.feature.PrivacySandboxFeatureType;
+import com.android.adservices.service.consent.ConsentConstants;
+import com.android.adservices.service.consent.ConsentManager;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
+
+/**
+ * This class provides an interface to read/write consent data to AppSearch. This is used as the
+ * source of truth for S-. When a device upgrades from S- to T+, the consent is initialized from
+ * AppSearch.
+ */
+// TODO(b/269798827): Enable for R.
+@RequiresApi(Build.VERSION_CODES.S)
+public class AppSearchConsentWorker {
+    // At the worker level, we ensure that writes do not conflict with any other writes/reads.
+    private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock();
+
+    // Timeout for AppSearch write query in milliseconds.
+    private static final int TIMEOUT_MS = 2000;
+
+    // This is used to convert the current package name belonging to AdExtServices to the
+    // corresponding package name for AdServices.
+    private static final String EXTSERVICES_PACKAGE_NAME_SUBSTRING = "ext.";
+    private static final String CONSENT_DATABASE_NAME = "adservices_consent";
+    private static final String APP_CONSENT_DATABASE_NAME = "adservices_app_consent";
+
+    // Required for allowing AdServices apk access to read consent written by ExtServices module.
+    private String mAdservicesPackageName;
+    private static final String ADSERVICES_SHA =
+            "686d5c450e00ebe600f979300a29234644eade42f24ede07a073f2bc6b94a3a2";
+    private Context mContext;
+
+    private ListenableFuture<AppSearchSession> mConsentSearchSession;
+    private ListenableFuture<AppSearchSession> mAppConsentSearchSession;
+
+    // When reading across APKs, a GlobalSearchSession is needed, hence we use it when reading.
+    private ListenableFuture<GlobalSearchSession> mGlobalSearchSession;
+    private Executor mExecutor = AdServicesExecutors.getBackgroundExecutor();
+
+    private PackageIdentifier mPackageIdentifier;
+    // There is a single user ID for a given process, so this class would not be instantiated
+    // across two user IDs.
+    private String mUid = getUserIdentifierFromBinderCallingUid();
+
+    private AppSearchConsentWorker(@NonNull Context context) {
+        Objects.requireNonNull(context);
+
+        mContext = context;
+        // We write with multiple schemas, so we need to initialize sessions per db.
+        mConsentSearchSession =
+                PlatformStorage.createSearchSessionAsync(
+                        new PlatformStorage.SearchContext.Builder(mContext, CONSENT_DATABASE_NAME)
+                                .build());
+        mAppConsentSearchSession =
+                PlatformStorage.createSearchSessionAsync(
+                        new PlatformStorage.SearchContext.Builder(
+                                        mContext, APP_CONSENT_DATABASE_NAME)
+                                .build());
+
+        // We use global session for reads since we may perform read on T+ AdServices package to
+        // restore consent data post OTA.
+        mGlobalSearchSession =
+                PlatformStorage.createGlobalSearchSessionAsync(
+                        new PlatformStorage.GlobalSearchContext.Builder(mContext).build());
+
+        // The package identifier of the AdServices package on T+ should always have access to read
+        // data written by AdExtServices package on S-.
+        mAdservicesPackageName = getAdServicesPackageName(mContext);
+        mPackageIdentifier =
+                new PackageIdentifier(
+                        mAdservicesPackageName, new Signature(ADSERVICES_SHA).toByteArray());
+    }
+
+    /** Get an instance of AppSearchConsentService. */
+    public static AppSearchConsentWorker getInstance(@NonNull Context context) {
+        Objects.requireNonNull(context);
+        return new AppSearchConsentWorker(context);
+    }
+
+    /**
+     * Get the consent for this user ID for this API type, as stored in AppSearch. Returns false if
+     * the database doesn't exist in AppSearch.
+     */
+    public boolean getConsent(@NonNull String apiType) {
+        Objects.requireNonNull(apiType);
+        READ_WRITE_LOCK.readLock().lock();
+        boolean result =
+                AppSearchConsentDao.readConsentData(mGlobalSearchSession, mExecutor, mUid, apiType);
+        READ_WRITE_LOCK.readLock().unlock();
+        return result;
+    }
+
+    /**
+     * Sets the consent for this user ID for this API type in AppSearch. If we do not get
+     * confirmation that the write was successful, then we throw an exception so that user does not
+     * incorrectly think that the consent is updated.
+     */
+    public void setConsent(@NonNull String apiType, @NonNull Boolean consented) {
+        Objects.requireNonNull(apiType);
+        Objects.requireNonNull(consented);
+        READ_WRITE_LOCK.writeLock().lock();
+        // The ID of the row needs to unique per row. For a given user, we store multiple rows, one
+        // per each apiType.
+        AppSearchConsentDao dao =
+                new AppSearchConsentDao(
+                        AppSearchConsentDao.getRowId(mUid, apiType),
+                        mUid,
+                        AppSearchConsentDao.NAMESPACE,
+                        apiType,
+                        consented.toString());
+        try {
+            dao.writeConsentData(mConsentSearchSession, mPackageIdentifier, mExecutor)
+                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            LogUtil.e("Failed to write consent to AppSearch ", e);
+            throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+        } finally {
+            READ_WRITE_LOCK.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Get the apps with consent as stored in AppSearch. If no such list was stored, empty list is
+     * returned.
+     */
+    public List<String> getAppsWithConsent(@NonNull String consentType) {
+        Objects.requireNonNull(consentType);
+        READ_WRITE_LOCK.readLock().lock();
+        AppSearchAppConsentDao dao =
+                AppSearchAppConsentDao.readConsentData(
+                        mGlobalSearchSession, mExecutor, mUid, consentType);
+        List result = (dao == null || dao.getApps() == null) ? List.of() : dao.getApps();
+        READ_WRITE_LOCK.readLock().unlock();
+        return result;
+    }
+
+    /** Clear app consent data for this user for the given type of consent. */
+    public void clearAppsWithConsent(@NonNull String consentType) {
+        Objects.requireNonNull(consentType);
+        READ_WRITE_LOCK.writeLock().lock();
+        try {
+            AppSearchDao.deleteConsentData(
+                            AppSearchAppConsentDao.class,
+                            mAppConsentSearchSession,
+                            mExecutor,
+                            AppSearchAppConsentDao.getRowId(mUid, consentType),
+                            AppSearchAppConsentDao.NAMESPACE)
+                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            LogUtil.e("Failed to delete consent to AppSearch ", e);
+            throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+        } finally {
+            READ_WRITE_LOCK.writeLock().unlock();
+        }
+    }
+
+    /** Adds an app to the list of apps with this consentType for this user. */
+    public boolean addAppWithConsent(@NonNull String consentType, @NonNull String app) {
+        Objects.requireNonNull(consentType);
+        Objects.requireNonNull(app);
+        READ_WRITE_LOCK.writeLock().lock();
+
+        try {
+            // Since AppSearch doesn't support PATCH api, we need to do a {read, modify, write}. See
+            // b/274507022 for details.
+            AppSearchAppConsentDao dao =
+                    AppSearchAppConsentDao.readConsentData(
+                            mGlobalSearchSession, mExecutor, mUid, consentType);
+            // If there was no such row in the table, create one. Else, update existing one.
+            if (dao == null) {
+                dao =
+                        new AppSearchAppConsentDao(
+                                AppSearchAppConsentDao.getRowId(mUid, consentType),
+                                mUid,
+                                AppSearchAppConsentDao.NAMESPACE,
+                                consentType,
+                                List.of(app));
+            } else {
+                // If this app was already present in the consent list, no need to rewrite.
+                if (dao.getApps() != null && dao.getApps().contains(app)) {
+                    return true;
+                }
+                List<String> apps =
+                        dao.getApps() != null ? new ArrayList<>(dao.getApps()) : new ArrayList<>();
+                apps.add(app);
+                dao.setApps(apps);
+            }
+            dao.writeConsentData(mAppConsentSearchSession, mPackageIdentifier, mExecutor)
+                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            LogUtil.e("Failed to write consent to AppSearch ", e);
+            return false;
+        } finally {
+            READ_WRITE_LOCK.writeLock().unlock();
+        }
+        return true;
+    }
+
+    /**
+     * Removes an app from the list of apps with this consentType for this user. If we do not get
+     * confirmation that the write was successful, then we throw an exception so that user does not
+     * incorrectly think that the consent is updated.
+     */
+    public void removeAppWithConsent(@NonNull String consentType, @NonNull String app) {
+        Objects.requireNonNull(consentType);
+        Objects.requireNonNull(app);
+        READ_WRITE_LOCK.readLock().lock();
+
+        try {
+            // Since AppSearch doesn't support PATCH api, we need to do a {read, modify, write}. See
+            // b/274507022 for details.
+            AppSearchAppConsentDao dao =
+                    AppSearchAppConsentDao.readConsentData(
+                            mGlobalSearchSession, mExecutor, mUid, consentType);
+            // If there was no such row in the table, do nothing. Else, update existing one.
+            if (dao == null || dao.getApps() == null || !dao.getApps().contains(app)) {
+                return;
+            }
+            dao.setApps(
+                    dao.getApps().stream()
+                            .filter(filterApp -> !filterApp.equals(app))
+                            .collect(Collectors.toList()));
+            dao.writeConsentData(mAppConsentSearchSession, mPackageIdentifier, mExecutor)
+                    .get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            LogUtil.e("Failed to write consent to AppSearch ", e);
+            throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+        } finally {
+            READ_WRITE_LOCK.readLock().unlock();
+        }
+    }
+
+    /** Returns whether the beta UX notification was displayed to this user on this device. */
+    public boolean wasNotificationDisplayed() {
+        // TODO(b/263297331): Implement.
+        return false;
+    }
+
+    /** Returns whether the GA UX notification was displayed to this user on this device. */
+    public boolean wasGaUxNotificationDisplayed() {
+        // TODO(b/263297331): Implement.
+        return false;
+    }
+
+    /** Record having shown the beta UX notification to this user on this device. */
+    public void recordNotificationDisplayed() {
+        // TODO(b/263297331): Implement.
+    }
+
+    /** Record having shown the GA UX notification to this user on this device. */
+    public void recordGaUxNotificationDisplayed() {
+        // TODO(b/263297331): Implement.
+    }
+
+    /**
+     * Returns the PrivacySandboxFeature recorded for this user on this device. Possible values are
+     * UNKNOWN, FIRST_CONSENT and RECONSENT.
+     */
+    public PrivacySandboxFeatureType getPrivacySandboxFeature() {
+        // TODO(b/263297331): Implement.
+        return null;
+    }
+
+    /** Record the current privacy sandbox feature. */
+    public void setCurrentPrivacySandboxFeature(PrivacySandboxFeatureType currentFeatureType) {
+        // TODO(b/263297331): Implement.
+    }
+
+    /**
+     * Returns information whether user interacted with consent manually.
+     *
+     * @return true if the user interacted with the consent manually, otherwise false.
+     */
+    public @ConsentManager.UserManualInteraction int getUserManualInteractionWithConsent() {
+        // TODO(b/263297331): Implement.
+        return ConsentManager.NO_MANUAL_INTERACTIONS_RECORDED;
+    }
+
+    /** Saves information to the storage that user interacted with consent manually. */
+    public void recordUserManualInteractionWithConsent(
+            @ConsentManager.UserManualInteraction int interaction) {
+        // TODO(b/263297331): Implement.
+    }
+
+    /** Returns the User Identifier from the CallingUid. */
+    @VisibleForTesting
+    String getUserIdentifierFromBinderCallingUid() {
+        return "" + UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier();
+    }
+
+    /**
+     * This method returns the package name of the AdServices APK from AdServices apex (T+). On an
+     * S- device, it removes the "ext." substring from the package name.
+     */
+    @VisibleForTesting
+    static String getAdServicesPackageName(Context context) {
+        Intent serviceIntent = new Intent(AdServicesCommon.ACTION_TOPICS_SERVICE);
+        List<ResolveInfo> resolveInfos =
+                context.getPackageManager()
+                        .queryIntentServices(
+                                serviceIntent,
+                                PackageManager.GET_SERVICES
+                                        | PackageManager.MATCH_SYSTEM_ONLY
+                                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+        final ServiceInfo serviceInfo =
+                AdServicesCommon.resolveAdServicesService(resolveInfos, serviceIntent.getAction());
+        if (serviceInfo != null) {
+            // Return the AdServices package name based on the current package name.
+            String packageName = serviceInfo.packageName;
+            if (packageName == null || packageName.isEmpty()) {
+                throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+            }
+            return packageName.replace(EXTSERVICES_PACKAGE_NAME_SUBSTRING, "");
+        }
+        // If we don't know the AdServices package name, we can't do a write.
+        throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
index 90dd44d..e4f770a 100644
--- a/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
+++ b/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchDao.java
@@ -16,8 +16,10 @@
 
 package com.android.adservices.service.appsearch;
 
+import android.annotation.NonNull;
 import android.os.Build;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.appsearch.app.AppSearchBatchResult;
 import androidx.appsearch.app.AppSearchSession;
@@ -25,6 +27,7 @@
 import androidx.appsearch.app.GlobalSearchSession;
 import androidx.appsearch.app.PackageIdentifier;
 import androidx.appsearch.app.PutDocumentsRequest;
+import androidx.appsearch.app.RemoveByDocumentIdRequest;
 import androidx.appsearch.app.SearchResults;
 import androidx.appsearch.app.SearchSpec;
 import androidx.appsearch.app.SetSchemaRequest;
@@ -38,6 +41,7 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -49,9 +53,9 @@
  */
 // TODO(b/269798827): Enable for R.
 @RequiresApi(Build.VERSION_CODES.S)
-public class AppSearchDao {
+class AppSearchDao {
     // Timeout for AppSearch search query in milliseconds.
-    private static final int TIMEOUT = 500;
+    private static final int TIMEOUT_MS = 500;
 
     /**
      * Iterate over the search results returned for the search query by AppSearch.
@@ -92,31 +96,40 @@
      *
      * @return the instance of subclass type that was read from AppSearch.
      */
+    @Nullable
     protected static <T> T readConsentData(
-            Class<T> cls,
-            ListenableFuture<GlobalSearchSession> searchSession,
-            Executor executor,
-            String query) {
-        // Query cannot be empty.
-        if (query == null || query.isEmpty()) {
+            @NonNull Class<T> cls,
+            @NonNull ListenableFuture<GlobalSearchSession> searchSession,
+            @NonNull Executor executor,
+            @NonNull String namespace,
+            @NonNull String query) {
+        Objects.requireNonNull(cls);
+        Objects.requireNonNull(searchSession);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(namespace);
+
+        // Namespace and Query cannot be empty.
+        if (query == null || query.isEmpty() || namespace.isEmpty()) {
             return null;
         }
 
-        SearchSpec searchSpec = new SearchSpec.Builder().build();
-        ListenableFuture<SearchResults> searchFuture =
-                Futures.transform(
-                        searchSession, session -> session.search(query, searchSpec), executor);
-        FluentFuture<T> future =
-                FluentFuture.from(searchFuture)
-                        .transformAsync(
-                                results -> iterateSearchResults(cls, results, executor), executor)
-                        .transform(result -> ((T) result), executor);
         try {
-            return future.get(TIMEOUT, TimeUnit.MILLISECONDS);
+            SearchSpec searchSpec = new SearchSpec.Builder().addFilterNamespaces(namespace).build();
+            ListenableFuture<SearchResults> searchFuture =
+                    Futures.transform(
+                            searchSession, session -> session.search(query, searchSpec), executor);
+            FluentFuture<T> future =
+                    FluentFuture.from(searchFuture)
+                            .transformAsync(
+                                    results -> iterateSearchResults(cls, results, executor),
+                                    executor)
+                            .transform(result -> ((T) result), executor);
+            T result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return result;
         } catch (ExecutionException | InterruptedException | TimeoutException e) {
             LogUtil.e("getConsent() Appsearch lookup failed with: ", e);
-            return null;
         }
+        return null;
     }
 
     /**
@@ -127,10 +140,14 @@
      *
      * @return the result of the write.
      */
-    public FluentFuture<AppSearchBatchResult<String, Void>> writeConsentData(
-            ListenableFuture<AppSearchSession> appSearchSession,
-            PackageIdentifier packageIdentifier,
-            Executor executor) {
+    FluentFuture<AppSearchBatchResult<String, Void>> writeConsentData(
+            @NonNull ListenableFuture<AppSearchSession> appSearchSession,
+            @NonNull PackageIdentifier packageIdentifier,
+            @NonNull Executor executor) {
+        Objects.requireNonNull(appSearchSession);
+        Objects.requireNonNull(packageIdentifier);
+        Objects.requireNonNull(executor);
+
         try {
             SetSchemaRequest setSchemaRequest =
                     new SetSchemaRequest.Builder()
@@ -174,4 +191,61 @@
                 Futures.immediateFailedFuture(
                         new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE)));
     }
+
+    /**
+     * Delete a row from the database.
+     *
+     * @return the result of the delete.
+     */
+    protected static <T> FluentFuture<AppSearchBatchResult<String, Void>> deleteConsentData(
+            @NonNull Class<T> cls,
+            @NonNull ListenableFuture<AppSearchSession> appSearchSession,
+            @NonNull Executor executor,
+            @NonNull String rowId,
+            @NonNull String namespace) {
+        Objects.requireNonNull(cls);
+        Objects.requireNonNull(appSearchSession);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(rowId);
+        Objects.requireNonNull(namespace);
+
+        try {
+            SetSchemaRequest setSchemaRequest =
+                    new SetSchemaRequest.Builder().addDocumentClasses(cls).build();
+            RemoveByDocumentIdRequest deleteRequest =
+                    new RemoveByDocumentIdRequest.Builder(namespace).addIds(rowId).build();
+            FluentFuture<AppSearchBatchResult<String, Void>> deleteFuture =
+                    FluentFuture.from(appSearchSession)
+                            .transformAsync(
+                                    session -> session.setSchemaAsync(setSchemaRequest), executor)
+                            .transformAsync(
+                                    setSchemaResponse -> {
+                                        // If we get failures in schemaResponse then we cannot try
+                                        // to write.
+                                        if (!setSchemaResponse.getMigrationFailures().isEmpty()) {
+                                            LogUtil.e(
+                                                    "SetSchemaResponse migration failure: "
+                                                            + setSchemaResponse
+                                                                    .getMigrationFailures()
+                                                                    .get(0));
+                                            throw new RuntimeException(
+                                                    ConsentConstants
+                                                            .ERROR_MESSAGE_APPSEARCH_FAILURE);
+                                        }
+                                        // The database knows about this schemaType and write can
+                                        // occur.
+                                        return Futures.transformAsync(
+                                                appSearchSession,
+                                                session -> session.removeAsync(deleteRequest),
+                                                executor);
+                                    },
+                                    executor);
+            return deleteFuture;
+        } catch (AppSearchException e) {
+            LogUtil.e("Cannot instantiate AppSearch database: " + e.getMessage());
+        }
+        return FluentFuture.from(
+                Futures.immediateFailedFuture(
+                        new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE)));
+    }
 }
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java b/adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java
index 2a926d9..b729b76 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/ConsentConstants.java
@@ -21,25 +21,26 @@
 /** ConsentManager related Constants. */
 public class ConsentConstants {
 
-    static final String NOTIFICATION_DISPLAYED_ONCE = "NOTIFICATION-DISPLAYED-ONCE";
+    public static final String NOTIFICATION_DISPLAYED_ONCE = "NOTIFICATION-DISPLAYED-ONCE";
 
-    static final String GA_UX_NOTIFICATION_DISPLAYED_ONCE = "GA-UX-NOTIFICATION-DISPLAYED-ONCE";
+    public static final String GA_UX_NOTIFICATION_DISPLAYED_ONCE =
+            "GA-UX-NOTIFICATION-DISPLAYED-ONCE";
 
-    static final String DEFAULT_CONSENT = "DEFAULT_CONSENT";
+    public static final String DEFAULT_CONSENT = "DEFAULT_CONSENT";
 
-    static final String TOPICS_DEFAULT_CONSENT = "TOPICS_DEFAULT_CONSENT";
+    public static final String TOPICS_DEFAULT_CONSENT = "TOPICS_DEFAULT_CONSENT";
 
-    static final String FLEDGE_DEFAULT_CONSENT = "FLEDGE_DEFAULT_CONSENT";
+    public static final String FLEDGE_DEFAULT_CONSENT = "FLEDGE_DEFAULT_CONSENT";
 
-    static final String MEASUREMENT_DEFAULT_CONSENT = "MEASUREMENT_DEFAULT_CONSENT";
+    public static final String MEASUREMENT_DEFAULT_CONSENT = "MEASUREMENT_DEFAULT_CONSENT";
 
-    static final String DEFAULT_AD_ID_STATE = "DEFAULT_AD_ID_STATE";
+    public static final String DEFAULT_AD_ID_STATE = "DEFAULT_AD_ID_STATE";
 
     @VisibleForTesting
     static final String MANUAL_INTERACTION_WITH_CONSENT_RECORDED =
             "MANUAL_INTERACTION_WITH_CONSENT_RECORDED";
 
-    static final String CONSENT_KEY = "CONSENT";
+    public static final String CONSENT_KEY = "CONSENT";
 
     // Internal datastore version
     static final int STORAGE_VERSION = 1;
@@ -53,6 +54,10 @@
     // happens again.
     static final String SHARED_PREFS_CONSENT = "PPAPI_Consent";
 
+    // Shared preferences to mark whether consent data from AppSearch has migrated to AdServices.
+    static final String SHARED_PREFS_KEY_APPSEARCH_HAS_MIGRATED =
+            "CONSENT_HAS_MIGRATED_FROM_APPSEARCH";
+
     // Shared preferences to mark whether PPAPI consent has been migrated to system server
     static final String SHARED_PREFS_KEY_HAS_MIGRATED = "CONSENT_HAS_MIGRATED_TO_SYSTEM_SERVER";
 
diff --git a/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java b/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
index 3848c56..278f526 100644
--- a/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/consent/ConsentManager.java
@@ -42,7 +42,7 @@
 import com.android.adservices.data.topics.TopicsTables;
 import com.android.adservices.service.Flags;
 import com.android.adservices.service.FlagsFactory;
-import com.android.adservices.service.appsearch.AppSearchConsentService;
+import com.android.adservices.service.appsearch.AppSearchConsentManager;
 import com.android.adservices.service.common.BackgroundJobsManager;
 import com.android.adservices.service.common.feature.PrivacySandboxFeatureType;
 import com.android.adservices.service.measurement.MeasurementImpl;
@@ -50,6 +50,7 @@
 import com.android.adservices.service.topics.TopicsWorker;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.modules.utils.build.SdkLevel;
 
 import com.google.common.collect.ImmutableList;
 
@@ -101,7 +102,7 @@
     private final AppInstallDao mAppInstallDao;
     private final AdServicesManager mAdServicesManager;
     private final int mConsentSourceOfTruth;
-    private final AppSearchConsentService mAppSearchConsentService;
+    private final AppSearchConsentManager mAppSearchConsentManager;
 
     private static final Object LOCK = new Object();
 
@@ -116,7 +117,7 @@
             @NonNull AppInstallDao appInstallDao,
             @NonNull AdServicesManager adServicesManager,
             @NonNull BooleanFileDatastore booleanFileDatastore,
-            @NonNull AppSearchConsentService appSearchConsentService,
+            @NonNull AppSearchConsentManager appSearchConsentManager,
             @NonNull Flags flags,
             @Flags.ConsentSourceOfTruth int consentSourceOfTruth) {
         Objects.requireNonNull(context);
@@ -134,7 +135,7 @@
         }
 
         if (flags.getEnableAppsearchConsentData()) {
-            Objects.requireNonNull(appSearchConsentService);
+            Objects.requireNonNull(appSearchConsentManager);
         }
 
         mContext = context;
@@ -148,7 +149,7 @@
         mAdSelectionEntryDao = adSelectionEntryDao;
         mAppInstallDao = appInstallDao;
 
-        mAppSearchConsentService = appSearchConsentService;
+        mAppSearchConsentManager = appSearchConsentManager;
         mFlags = flags;
         mConsentSourceOfTruth = consentSourceOfTruth;
     }
@@ -169,15 +170,26 @@
                 int consentSourceOfTruth = FlagsFactory.getFlags().getConsentSourceOfTruth();
                 BooleanFileDatastore datastore = createAndInitializeDataStore(context);
                 AdServicesManager adServicesManager = AdServicesManager.getInstance(context);
+                AppConsentDao appConsentDao = AppConsentDao.getInstance(context);
                 handleConsentMigrationIfNeeded(
                         context, datastore, adServicesManager, consentSourceOfTruth);
 
+                AppSearchConsentManager appSearchConsentManager = null;
+                if (FlagsFactory.getFlags().getEnableAppsearchConsentData()) {
+                    appSearchConsentManager = AppSearchConsentManager.getInstance(context);
+                    handleConsentMigrationFromAppSearchIfNeeded(
+                            context,
+                            datastore,
+                            appConsentDao,
+                            appSearchConsentManager,
+                            adServicesManager);
+                }
                 if (sConsentManager == null) {
                     sConsentManager =
                             new ConsentManager(
                                     context,
                                     TopicsWorker.getInstance(context),
-                                    AppConsentDao.getInstance(context),
+                                    appConsentDao,
                                     EnrollmentDao.getInstance(context),
                                     MeasurementImpl.getInstance(context),
                                     CustomAudienceDatabase.getInstance(context).customAudienceDao(),
@@ -185,7 +197,7 @@
                                     SharedStorageDatabase.getInstance(context).appInstallDao(),
                                     adServicesManager,
                                     datastore,
-                                    AppSearchConsentService.getInstance(context),
+                                    appSearchConsentManager,
                                     // TODO(b/260601944): Remove Flag Instance.
                                     FlagsFactory.getFlags(),
                                     consentSourceOfTruth);
@@ -339,8 +351,11 @@
                         // This is the default for back compat. All consent data is written to and
                         // read from AppSearch on S- devices.
                     case Flags.APPSEARCH_ONLY:
-                        return AdServicesApiConsent.getConsent(
-                                mAppSearchConsentService.getConsent(ConsentConstants.CONSENT_KEY));
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return AdServicesApiConsent.getConsent(
+                                    mAppSearchConsentManager.getConsent(
+                                            ConsentConstants.CONSENT_KEY));
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return AdServicesApiConsent.REVOKED;
@@ -382,8 +397,11 @@
                                 mAdServicesManager.getConsent(apiType.toConsentApiType());
                         return AdServicesApiConsent.getConsent(consentParcel.isIsGiven());
                     case Flags.APPSEARCH_ONLY:
-                        return AdServicesApiConsent.getConsent(
-                                mAppSearchConsentService.getConsent(apiType.toPpApiDatastoreKey()));
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return AdServicesApiConsent.getConsent(
+                                    mAppSearchConsentManager.getConsent(
+                                            apiType.toPpApiDatastoreKey()));
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return AdServicesApiConsent.REVOKED;
@@ -482,6 +500,9 @@
                                         .map(App::create)
                                         .collect(Collectors.toList()));
                     case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.getKnownAppsWithConsent();
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return ImmutableList.of();
@@ -523,6 +544,9 @@
                                         .map(App::create)
                                         .collect(Collectors.toList()));
                     case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.getAppsWithRevokedConsent();
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return ImmutableList.of();
@@ -563,6 +587,10 @@
                                 true);
                         break;
                     case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.revokeConsentForApp(app);
+                            break;
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                 }
@@ -610,6 +638,10 @@
                                 false);
                         break;
                     case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.restoreConsentForApp(app);
+                            break;
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                 }
@@ -641,6 +673,10 @@
                         mAdServicesManager.clearAllAppConsentData();
                         break;
                     case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.clearAllAppConsentData();
+                            break;
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                 }
@@ -679,6 +715,10 @@
                         mAdServicesManager.clearKnownAppsWithConsent();
                         break;
                     case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.clearKnownAppsWithConsent();
+                            break;
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                 }
@@ -738,6 +778,9 @@
                     return mAdServicesManager.isConsentRevokedForApp(
                             packageName, mAppConsentDao.getUidForInstalledPackageName(packageName));
                 case Flags.APPSEARCH_ONLY:
+                    if (mFlags.getEnableAppsearchConsentData()) {
+                        return mAppSearchConsentManager.isFledgeConsentRevokedForApp(packageName);
+                    }
                 default:
                     LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                     return true;
@@ -801,6 +844,10 @@
                             mAppConsentDao.getUidForInstalledPackageName(packageName),
                             false);
                 case Flags.APPSEARCH_ONLY:
+                    if (mFlags.getEnableAppsearchConsentData()) {
+                        return mAppSearchConsentManager
+                                .isFledgeConsentRevokedForAppAfterSettingFledgeUse(packageName);
+                    }
                 default:
                     LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                     return true;
@@ -885,6 +932,12 @@
                         mAdServicesManager.clearConsentForUninstalledApp(packageName, packageUid);
                         break;
                     case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            // AppSearch is written only for S- where we don't have permission to
+                            // receive UID info when package is uninstalled, so clear for all.
+                            mAppSearchConsentManager.clearConsentForUninstalledApp(packageName);
+                            break;
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                 }
@@ -925,6 +978,11 @@
                                     packageName);
                         }
                         break;
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.clearConsentForUninstalledApp(packageName);
+                            break;
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                 }
@@ -968,7 +1026,10 @@
                         mAdServicesManager.recordNotificationDisplayed();
                         break;
                     case Flags.APPSEARCH_ONLY:
-                        break;
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.recordNotificationDisplayed();
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -998,6 +1059,9 @@
                     case Flags.PPAPI_AND_SYSTEM_SERVER:
                         return mAdServicesManager.wasNotificationDisplayed();
                     case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.wasNotificationDisplayed();
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return false;
@@ -1031,6 +1095,11 @@
                         mDatastore.put(ConsentConstants.GA_UX_NOTIFICATION_DISPLAYED_ONCE, true);
                         mAdServicesManager.recordGaUxNotificationDisplayed();
                         break;
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.recordGaUxNotificationDisplayed();
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1059,6 +1128,10 @@
                         // Intentional fallthrough
                     case Flags.PPAPI_AND_SYSTEM_SERVER:
                         return mAdServicesManager.wasGaUxNotificationDisplayed();
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.wasGaUxNotificationDisplayed();
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return false;
@@ -1088,6 +1161,11 @@
                         // Intentional fallthrough
                     case Flags.PPAPI_AND_SYSTEM_SERVER:
                         return mAdServicesManager.getDefaultConsent();
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.getConsent(
+                                    ConsentConstants.DEFAULT_CONSENT);
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return false;
@@ -1117,6 +1195,11 @@
                         // Intentional fallthrough
                     case Flags.PPAPI_AND_SYSTEM_SERVER:
                         return mAdServicesManager.getTopicsDefaultConsent();
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.getConsent(
+                                    ConsentConstants.TOPICS_DEFAULT_CONSENT);
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return false;
@@ -1146,6 +1229,11 @@
                         // Intentional fallthrough
                     case Flags.PPAPI_AND_SYSTEM_SERVER:
                         return mAdServicesManager.getFledgeDefaultConsent();
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.getConsent(
+                                    ConsentConstants.FLEDGE_DEFAULT_CONSENT);
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return false;
@@ -1175,6 +1263,9 @@
                         // Intentional fallthrough
                     case Flags.PPAPI_AND_SYSTEM_SERVER:
                         return mAdServicesManager.getMeasurementDefaultConsent();
+                    case Flags.APPSEARCH_ONLY:
+                        return mAppSearchConsentManager.getConsent(
+                                ConsentConstants.MEASUREMENT_DEFAULT_CONSENT);
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return false;
@@ -1204,6 +1295,11 @@
                         // Intentional fallthrough
                     case Flags.PPAPI_AND_SYSTEM_SERVER:
                         return mAdServicesManager.getDefaultAdIdState();
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.getConsent(
+                                    ConsentConstants.DEFAULT_AD_ID_STATE);
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return false;
@@ -1235,6 +1331,12 @@
                         mDatastore.put(ConsentConstants.DEFAULT_CONSENT, defaultConsent);
                         mAdServicesManager.recordDefaultConsent(defaultConsent);
                         break;
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.setConsent(
+                                    ConsentConstants.DEFAULT_CONSENT, defaultConsent);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1265,6 +1367,12 @@
                         mDatastore.put(ConsentConstants.TOPICS_DEFAULT_CONSENT, defaultConsent);
                         mAdServicesManager.recordTopicsDefaultConsent(defaultConsent);
                         break;
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.setConsent(
+                                    ConsentConstants.TOPICS_DEFAULT_CONSENT, defaultConsent);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1295,6 +1403,12 @@
                         mDatastore.put(ConsentConstants.FLEDGE_DEFAULT_CONSENT, defaultConsent);
                         mAdServicesManager.recordFledgeDefaultConsent(defaultConsent);
                         break;
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.setConsent(
+                                    ConsentConstants.FLEDGE_DEFAULT_CONSENT, defaultConsent);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1327,6 +1441,12 @@
                                 ConsentConstants.MEASUREMENT_DEFAULT_CONSENT, defaultConsent);
                         mAdServicesManager.recordMeasurementDefaultConsent(defaultConsent);
                         break;
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.setConsent(
+                                    ConsentConstants.MEASUREMENT_DEFAULT_CONSENT, defaultConsent);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1357,6 +1477,12 @@
                         mDatastore.put(ConsentConstants.DEFAULT_AD_ID_STATE, defaultAdIdState);
                         mAdServicesManager.recordDefaultAdIdState(defaultAdIdState);
                         break;
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.setConsent(
+                                    ConsentConstants.DEFAULT_AD_ID_STATE, defaultAdIdState);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1403,6 +1529,12 @@
                         mAdServicesManager.setCurrentPrivacySandboxFeature(
                                 currentFeatureType.name());
                         break;
+                    case Flags.APPSEARCH_ONLY:
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.setCurrentPrivacySandboxFeature(
+                                    currentFeatureType);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1429,7 +1561,11 @@
                         mAdServicesManager.recordUserManualInteractionWithConsent(interaction);
                         break;
                     case Flags.APPSEARCH_ONLY:
-                        break;
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.recordUserManualInteractionWithConsent(
+                                    interaction);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED);
@@ -1471,7 +1607,9 @@
                         }
                         break;
                     case Flags.APPSEARCH_ONLY:
-                        break;
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.getCurrentPrivacySandboxFeature();
+                        }
                     default:
                         LogUtil.e(ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
                         return PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED;
@@ -1526,7 +1664,9 @@
                     case Flags.PPAPI_AND_SYSTEM_SERVER:
                         return mAdServicesManager.getUserManualInteractionWithConsent();
                     case Flags.APPSEARCH_ONLY:
-                        return UNKNOWN;
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            return mAppSearchConsentManager.getUserManualInteractionWithConsent();
+                        }
                     default:
                         LogUtil.e(ConsentConstants.MANUAL_INTERACTION_WITH_CONSENT_RECORDED);
                         return UNKNOWN;
@@ -1603,6 +1743,8 @@
                 clearPpApiConsent(context, datastore);
                 break;
             case Flags.APPSEARCH_ONLY:
+                // If this is an S- device, the consent source of truth is always APPSEARCH_ONLY.
+                break;
             default:
                 break;
         }
@@ -1808,8 +1950,11 @@
                         setConsentToSystemServer(mAdServicesManager, isGiven);
                         break;
                     case Flags.APPSEARCH_ONLY:
-                        mAppSearchConsentService.setConsent(ConsentConstants.CONSENT_KEY, isGiven);
-                        break;
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.setConsent(
+                                    ConsentConstants.CONSENT_KEY, isGiven);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1841,8 +1986,11 @@
                         setAggregatedConsentToPpApi();
                         break;
                     case Flags.APPSEARCH_ONLY:
-                        mAppSearchConsentService.setConsent(apiType.toPpApiDatastoreKey(), isGiven);
-                        break;
+                        if (mFlags.getEnableAppsearchConsentData()) {
+                            mAppSearchConsentManager.setConsent(
+                                    apiType.toPpApiDatastoreKey(), isGiven);
+                            break;
+                        }
                     default:
                         throw new RuntimeException(
                                 ConsentConstants.ERROR_MESSAGE_INVALID_CONSENT_SOURCE_OF_TRUTH);
@@ -1854,6 +2002,28 @@
     }
 
     /**
+     * This method handles migration of consent data from AppSearch to AdServices. Consent data is
+     * written to AppSearch on S- and ported to AdServices after OTA to T. If any new data is
+     * written for consent, we need to make sure it is migrated correctly post-OTA in this method.
+     */
+    @VisibleForTesting
+    static void handleConsentMigrationFromAppSearchIfNeeded(
+            @NonNull Context context,
+            @NonNull BooleanFileDatastore datastore,
+            @NonNull AppConsentDao appConsentDao,
+            @NonNull AppSearchConsentManager appSearchConsentManager,
+            @NonNull AdServicesManager adServicesManager) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(datastore);
+        Objects.requireNonNull(appConsentDao);
+        Objects.requireNonNull(appSearchConsentManager);
+        if (SdkLevel.isAtLeastT()) {
+            Objects.requireNonNull(adServicesManager);
+        }
+        // TODO(b/263297331): Implement migration of AppSearch data to AdServices.
+    }
+
+    /**
      * Represents revoked consent as internally determined by the PP APIs.
      *
      * <p>This is an internal-only exception and is not meant to be returned to external callers.
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
index 5e74af9..9792bfa 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/PhFlagsTest.java
@@ -32,7 +32,6 @@
 import static com.android.adservices.service.Flags.COMPAT_LOGGING_KILL_SWITCH;
 import static com.android.adservices.service.Flags.DEFAULT_BLOCKED_TOPICS_SOURCE_OF_TRUTH;
 import static com.android.adservices.service.Flags.DEFAULT_CLASSIFIER_TYPE;
-import static com.android.adservices.service.Flags.DEFAULT_CONSENT_SOURCE_OF_TRUTH;
 import static com.android.adservices.service.Flags.DEFAULT_MEASUREMENT_DEBUG_JOIN_KEY_ENROLLMENT_ALLOWLIST;
 import static com.android.adservices.service.Flags.DEFAULT_MEASUREMENT_DEBUG_JOIN_KEY_HASH_LIMIT;
 import static com.android.adservices.service.Flags.DEFAULT_NOTIFICATION_DISMISSED_ON_CLICK;
@@ -167,6 +166,7 @@
 import static com.android.adservices.service.Flags.MEASUREMENT_REGISTRATION_JOB_TRIGGER_MAX_DELAY_MS;
 import static com.android.adservices.service.Flags.MEASUREMENT_ROLLBACK_DELETION_KILL_SWITCH;
 import static com.android.adservices.service.Flags.NUMBER_OF_EPOCHS_TO_KEEP_IN_HISTORY;
+import static com.android.adservices.service.Flags.PPAPI_AND_SYSTEM_SERVER;
 import static com.android.adservices.service.Flags.PPAPI_APP_ALLOW_LIST;
 import static com.android.adservices.service.Flags.PPAPI_APP_SIGNATURE_ALLOW_LIST;
 import static com.android.adservices.service.Flags.PPAPI_ONLY;
@@ -369,6 +369,7 @@
 
 import com.google.common.collect.ImmutableList;
 
+import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.MockitoSession;
@@ -4561,9 +4562,23 @@
     }
 
     @Test
+    public void testDefaultConsentSourceOfTruth_isAtLeastT() {
+        Assume.assumeTrue(SdkLevel.isAtLeastT());
+        // On T+, default is PPAPI_AND_SYSTEM_SERVER.
+        assertThat(Flags.DEFAULT_CONSENT_SOURCE_OF_TRUTH).isEqualTo(PPAPI_AND_SYSTEM_SERVER);
+    }
+
+    @Test
+    public void testDefaultConsentSourceOfTruth_isS() {
+        Assume.assumeFalse(SdkLevel.isAtLeastT());
+        // On T+, default is PPAPI_AND_SYSTEM_SERVER.
+        assertThat(Flags.DEFAULT_CONSENT_SOURCE_OF_TRUTH).isEqualTo(PPAPI_ONLY);
+    }
+
+    @Test
     public void testGetConsentSourceOfTruth() {
         assertThat(FlagsFactory.getFlags().getConsentSourceOfTruth())
-                .isEqualTo(DEFAULT_CONSENT_SOURCE_OF_TRUTH);
+                .isEqualTo(Flags.DEFAULT_CONSENT_SOURCE_OF_TRUTH);
 
         final int phOverridingValue = PPAPI_ONLY;
         DeviceConfig.setProperty(
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchAppConsentDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchAppConsentDaoTest.java
new file mode 100644
index 0000000..2d74cfd
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchAppConsentDaoTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+public class AppSearchAppConsentDaoTest {
+    private static final String ID = "1";
+    private static final String ID2 = "2";
+    private static final String NAMESPACE = "consent";
+    private static final List<String> APPS =
+            ImmutableList.of(ApplicationProvider.getApplicationContext().getPackageName());
+    private MockitoSession mStaticMockSession;
+
+    @Before
+    public void setup() {
+        mStaticMockSession =
+                ExtendedMockito.mockitoSession()
+                        .mockStatic(AppSearchDao.class)
+                        .strictness(Strictness.WARN)
+                        .initMocks(this)
+                        .startMocking();
+    }
+
+    @After
+    public void teardown() {
+        if (mStaticMockSession != null) {
+            mStaticMockSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testToString() {
+        AppSearchAppConsentDao dao =
+                new AppSearchAppConsentDao(
+                        ID, ID, NAMESPACE, AppSearchAppConsentDao.APPS_WITH_CONSENT, APPS);
+        assertThat(dao.toString())
+                .isEqualTo(
+                        "id="
+                                + ID
+                                + "; userId="
+                                + ID
+                                + "; consentType="
+                                + AppSearchAppConsentDao.APPS_WITH_CONSENT
+                                + "; namespace="
+                                + NAMESPACE
+                                + "; apps="
+                                + Arrays.toString(APPS.toArray()));
+    }
+
+    @Test
+    public void testEquals() {
+        AppSearchAppConsentDao dao1 =
+                new AppSearchAppConsentDao(
+                        ID, ID, NAMESPACE, AppSearchAppConsentDao.APPS_WITH_CONSENT, APPS);
+        AppSearchAppConsentDao dao2 =
+                new AppSearchAppConsentDao(
+                        ID, ID, NAMESPACE, AppSearchAppConsentDao.APPS_WITH_CONSENT, APPS);
+        AppSearchAppConsentDao dao3 =
+                new AppSearchAppConsentDao(
+                        ID, ID, NAMESPACE, AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT, APPS);
+        assertThat(dao1.equals(dao2)).isTrue();
+        assertThat(dao1.equals(dao3)).isFalse();
+        assertThat(dao2.equals(dao3)).isFalse();
+    }
+
+    @Test
+    public void testGetQuery() {
+        String expected =
+                "userId:" + ID + " " + "consentType:" + AppSearchAppConsentDao.APPS_WITH_CONSENT;
+        assertThat(AppSearchAppConsentDao.getQuery(ID, AppSearchAppConsentDao.APPS_WITH_CONSENT))
+                .isEqualTo(expected);
+    }
+
+    @Test
+    public void testGetRowId() {
+        String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+        String expected = ID + "_" + consentType;
+        assertThat(AppSearchAppConsentDao.getRowId(ID, consentType)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testReadConsentData_null() {
+        String consentType = AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT;
+        ListenableFuture mockSearchSession = Mockito.mock(ListenableFuture.class);
+        Executor mockExecutor = Mockito.mock(Executor.class);
+        ExtendedMockito.doReturn(null)
+                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), any(), any()));
+        AppSearchAppConsentDao result =
+                AppSearchAppConsentDao.readConsentData(
+                        mockSearchSession, mockExecutor, ID, consentType);
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void testReadConsentData() {
+        ListenableFuture mockSearchSession = Mockito.mock(ListenableFuture.class);
+        Executor mockExecutor = Mockito.mock(Executor.class);
+
+        String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+        String query = "userId:" + ID + " " + "consentType:" + consentType;
+        AppSearchAppConsentDao dao = Mockito.mock(AppSearchAppConsentDao.class);
+        ExtendedMockito.doReturn(dao)
+                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), any(), eq(query)));
+        AppSearchAppConsentDao result =
+                AppSearchAppConsentDao.readConsentData(
+                        mockSearchSession, mockExecutor, ID, consentType);
+        assertThat(result).isEqualTo(dao);
+
+        // Confirm that the right value is returned even when it is true.
+        String consentType2 = AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT;
+        String query2 = "userId:" + ID2 + " " + "consentType:" + consentType2;
+        ExtendedMockito.doReturn(dao)
+                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), any(), eq(query2)));
+        AppSearchAppConsentDao result2 =
+                AppSearchAppConsentDao.readConsentData(
+                        mockSearchSession, mockExecutor, ID2, consentType2);
+        assertThat(result2).isEqualTo(dao);
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentDaoTest.java
index 1543bf3..5dd0c6a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentDaoTest.java
@@ -97,11 +97,17 @@
     }
 
     @Test
+    public void testGetRowId() {
+        String expected = ID + "_" + API_TYPE;
+        assertThat(AppSearchConsentDao.getRowId(ID, API_TYPE)).isEqualTo(expected);
+    }
+
+    @Test
     public void testReadConsentData_null() {
         ListenableFuture mockSearchSession = Mockito.mock(ListenableFuture.class);
         Executor mockExecutor = Mockito.mock(Executor.class);
         ExtendedMockito.doReturn(null)
-                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), any()));
+                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), any(), any()));
         boolean result =
                 AppSearchConsentDao.readConsentData(mockSearchSession, mockExecutor, ID, API_TYPE);
         assertThat(result).isFalse();
@@ -116,7 +122,7 @@
         AppSearchConsentDao dao = Mockito.mock(AppSearchConsentDao.class);
         Mockito.when(dao.isConsented()).thenReturn(false);
         ExtendedMockito.doReturn(dao)
-                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), eq(query)));
+                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), any(), eq(query)));
         boolean result =
                 AppSearchConsentDao.readConsentData(mockSearchSession, mockExecutor, ID, API_TYPE);
         assertThat(result).isFalse();
@@ -125,7 +131,7 @@
         String query2 = "userId:" + ID2 + " " + "apiType:" + API_TYPE2;
         Mockito.when(dao.isConsented()).thenReturn(true);
         ExtendedMockito.doReturn(dao)
-                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), eq(query2)));
+                .when(() -> AppSearchDao.readConsentData(any(), any(), any(), any(), eq(query2)));
         boolean result2 =
                 AppSearchConsentDao.readConsentData(mockSearchSession, mockExecutor, ID, API_TYPE);
         assertThat(result2).isTrue();
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentManagerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentManagerTest.java
new file mode 100644
index 0000000..6fd5162
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentManagerTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.adservices.service.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.adservices.service.common.compat.PackageManagerCompatUtils;
+import com.android.adservices.service.common.feature.PrivacySandboxFeatureType;
+import com.android.adservices.service.consent.AdServicesApiType;
+import com.android.adservices.service.consent.App;
+import com.android.adservices.service.consent.ConsentManager;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.List;
+
+@SmallTest
+public class AppSearchConsentManagerTest {
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    private MockitoSession mStaticMockSession;
+    @Mock private AppSearchConsentWorker mAppSearchConsentWorker;
+    private AppSearchConsentManager mAppSearchConsentManager;
+    private static final String API_TYPE = AdServicesApiType.TOPICS.toPpApiDatastoreKey();
+    private static final String PACKAGE_NAME1 = "foo.bar.one";
+    private static final String PACKAGE_NAME2 = "foo.bar.two";
+    private static final String PACKAGE_NAME3 = "foo.bar.three";
+
+    @Before
+    public void setup() {
+        mStaticMockSession =
+                ExtendedMockito.mockitoSession()
+                        .mockStatic(AppSearchConsentWorker.class)
+                        .mockStatic(PackageManagerCompatUtils.class)
+                        .strictness(Strictness.WARN)
+                        .initMocks(this)
+                        .startMocking();
+        ExtendedMockito.doReturn(mAppSearchConsentWorker)
+                .when(() -> AppSearchConsentWorker.getInstance(mContext));
+        mAppSearchConsentManager = AppSearchConsentManager.getInstance(mContext);
+        ApplicationInfo app1 = new ApplicationInfo();
+        app1.packageName = PACKAGE_NAME1;
+        ApplicationInfo app2 = new ApplicationInfo();
+        app2.packageName = PACKAGE_NAME2;
+        ApplicationInfo app3 = new ApplicationInfo();
+        app3.packageName = PACKAGE_NAME3;
+        ExtendedMockito.doReturn(List.of(app1, app2, app3))
+                .when(() -> PackageManagerCompatUtils.getInstalledApplications(any(), anyInt()));
+    }
+
+    @After
+    public void teardown() {
+        if (mStaticMockSession != null) {
+            mStaticMockSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testGetConsent() {
+        when(mAppSearchConsentWorker.getConsent(API_TYPE)).thenReturn(false);
+        assertThat(mAppSearchConsentManager.getConsent(API_TYPE)).isEqualTo(false);
+
+        when(mAppSearchConsentWorker.getConsent(API_TYPE)).thenReturn(true);
+        assertThat(mAppSearchConsentManager.getConsent(API_TYPE)).isEqualTo(true);
+    }
+
+    @Test
+    public void testSetConsent() {
+        mAppSearchConsentManager.setConsent(API_TYPE, true);
+        verify(mAppSearchConsentWorker).setConsent(API_TYPE, true);
+
+        mAppSearchConsentManager.setConsent(API_TYPE, false);
+        verify(mAppSearchConsentWorker).setConsent(API_TYPE, false);
+    }
+
+    @Test
+    public void testKnownAppsWithConsent() {
+        String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+        when(mAppSearchConsentWorker.getAppsWithConsent(eq(consentType)))
+                .thenReturn(List.of(PACKAGE_NAME1, PACKAGE_NAME2));
+        List<App> result = mAppSearchConsentManager.getKnownAppsWithConsent();
+        assertThat(result.size()).isEqualTo(2);
+
+        String package1 = result.get(0).getPackageName();
+        String package2 = result.get(1).getPackageName();
+        assertThat(package1.equals(PACKAGE_NAME1) || package2.equals(PACKAGE_NAME1)).isTrue();
+        assertThat(package1.equals(PACKAGE_NAME2) || package2.equals(PACKAGE_NAME2)).isTrue();
+    }
+
+    @Test
+    public void testAppsWithRevokedConsent() {
+        String consentType = AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT;
+        when(mAppSearchConsentWorker.getAppsWithConsent(eq(consentType)))
+                .thenReturn(List.of(PACKAGE_NAME1, PACKAGE_NAME2));
+        List<App> result = mAppSearchConsentManager.getAppsWithRevokedConsent();
+        assertThat(result.size()).isEqualTo(2);
+
+        String package1 = result.get(0).getPackageName();
+        String package2 = result.get(1).getPackageName();
+        assertThat(package1.equals(PACKAGE_NAME1) || package2.equals(PACKAGE_NAME1)).isTrue();
+        assertThat(package1.equals(PACKAGE_NAME2) || package2.equals(PACKAGE_NAME2)).isTrue();
+    }
+
+    @Test
+    public void testRevokeConsentForApp() {
+        App app = App.create(PACKAGE_NAME1);
+        mAppSearchConsentManager.revokeConsentForApp(app);
+        verify(mAppSearchConsentWorker)
+                .addAppWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT, app.getPackageName());
+        verify(mAppSearchConsentWorker)
+                .removeAppWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_CONSENT, app.getPackageName());
+    }
+
+    @Test
+    public void testRestoreConsentForApp() {
+        App app = App.create(PACKAGE_NAME1);
+        mAppSearchConsentManager.restoreConsentForApp(app);
+        verify(mAppSearchConsentWorker)
+                .addAppWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT, app.getPackageName());
+        verify(mAppSearchConsentWorker)
+                .removeAppWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT, app.getPackageName());
+    }
+
+    @Test
+    public void testClearAllAppConsentData() {
+        mAppSearchConsentManager.clearAllAppConsentData();
+        verify(mAppSearchConsentWorker)
+                .clearAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT);
+        verify(mAppSearchConsentWorker)
+                .clearAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT);
+    }
+
+    @Test
+    public void testClearKnownAppsWithConsent() throws Exception {
+        mAppSearchConsentManager.clearKnownAppsWithConsent();
+        verify(mAppSearchConsentWorker)
+                .clearAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT);
+    }
+
+    @Test
+    public void testIsFledgeConsentRevokedForApp_consented() {
+        when(mAppSearchConsentWorker.getAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT))
+                .thenReturn(List.of(PACKAGE_NAME1));
+        when(mAppSearchConsentWorker.getAppsWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT))
+                .thenReturn(List.of());
+        assertThat(mAppSearchConsentManager.isFledgeConsentRevokedForApp(PACKAGE_NAME1)).isFalse();
+    }
+
+    @Test
+    public void testIsFledgeConsentRevokedForApp_revoked() {
+        when(mAppSearchConsentWorker.getAppsWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT))
+                .thenReturn(List.of(PACKAGE_NAME1));
+        when(mAppSearchConsentWorker.getAppsWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT))
+                .thenReturn(List.of(PACKAGE_NAME1));
+        assertThat(mAppSearchConsentManager.isFledgeConsentRevokedForApp(PACKAGE_NAME1)).isTrue();
+    }
+
+    @Test
+    public void testIsFledgeConsentRevokedForAppAfterSettingFledgeUse() {
+        when(mAppSearchConsentWorker.addAppWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_CONSENT, PACKAGE_NAME1))
+                .thenReturn(true);
+        assertThat(
+                        mAppSearchConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
+                                PACKAGE_NAME1))
+                .isFalse();
+    }
+
+    @Test
+    public void testIsFledgeConsentRevokedForAppAfterSettingFledgeUse_revoked() {
+        when(mAppSearchConsentWorker.addAppWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_CONSENT, PACKAGE_NAME2))
+                .thenReturn(false);
+        assertThat(
+                        mAppSearchConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
+                                PACKAGE_NAME2))
+                .isTrue();
+    }
+
+    @Test
+    public void testClearConsentForUninstalledApp() {
+        mAppSearchConsentManager.clearConsentForUninstalledApp(PACKAGE_NAME1);
+        verify(mAppSearchConsentWorker)
+                .removeAppWithConsent(
+                        AppSearchAppConsentDao.APPS_WITH_REVOKED_CONSENT, PACKAGE_NAME1);
+        verify(mAppSearchConsentWorker)
+                .removeAppWithConsent(AppSearchAppConsentDao.APPS_WITH_CONSENT, PACKAGE_NAME1);
+    }
+
+    @Test
+    public void testRecordNotificationDisplayed() {
+        mAppSearchConsentManager.recordNotificationDisplayed();
+        verify(mAppSearchConsentWorker).recordNotificationDisplayed();
+    }
+
+    @Test
+    public void testRecordGaUxNotificationDisplayed() {
+        mAppSearchConsentManager.recordGaUxNotificationDisplayed();
+        verify(mAppSearchConsentWorker).recordGaUxNotificationDisplayed();
+    }
+
+    @Test
+    public void testWasNotificationDisplayed() {
+        when(mAppSearchConsentWorker.wasNotificationDisplayed()).thenReturn(false);
+        assertThat(mAppSearchConsentManager.wasNotificationDisplayed()).isFalse();
+        verify(mAppSearchConsentWorker).wasNotificationDisplayed();
+    }
+
+    @Test
+    public void testWasGaUxNotificationDisplayed() {
+        when(mAppSearchConsentWorker.wasGaUxNotificationDisplayed()).thenReturn(false);
+        assertThat(mAppSearchConsentManager.wasGaUxNotificationDisplayed()).isFalse();
+        verify(mAppSearchConsentWorker).wasGaUxNotificationDisplayed();
+    }
+
+    @Test
+    public void testGetCurrentPrivacySandboxFeature() {
+        when(mAppSearchConsentWorker.getPrivacySandboxFeature())
+                .thenReturn(PrivacySandboxFeatureType.PRIVACY_SANDBOX_FIRST_CONSENT);
+        assertThat(mAppSearchConsentManager.getCurrentPrivacySandboxFeature())
+                .isEqualTo(PrivacySandboxFeatureType.PRIVACY_SANDBOX_FIRST_CONSENT);
+        verify(mAppSearchConsentWorker).getPrivacySandboxFeature();
+    }
+
+    @Test
+    public void testSetCurrentPrivacySandboxFeature() {
+        mAppSearchConsentManager.setCurrentPrivacySandboxFeature(
+                PrivacySandboxFeatureType.PRIVACY_SANDBOX_RECONSENT);
+        verify(mAppSearchConsentWorker)
+                .setCurrentPrivacySandboxFeature(
+                        PrivacySandboxFeatureType.PRIVACY_SANDBOX_RECONSENT);
+    }
+
+    @Test
+    public void testGetUserManualInteractionsWithConsent() {
+        when(mAppSearchConsentWorker.getUserManualInteractionWithConsent())
+                .thenReturn(ConsentManager.MANUAL_INTERACTIONS_RECORDED);
+        assertThat(mAppSearchConsentManager.getUserManualInteractionWithConsent())
+                .isEqualTo(ConsentManager.MANUAL_INTERACTIONS_RECORDED);
+        verify(mAppSearchConsentWorker).getUserManualInteractionWithConsent();
+    }
+
+    @Test
+    public void testRecordUserManualInteractionWithConsent() {
+        mAppSearchConsentManager.recordUserManualInteractionWithConsent(
+                ConsentManager.NO_MANUAL_INTERACTIONS_RECORDED);
+        verify(mAppSearchConsentWorker)
+                .recordUserManualInteractionWithConsent(
+                        ConsentManager.NO_MANUAL_INTERACTIONS_RECORDED);
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentServiceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentServiceTest.java
deleted file mode 100644
index e5bb00f..0000000
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentServiceTest.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * 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.adservices.service.appsearch;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atMost;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Binder;
-import android.os.UserHandle;
-
-import androidx.appsearch.app.AppSearchBatchResult;
-import androidx.appsearch.app.AppSearchResult;
-import androidx.appsearch.app.AppSearchSession;
-import androidx.appsearch.app.SetSchemaRequest;
-import androidx.appsearch.app.SetSchemaResponse;
-import androidx.appsearch.platformstorage.PlatformStorage;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-
-import com.android.adservices.AdServicesCommon;
-import com.android.adservices.service.consent.ConsentConstants;
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.util.List;
-
-@SmallTest
-public class AppSearchConsentServiceTest {
-    private Context mContext = ApplicationProvider.getApplicationContext();
-    private static final String ADSERVICES_PACKAGE_NAME = "com.android.adservices.api";
-    private static final String ADEXTSERVICES_PACKAGE_NAME = "com.android.ext.adservices.api";
-    private static final String API_TYPE = "CONSENT-TOPICS";
-    private static final Boolean CONSENTED = true;
-    private static final String TEST = "test";
-    private static final int UID = 55;
-
-    @Test
-    public void testGetConsent() {
-        MockitoSession staticMockSessionLocal = null;
-        try {
-            staticMockSessionLocal =
-                    ExtendedMockito.mockitoSession()
-                            .mockStatic(AppSearchConsentDao.class)
-                            .strictness(Strictness.LENIENT)
-                            .initMocks(this)
-                            .startMocking();
-            ExtendedMockito.doReturn(false)
-                    .when(
-                            () ->
-                                    AppSearchConsentDao.readConsentData(
-                                            /* globalSearchSession= */ any(ListenableFuture.class),
-                                            /* executor= */ any(),
-                                            /* userId= */ any(),
-                                            eq(API_TYPE)));
-            boolean result = AppSearchConsentService.getInstance(mContext).getConsent(API_TYPE);
-            assertThat(result).isFalse();
-
-            // Confirm that the right value is returned even when it is true.
-            ExtendedMockito.doReturn(true)
-                    .when(
-                            () ->
-                                    AppSearchConsentDao.readConsentData(
-                                            /* globalSearchSession= */ any(ListenableFuture.class),
-                                            /* executor= */ any(),
-                                            /* userId= */ any(),
-                                            eq(API_TYPE)));
-            boolean result2 = AppSearchConsentService.getInstance(mContext).getConsent(API_TYPE);
-            assertThat(result2).isTrue();
-        } finally {
-            if (staticMockSessionLocal != null) {
-                staticMockSessionLocal.finishMocking();
-            }
-        }
-    }
-
-    @Test
-    public void testSetContent_failure() {
-        MockitoSession staticMockSessionLocal = null;
-        try {
-            staticMockSessionLocal =
-                    ExtendedMockito.mockitoSession()
-                            .spyStatic(PlatformStorage.class)
-                            .strictness(Strictness.LENIENT)
-                            .initMocks(this)
-                            .startMocking();
-            AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
-            ExtendedMockito.doReturn(Futures.immediateFuture(mockSession))
-                    .when(
-                            () ->
-                                    PlatformStorage.createSearchSessionAsync(
-                                            any(PlatformStorage.SearchContext.class)));
-            verify(mockSession, atMost(1)).setSchemaAsync(any(SetSchemaRequest.class));
-
-            SetSchemaResponse mockResponse = Mockito.mock(SetSchemaResponse.class);
-            when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
-                    .thenReturn(Futures.immediateFuture(mockResponse));
-
-            AppSearchResult mockResult = Mockito.mock(AppSearchResult.class);
-            SetSchemaResponse.MigrationFailure failure =
-                    new SetSchemaResponse.MigrationFailure(
-                            /* namespace= */ TEST,
-                            /* id= */ TEST,
-                            /* schemaType= */ TEST,
-                            /* appSearchResult= */ mockResult);
-            when(mockResponse.getMigrationFailures()).thenReturn(List.of(failure));
-            RuntimeException e =
-                    assertThrows(
-                            RuntimeException.class,
-                            () ->
-                                    AppSearchConsentService.getInstance(mContext)
-                                            .setConsent(API_TYPE, CONSENTED));
-            assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
-        } finally {
-            if (staticMockSessionLocal != null) {
-                staticMockSessionLocal.finishMocking();
-            }
-        }
-    }
-
-    @Test
-    public void testSetContent() {
-        MockitoSession staticMockSessionLocal = null;
-        try {
-            staticMockSessionLocal =
-                    ExtendedMockito.mockitoSession()
-                            .spyStatic(PlatformStorage.class)
-                            .strictness(Strictness.LENIENT)
-                            .initMocks(this)
-                            .startMocking();
-            AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
-            ExtendedMockito.doReturn(Futures.immediateFuture(mockSession))
-                    .when(
-                            () ->
-                                    PlatformStorage.createSearchSessionAsync(
-                                            any(PlatformStorage.SearchContext.class)));
-            verify(mockSession, atMost(1)).setSchemaAsync(any(SetSchemaRequest.class));
-
-            SetSchemaResponse mockResponse = Mockito.mock(SetSchemaResponse.class);
-            when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
-                    .thenReturn(Futures.immediateFuture(mockResponse));
-            AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
-            when(mockSession.putAsync(any())).thenReturn(Futures.immediateFuture(result));
-
-            verify(mockResponse, atMost(1)).getMigrationFailures();
-            when(mockResponse.getMigrationFailures()).thenReturn(List.of());
-            // Verify that no exception is thrown.
-            AppSearchConsentService.getInstance(mContext).setConsent(API_TYPE, CONSENTED);
-        } finally {
-            if (staticMockSessionLocal != null) {
-                staticMockSessionLocal.finishMocking();
-            }
-        }
-    }
-
-    @Test
-    public void testGetUserIdentifierFromBinderCallingUid() {
-        MockitoSession staticMockSessionLocal = null;
-        try {
-            staticMockSessionLocal =
-                    ExtendedMockito.mockitoSession()
-                            .spyStatic(UserHandle.class)
-                            .strictness(Strictness.LENIENT)
-                            .initMocks(this)
-                            .startMocking();
-            UserHandle mockUserHandle = Mockito.mock(UserHandle.class);
-            Mockito.when(UserHandle.getUserHandleForUid(Binder.getCallingUid()))
-                    .thenReturn(mockUserHandle);
-            Mockito.when(mockUserHandle.getIdentifier()).thenReturn(UID);
-            String result =
-                    AppSearchConsentService.getInstance(mContext)
-                            .getUserIdentifierFromBinderCallingUid();
-            assertThat(result).isEqualTo("" + UID);
-        } finally {
-            if (staticMockSessionLocal != null) {
-                staticMockSessionLocal.finishMocking();
-            }
-        }
-    }
-
-    @Test
-    public void testGetAdServicesPackageName_null() {
-        MockitoSession staticMockSessionLocal = null;
-        try {
-            staticMockSessionLocal =
-                    ExtendedMockito.mockitoSession()
-                            .spyStatic(AdServicesCommon.class)
-                            .strictness(Strictness.LENIENT)
-                            .initMocks(this)
-                            .startMocking();
-            Context context = Mockito.mock(Context.class);
-            PackageManager mockPackageManager = Mockito.mock(PackageManager.class);
-            Mockito.when(context.getPackageManager()).thenReturn(mockPackageManager);
-            Mockito.when(AdServicesCommon.resolveAdServicesService(any(), any())).thenReturn(null);
-            RuntimeException e =
-                    assertThrows(
-                            RuntimeException.class,
-                            () -> AppSearchConsentService.getAdServicesPackageName(context));
-            assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
-        } finally {
-            if (staticMockSessionLocal != null) {
-                staticMockSessionLocal.finishMocking();
-            }
-        }
-    }
-
-    @Test
-    public void testGetAdServicesPackageName() {
-        Context context = Mockito.mock(Context.class);
-        PackageManager mockPackageManager = Mockito.mock(PackageManager.class);
-        // When the resolveInfo returns AdServices package name, that is returned.
-        Mockito.when(context.getPackageManager()).thenReturn(mockPackageManager);
-
-        ServiceInfo serviceInfo1 = new ServiceInfo();
-        serviceInfo1.packageName = ADSERVICES_PACKAGE_NAME;
-        ResolveInfo resolveInfo1 = new ResolveInfo();
-        resolveInfo1.serviceInfo = serviceInfo1;
-
-        ServiceInfo serviceInfo2 = new ServiceInfo();
-        serviceInfo2.packageName = ADEXTSERVICES_PACKAGE_NAME;
-        ResolveInfo resolveInfo2 = new ResolveInfo();
-        resolveInfo2.serviceInfo = serviceInfo2;
-        Mockito.when(mockPackageManager.queryIntentServices(any(), anyInt()))
-                .thenReturn(List.of(resolveInfo1, resolveInfo2));
-        assertThat(AppSearchConsentService.getAdServicesPackageName(context))
-                .isEqualTo(ADSERVICES_PACKAGE_NAME);
-
-        // When the resolveInfo returns AdExtServices package name, the AdServices package name
-        // is returned.
-        Mockito.when(mockPackageManager.queryIntentServices(any(), anyInt()))
-                .thenReturn(List.of(resolveInfo2));
-        assertThat(AppSearchConsentService.getAdServicesPackageName(context))
-                .isEqualTo(ADSERVICES_PACKAGE_NAME);
-    }
-}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentWorkerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentWorkerTest.java
new file mode 100644
index 0000000..058682f
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchConsentWorkerTest.java
@@ -0,0 +1,583 @@
+/*
+ * 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.adservices.service.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.UserHandle;
+
+import androidx.appsearch.app.AppSearchBatchResult;
+import androidx.appsearch.app.AppSearchResult;
+import androidx.appsearch.app.AppSearchSession;
+import androidx.appsearch.app.SetSchemaRequest;
+import androidx.appsearch.app.SetSchemaResponse;
+import androidx.appsearch.platformstorage.PlatformStorage;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.adservices.AdServicesCommon;
+import com.android.adservices.service.consent.AdServicesApiType;
+import com.android.adservices.service.consent.ConsentConstants;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+@SmallTest
+public class AppSearchConsentWorkerTest {
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    private static final String ADSERVICES_PACKAGE_NAME = "com.android.adservices.api";
+    private static final String ADEXTSERVICES_PACKAGE_NAME = "com.android.ext.adservices.api";
+    private static final String API_TYPE = AdServicesApiType.TOPICS.toPpApiDatastoreKey();
+    private static final Boolean CONSENTED = true;
+    private static final String TEST = "test";
+    private static final int UID = 55;
+
+    @Test
+    public void testGetConsent() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .mockStatic(AppSearchConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            ExtendedMockito.doReturn(false)
+                    .when(
+                            () ->
+                                    AppSearchConsentDao.readConsentData(
+                                            /* globalSearchSession= */ any(ListenableFuture.class),
+                                            /* executor= */ any(),
+                                            /* userId= */ any(),
+                                            eq(API_TYPE)));
+            boolean result = AppSearchConsentWorker.getInstance(mContext).getConsent(API_TYPE);
+            assertThat(result).isFalse();
+
+            // Confirm that the right value is returned even when it is true.
+            ExtendedMockito.doReturn(true)
+                    .when(
+                            () ->
+                                    AppSearchConsentDao.readConsentData(
+                                            /* globalSearchSession= */ any(ListenableFuture.class),
+                                            /* executor= */ any(),
+                                            /* userId= */ any(),
+                                            eq(API_TYPE)));
+            boolean result2 = AppSearchConsentWorker.getInstance(mContext).getConsent(API_TYPE);
+            assertThat(result2).isTrue();
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testSetConsent_failure() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(PlatformStorage.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
+            ExtendedMockito.doReturn(Futures.immediateFuture(mockSession))
+                    .when(
+                            () ->
+                                    PlatformStorage.createSearchSessionAsync(
+                                            any(PlatformStorage.SearchContext.class)));
+            verify(mockSession, atMost(1)).setSchemaAsync(any(SetSchemaRequest.class));
+
+            SetSchemaResponse mockResponse = Mockito.mock(SetSchemaResponse.class);
+            when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
+                    .thenReturn(Futures.immediateFuture(mockResponse));
+
+            AppSearchResult mockResult = Mockito.mock(AppSearchResult.class);
+            SetSchemaResponse.MigrationFailure failure =
+                    new SetSchemaResponse.MigrationFailure(
+                            /* namespace= */ TEST,
+                            /* id= */ TEST,
+                            /* schemaType= */ TEST,
+                            /* appSearchResult= */ mockResult);
+            when(mockResponse.getMigrationFailures()).thenReturn(List.of(failure));
+            RuntimeException e =
+                    assertThrows(
+                            RuntimeException.class,
+                            () ->
+                                    AppSearchConsentWorker.getInstance(mContext)
+                                            .setConsent(API_TYPE, CONSENTED));
+            assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testSetConsent() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(PlatformStorage.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
+            ExtendedMockito.doReturn(Futures.immediateFuture(mockSession))
+                    .when(
+                            () ->
+                                    PlatformStorage.createSearchSessionAsync(
+                                            any(PlatformStorage.SearchContext.class)));
+            verify(mockSession, atMost(1)).setSchemaAsync(any(SetSchemaRequest.class));
+
+            SetSchemaResponse mockResponse = Mockito.mock(SetSchemaResponse.class);
+            when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
+                    .thenReturn(Futures.immediateFuture(mockResponse));
+            AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+            when(mockSession.putAsync(any())).thenReturn(Futures.immediateFuture(result));
+
+            verify(mockResponse, atMost(1)).getMigrationFailures();
+            when(mockResponse.getMigrationFailures()).thenReturn(List.of());
+            // Verify that no exception is thrown.
+            AppSearchConsentWorker.getInstance(mContext).setConsent(API_TYPE, CONSENTED);
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testGetUserIdentifierFromBinderCallingUid() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(UserHandle.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            UserHandle mockUserHandle = Mockito.mock(UserHandle.class);
+            Mockito.when(UserHandle.getUserHandleForUid(Binder.getCallingUid()))
+                    .thenReturn(mockUserHandle);
+            Mockito.when(mockUserHandle.getIdentifier()).thenReturn(UID);
+            String result =
+                    AppSearchConsentWorker.getInstance(mContext)
+                            .getUserIdentifierFromBinderCallingUid();
+            assertThat(result).isEqualTo("" + UID);
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testGetAdServicesPackageName_null() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AdServicesCommon.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            Context context = Mockito.mock(Context.class);
+            PackageManager mockPackageManager = Mockito.mock(PackageManager.class);
+            Mockito.when(context.getPackageManager()).thenReturn(mockPackageManager);
+            Mockito.when(AdServicesCommon.resolveAdServicesService(any(), any())).thenReturn(null);
+            RuntimeException e =
+                    assertThrows(
+                            RuntimeException.class,
+                            () -> AppSearchConsentWorker.getAdServicesPackageName(context));
+            assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testGetAdServicesPackageName() {
+        Context context = Mockito.mock(Context.class);
+        PackageManager mockPackageManager = Mockito.mock(PackageManager.class);
+        // When the resolveInfo returns AdServices package name, that is returned.
+        Mockito.when(context.getPackageManager()).thenReturn(mockPackageManager);
+
+        ServiceInfo serviceInfo1 = new ServiceInfo();
+        serviceInfo1.packageName = ADSERVICES_PACKAGE_NAME;
+        ResolveInfo resolveInfo1 = new ResolveInfo();
+        resolveInfo1.serviceInfo = serviceInfo1;
+
+        ServiceInfo serviceInfo2 = new ServiceInfo();
+        serviceInfo2.packageName = ADEXTSERVICES_PACKAGE_NAME;
+        ResolveInfo resolveInfo2 = new ResolveInfo();
+        resolveInfo2.serviceInfo = serviceInfo2;
+        Mockito.when(mockPackageManager.queryIntentServices(any(), anyInt()))
+                .thenReturn(List.of(resolveInfo1, resolveInfo2));
+        assertThat(AppSearchConsentWorker.getAdServicesPackageName(context))
+                .isEqualTo(ADSERVICES_PACKAGE_NAME);
+
+        // When the resolveInfo returns AdExtServices package name, the AdServices package name
+        // is returned.
+        Mockito.when(mockPackageManager.queryIntentServices(any(), anyInt()))
+                .thenReturn(List.of(resolveInfo2));
+        assertThat(AppSearchConsentWorker.getAdServicesPackageName(context))
+                .isEqualTo(ADSERVICES_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testGetAppsWithConsent_nullOrEmpty() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchAppConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            // Null dao is returned.
+            ExtendedMockito.doReturn(null)
+                    .when(() -> AppSearchAppConsentDao.readConsentData(any(), any(), any(), any()));
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            assertThat(appSearchConsentWorker.getAppsWithConsent(TEST)).isNotNull();
+            assertThat(appSearchConsentWorker.getAppsWithConsent(TEST)).isEmpty();
+
+            // Dao is returned, but list is null.
+            AppSearchAppConsentDao mockDao = Mockito.mock(AppSearchAppConsentDao.class);
+            ExtendedMockito.doReturn(mockDao)
+                    .when(() -> AppSearchAppConsentDao.readConsentData(any(), any(), any(), any()));
+            assertThat(appSearchConsentWorker.getAppsWithConsent(TEST)).isNotNull();
+            assertThat(appSearchConsentWorker.getAppsWithConsent(TEST)).isEmpty();
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testGetAppsWithConsent() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchAppConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            // Null dao is returned.
+            ExtendedMockito.doReturn(null)
+                    .when(() -> AppSearchAppConsentDao.readConsentData(any(), any(), any(), any()));
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            assertThat(appSearchConsentWorker.getAppsWithConsent(TEST)).isNotNull();
+            assertThat(appSearchConsentWorker.getAppsWithConsent(TEST)).isEmpty();
+
+            // Dao is returned, but list is null.
+            AppSearchAppConsentDao mockDao = Mockito.mock(AppSearchAppConsentDao.class);
+            List<String> apps = ImmutableList.of(TEST);
+            when(mockDao.getApps()).thenReturn(apps);
+            ExtendedMockito.doReturn(mockDao)
+                    .when(() -> AppSearchAppConsentDao.readConsentData(any(), any(), any(), any()));
+            assertThat(appSearchConsentWorker.getAppsWithConsent(TEST)).isNotNull();
+            assertThat(appSearchConsentWorker.getAppsWithConsent(TEST)).isEqualTo(apps);
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testClearAppsWithConsent_failure() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            FluentFuture future =
+                    FluentFuture.from(
+                            Futures.immediateFailedFuture(new ExecutionException("test", null)));
+            ExtendedMockito.doReturn(future)
+                    .when(() -> AppSearchDao.deleteConsentData(any(), any(), any(), any(), any()));
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            RuntimeException e =
+                    assertThrows(
+                            RuntimeException.class,
+                            () -> appSearchConsentWorker.clearAppsWithConsent(TEST));
+            assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testClearAppsWithConsent() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+            FluentFuture future = FluentFuture.from(Futures.immediateFuture(result));
+            ExtendedMockito.doReturn(future)
+                    .when(() -> AppSearchDao.deleteConsentData(any(), any(), any(), any(), any()));
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            // No exceptions are thrown.
+            appSearchConsentWorker.clearAppsWithConsent(TEST);
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testAddAppWithConsent_null() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchAppConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            ExtendedMockito.doReturn(null)
+                    .when(() -> AppSearchAppConsentDao.readConsentData(any(), any(), any(), any()));
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            // No exceptions are thrown.
+            assertThat(appSearchConsentWorker.addAppWithConsent(consentType, TEST)).isTrue();
+            ExtendedMockito.verify(
+                    () -> AppSearchAppConsentDao.getRowId(any(), eq(consentType)), atLeastOnce());
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testAddAppWithConsent_failure() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchAppConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            AppSearchAppConsentDao dao = Mockito.mock(AppSearchAppConsentDao.class);
+            ExtendedMockito.doReturn(dao)
+                    .when(
+                            () ->
+                                    AppSearchAppConsentDao.readConsentData(
+                                            any(), any(), any(), eq(consentType)));
+            when(dao.getApps()).thenReturn(List.of());
+            FluentFuture future =
+                    FluentFuture.from(
+                            Futures.immediateFailedFuture(new ExecutionException("test", null)));
+            when(dao.writeConsentData(any(), any(), any())).thenReturn(future);
+
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            assertThat(appSearchConsentWorker.addAppWithConsent(consentType, TEST)).isFalse();
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testAddAppWithConsent() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchAppConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            AppSearchAppConsentDao dao = Mockito.mock(AppSearchAppConsentDao.class);
+            ExtendedMockito.doReturn(dao)
+                    .when(
+                            () ->
+                                    AppSearchAppConsentDao.readConsentData(
+                                            any(), any(), any(), eq(consentType)));
+            when(dao.getApps()).thenReturn(List.of());
+            AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+            FluentFuture future = FluentFuture.from(Futures.immediateFuture(result));
+            when(dao.writeConsentData(any(), any(), any())).thenReturn(future);
+
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            // No exceptions are thrown.
+            assertThat(appSearchConsentWorker.addAppWithConsent(consentType, TEST)).isTrue();
+            verify(dao, atLeastOnce()).getApps();
+            verify(dao, atLeastOnce()).writeConsentData(any(), any(), any());
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveAppWithConsent_null() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchAppConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            ExtendedMockito.doReturn(null)
+                    .when(() -> AppSearchAppConsentDao.readConsentData(any(), any(), any(), any()));
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            // No exceptions are thrown.
+            appSearchConsentWorker.removeAppWithConsent(consentType, TEST);
+            ExtendedMockito.verify(
+                    () -> AppSearchAppConsentDao.getRowId(any(), eq(consentType)), never());
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveAppWithConsent_failure() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchAppConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            AppSearchAppConsentDao dao = Mockito.mock(AppSearchAppConsentDao.class);
+            ExtendedMockito.doReturn(dao)
+                    .when(
+                            () ->
+                                    AppSearchAppConsentDao.readConsentData(
+                                            any(), any(), any(), eq(consentType)));
+            when(dao.getApps()).thenReturn(List.of(TEST));
+            FluentFuture future =
+                    FluentFuture.from(
+                            Futures.immediateFailedFuture(new ExecutionException("test", null)));
+            when(dao.writeConsentData(any(), any(), any())).thenReturn(future);
+
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            RuntimeException e =
+                    assertThrows(
+                            RuntimeException.class,
+                            () -> appSearchConsentWorker.removeAppWithConsent(consentType, TEST));
+            assertThat(e.getMessage()).isEqualTo(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveAppWithConsent() {
+        MockitoSession staticMockSessionLocal = null;
+        try {
+            String consentType = AppSearchAppConsentDao.APPS_WITH_CONSENT;
+            staticMockSessionLocal =
+                    ExtendedMockito.mockitoSession()
+                            .spyStatic(AppSearchAppConsentDao.class)
+                            .strictness(Strictness.WARN)
+                            .initMocks(this)
+                            .startMocking();
+            AppSearchAppConsentDao dao = Mockito.mock(AppSearchAppConsentDao.class);
+            ExtendedMockito.doReturn(dao)
+                    .when(
+                            () ->
+                                    AppSearchAppConsentDao.readConsentData(
+                                            any(), any(), any(), eq(consentType)));
+
+            when(dao.getApps()).thenReturn(List.of(TEST));
+            AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+            FluentFuture future = FluentFuture.from(Futures.immediateFuture(result));
+            when(dao.writeConsentData(any(), any(), any())).thenReturn(future);
+
+            AppSearchConsentWorker appSearchConsentWorker =
+                    AppSearchConsentWorker.getInstance(mContext);
+            // No exceptions are thrown.
+            appSearchConsentWorker.removeAppWithConsent(consentType, TEST);
+            verify(dao, atLeastOnce()).getApps();
+            verify(dao, atLeastOnce()).writeConsentData(any(), any(), any());
+        } finally {
+            if (staticMockSessionLocal != null) {
+                staticMockSessionLocal.finishMocking();
+            }
+        }
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java
index 65c130c..4c791be 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/appsearch/AppSearchDaoTest.java
@@ -112,21 +112,23 @@
 
     @Test
     public void testReadConsentData_emptyQuery() {
-        assertThat(
-                        AppSearchDao.readConsentData(
-                                AppSearchConsentDao.class,
-                                Futures.immediateFuture(mGlobalSearchSession),
-                                mExecutor,
-                                null))
-                .isEqualTo(null);
+        AppSearchDao dao =
+                AppSearchDao.readConsentData(
+                        AppSearchConsentDao.class,
+                        Futures.immediateFuture(mGlobalSearchSession),
+                        mExecutor,
+                        NAMESPACE,
+                        null);
+        assertThat(dao).isEqualTo(null);
 
-        assertThat(
-                        AppSearchDao.readConsentData(
-                                AppSearchConsentDao.class,
-                                Futures.immediateFuture(mGlobalSearchSession),
-                                mExecutor,
-                                ""))
-                .isEqualTo(null);
+        AppSearchDao dao2 =
+                AppSearchDao.readConsentData(
+                        AppSearchConsentDao.class,
+                        Futures.immediateFuture(mGlobalSearchSession),
+                        mExecutor,
+                        NAMESPACE,
+                        "");
+        assertThat(dao2).isEqualTo(null);
     }
 
     @Test
@@ -144,17 +146,18 @@
                 new SearchResult.Builder(TEST, TEST).setGenericDocument(document).build();
         when(mMockPage.get(0)).thenReturn(searchResult);
         when(mGlobalSearchSession.search(any(), any())).thenReturn(mSearchResults);
-        assertThat(
-                        AppSearchDao.readConsentData(
-                                AppSearchConsentDao.class,
-                                Futures.immediateFuture(mGlobalSearchSession),
-                                mExecutor,
-                                TEST))
-                .isEqualTo(dao);
+        AppSearchDao result =
+                AppSearchDao.readConsentData(
+                        AppSearchConsentDao.class,
+                        Futures.immediateFuture(mGlobalSearchSession),
+                        mExecutor,
+                        NAMESPACE,
+                        TEST);
+        assertThat(result).isEqualTo(dao);
     }
 
     @Test
-    public void testWriteConsentData_failure() throws Exception {
+    public void testWriteConsentData_failure() {
         AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
         verify(mockSession, atMost(1)).setSchemaAsync(any(SetSchemaRequest.class));
 
@@ -206,4 +209,64 @@
                         Futures.immediateFuture(mockSession), PACKAGE_IDENTIFIER, mExecutor);
         assertThat(future.get()).isNotNull();
     }
+
+    @Test
+    public void testDeleteConsentData_failure() {
+        AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
+        verify(mockSession, atMost(1)).setSchemaAsync(any(SetSchemaRequest.class));
+
+        SetSchemaResponse mockResponse = Mockito.mock(SetSchemaResponse.class);
+        when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
+                .thenReturn(Futures.immediateFuture(mockResponse));
+
+        AppSearchResult mockResult = Mockito.mock(AppSearchResult.class);
+        SetSchemaResponse.MigrationFailure failure =
+                new SetSchemaResponse.MigrationFailure(
+                        /* namespace= */ TEST,
+                        /* id= */ TEST,
+                        /* schemaType= */ TEST,
+                        /* appSearchResult= */ mockResult);
+        when(mockResponse.getMigrationFailures()).thenReturn(List.of(failure));
+        // We can't use the base class instance since writing will fail without the necessary
+        // Document fields defined on the class, so we use a subclass instance.
+        FluentFuture<AppSearchBatchResult<String, Void>> result =
+                AppSearchDao.deleteConsentData(
+                        AppSearchConsentDao.class,
+                        Futures.immediateFuture(mockSession),
+                        mExecutor,
+                        NAMESPACE,
+                        TEST);
+        ExecutionException e = assertThrows(ExecutionException.class, () -> result.get());
+        assertThat(e.getMessage())
+                .isEqualTo(
+                        "java.lang.RuntimeException: "
+                                + ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
+    }
+
+    @Test
+    public void testDeleteConsentData() throws Exception {
+        AppSearchSession mockSession = Mockito.mock(AppSearchSession.class);
+        verify(mockSession, atMost(1)).setSchemaAsync(any(SetSchemaRequest.class));
+
+        SetSchemaResponse mockResponse = Mockito.mock(SetSchemaResponse.class);
+        when(mockSession.setSchemaAsync(any(SetSchemaRequest.class)))
+                .thenReturn(Futures.immediateFuture(mockResponse));
+
+        verify(mockResponse, atMost(1)).getMigrationFailures();
+        when(mockResponse.getMigrationFailures()).thenReturn(List.of());
+        // We can't use the base class instance since writing will fail without the necessary
+        // Document fields defined on the class, so we use a subclass instance.
+        AppSearchBatchResult<String, Void> result = Mockito.mock(AppSearchBatchResult.class);
+        when(mockSession.removeAsync(any())).thenReturn(Futures.immediateFuture(result));
+
+        // Verify that no exception is thrown.
+        FluentFuture future =
+                AppSearchDao.deleteConsentData(
+                        AppSearchConsentDao.class,
+                        Futures.immediateFuture(mockSession),
+                        mExecutor,
+                        NAMESPACE,
+                        TEST);
+        assertThat(future.get()).isNotNull();
+    }
 }
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java
index aae0fd8..5eb620e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/consent/ConsentManagerTest.java
@@ -84,7 +84,7 @@
 import com.android.adservices.service.Flags;
 import com.android.adservices.service.FlagsFactory;
 import com.android.adservices.service.MaintenanceJobService;
-import com.android.adservices.service.appsearch.AppSearchConsentService;
+import com.android.adservices.service.appsearch.AppSearchConsentManager;
 import com.android.adservices.service.common.BackgroundJobsManager;
 import com.android.adservices.service.common.compat.PackageManagerCompatUtils;
 import com.android.adservices.service.common.feature.PrivacySandboxFeatureType;
@@ -152,7 +152,7 @@
     @Mock private Flags mMockFlags;
     @Mock private JobScheduler mJobSchedulerMock;
     @Mock private IAdServicesManager mMockIAdServicesManager;
-    @Mock private AppSearchConsentService mAppSearchConsentService;
+    @Mock private AppSearchConsentManager mAppSearchConsentManager;
     private MockitoSession mStaticMockSession = null;
 
     @Before
@@ -316,6 +316,7 @@
     public void testConsentIsGivenAfterEnabling_AppSearchOnly() throws Exception {
         boolean isGiven = true;
         int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
         ConsentManager spyConsentManager =
                 getSpiedConsentManagerForMigrationTesting(isGiven, consentSourceOfTruth);
 
@@ -329,7 +330,7 @@
                 /* hasWrittenToPpApi */ false,
                 /* hasWrittenToSystemServer */ false,
                 /* hasReadFromSystemServer */ false);
-        verify(mAppSearchConsentService, atLeastOnce()).getConsent(CONSENT_KEY);
+        verify(mAppSearchConsentManager, atLeastOnce()).getConsent(CONSENT_KEY);
         verifyDataCleanup(spyConsentManager);
     }
 
@@ -410,6 +411,7 @@
             throws RemoteException, IOException {
         boolean isGiven = false;
         int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
         ConsentManager spyConsentManager =
                 getSpiedConsentManagerForMigrationTesting(isGiven, consentSourceOfTruth);
 
@@ -423,7 +425,7 @@
                 /* hasWrittenToPpApi */ false,
                 /* hasWrittenToSystemServer */ false,
                 /* hasReadFromSystemServer */ false);
-        verify(mAppSearchConsentService, atLeastOnce()).getConsent(CONSENT_KEY);
+        verify(mAppSearchConsentManager, atLeastOnce()).getConsent(CONSENT_KEY);
         verifyDataCleanup(spyConsentManager);
     }
 
@@ -849,6 +851,46 @@
     }
 
     @Test
+    public void testIsFledgeConsentRevokedForAppWithFullApiConsentGaUxEnabled_appSearchOnly()
+            throws Exception {
+        runTestIsFledgeConsentRevokedForAppWithFullApiConsentAppSearchOnly(true);
+    }
+
+    @Test
+    public void testIsFledgeConsentRevokedForAppWithFullApiConsentGaUxDisabled_appSearchOnly()
+            throws Exception {
+        runTestIsFledgeConsentRevokedForAppWithFullApiConsentAppSearchOnly(false);
+    }
+
+    private void runTestIsFledgeConsentRevokedForAppWithFullApiConsentAppSearchOnly(
+            boolean isGaUxEnabled) throws Exception {
+        when(mMockFlags.getGaUxFeatureEnabled()).thenReturn(isGaUxEnabled);
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        mConsentManager = getConsentManagerByConsentSourceOfTruth(consentSourceOfTruth);
+        when(mAppSearchConsentManager.getConsent(any())).thenReturn(true);
+
+        mConsentManager.enable(mContextSpy, AdServicesApiType.FLEDGE);
+        ExtendedMockito.verify(
+                () -> UiStatsLogger.logOptInSelected(mContextSpy, AdServicesApiType.FLEDGE));
+
+        String app1 = AppConsentDaoFixture.APP10_PACKAGE_NAME;
+        String app2 = AppConsentDaoFixture.APP20_PACKAGE_NAME;
+        String app3 = AppConsentDaoFixture.APP30_PACKAGE_NAME;
+        mockGetPackageUid(app1, AppConsentDaoFixture.APP10_UID);
+        mockGetPackageUid(app2, AppConsentDaoFixture.APP20_UID);
+        mockGetPackageUid(app3, AppConsentDaoFixture.APP30_UID);
+
+        when(mAppSearchConsentManager.isFledgeConsentRevokedForApp(app1)).thenReturn(false);
+        when(mAppSearchConsentManager.isFledgeConsentRevokedForApp(app2)).thenReturn(true);
+        when(mAppSearchConsentManager.isFledgeConsentRevokedForApp(app3)).thenReturn(false);
+
+        assertFalse(mConsentManager.isFledgeConsentRevokedForApp(app1));
+        assertTrue(mConsentManager.isFledgeConsentRevokedForApp(app2));
+        assertFalse(mConsentManager.isFledgeConsentRevokedForApp(app3));
+    }
+
+    @Test
     public void testIsFledgeConsentRevokedForAppWithFullApiConsentGaUxEnabled_ppApiAndSystemServer()
             throws PackageManager.NameNotFoundException, RemoteException {
         when(mMockFlags.getGaUxFeatureEnabled()).thenReturn(true);
@@ -1228,6 +1270,50 @@
                         AppConsentDaoFixture.APP30_PACKAGE_NAME));
     }
 
+    // AppSearch test for isFledgeConsentRevokedForAppAfterSettingFledgeUse with GA UX disabled.
+    @Test
+    public void testIsFledgeConsentRevokedForAppAfterSetFledgeUseWithFullApiConsentGaUxDisabled_as()
+            throws Exception {
+        runTestIsFledgeConsentRevokedForAppAfterSetFledgeUseWithFullApiConsentAppSearch(false);
+    }
+
+    // AppSearch test for isFledgeConsentRevokedForAppAfterSettingFledgeUse with GA UX enabled.
+    @Test
+    public void testIsFledgeConsentRevokedForAppAfterSetFledgeUseWithFullApiConsentGaUxEnabled_as()
+            throws Exception {
+        runTestIsFledgeConsentRevokedForAppAfterSetFledgeUseWithFullApiConsentAppSearch(true);
+    }
+
+    private void runTestIsFledgeConsentRevokedForAppAfterSetFledgeUseWithFullApiConsentAppSearch(
+            boolean isGaUxEnabled) throws Exception {
+        mConsentManager = getConsentManagerByConsentSourceOfTruth(Flags.APPSEARCH_ONLY);
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+        when(mMockFlags.getGaUxFeatureEnabled()).thenReturn(isGaUxEnabled);
+        mConsentManager.enable(mContextSpy);
+        when(mAppSearchConsentManager.getConsent(any())).thenReturn(true);
+
+        ExtendedMockito.verify(() -> UiStatsLogger.logOptInSelected(mContextSpy));
+        ExtendedMockito.verify(() -> UiStatsLogger.logResetMeasurement(mContextSpy));
+
+        String app1 = AppConsentDaoFixture.APP10_PACKAGE_NAME;
+        String app2 = AppConsentDaoFixture.APP20_PACKAGE_NAME;
+        String app3 = AppConsentDaoFixture.APP30_PACKAGE_NAME;
+        mockGetPackageUid(app1, AppConsentDaoFixture.APP10_UID);
+        mockGetPackageUid(app2, AppConsentDaoFixture.APP20_UID);
+        mockGetPackageUid(app3, AppConsentDaoFixture.APP30_UID);
+
+        when(mAppSearchConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(app1))
+                .thenReturn(false);
+        when(mAppSearchConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(app2))
+                .thenReturn(true);
+        when(mAppSearchConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(app3))
+                .thenReturn(false);
+
+        assertFalse(mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(app1));
+        assertTrue(mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(app2));
+        assertFalse(mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(app3));
+    }
+
     @Test
     public void
             testIsFledgeConsentRevokedForAppAfterSetFledgeUseWithFullApiConsentGaUxEnabled_ppApi()
@@ -1797,6 +1883,41 @@
     }
 
     @Test
+    public void testGetKnownAppsWithConsent_appSearchOnly() {
+        mConsentManager = getConsentManagerByConsentSourceOfTruth(Flags.APPSEARCH_ONLY);
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+
+        ImmutableList<App> consentedAppsList =
+                ImmutableList.of(App.create(AppConsentDaoFixture.APP10_PACKAGE_NAME));
+        ImmutableList<App> revokedAppsList =
+                ImmutableList.of(
+                        App.create(AppConsentDaoFixture.APP20_PACKAGE_NAME),
+                        App.create(AppConsentDaoFixture.APP30_PACKAGE_NAME));
+
+        doReturn(consentedAppsList).when(mAppSearchConsentManager).getKnownAppsWithConsent();
+        doReturn(revokedAppsList).when(mAppSearchConsentManager).getAppsWithRevokedConsent();
+
+        ImmutableList<App> knownAppsWithConsent = mConsentManager.getKnownAppsWithConsent();
+        ImmutableList<App> appsWithRevokedConsent = mConsentManager.getAppsWithRevokedConsent();
+
+        verify(mAppSearchConsentManager).getKnownAppsWithConsent();
+        verify(mAppSearchConsentManager).getAppsWithRevokedConsent();
+
+        // Correct apps have received consent.
+        assertThat(knownAppsWithConsent).hasSize(1);
+        assertThat(knownAppsWithConsent.get(0).getPackageName())
+                .isEqualTo(AppConsentDaoFixture.APP10_PACKAGE_NAME);
+        assertThat(appsWithRevokedConsent).hasSize(2);
+        assertThat(
+                        appsWithRevokedConsent.stream()
+                                .map(app -> app.getPackageName())
+                                .collect(Collectors.toList()))
+                .containsAtLeast(
+                        AppConsentDaoFixture.APP20_PACKAGE_NAME,
+                        AppConsentDaoFixture.APP30_PACKAGE_NAME);
+    }
+
+    @Test
     public void testGetKnownAppsWithConsentAfterConsentForOneOfThemWasRevoked_ppApiOnly()
             throws IOException, PackageManager.NameNotFoundException {
         doNothing().when(mCustomAudienceDaoMock).deleteCustomAudienceDataByOwner(any());
@@ -1973,7 +2094,7 @@
         ImmutableList<App> knownAppsWithConsent = mConsentManager.getKnownAppsWithConsent();
         ImmutableList<App> appsWithRevokedConsent = mConsentManager.getAppsWithRevokedConsent();
 
-        // all apps have received a consent
+        // Correct apps have received a consent
         assertThat(knownAppsWithConsent).hasSize(2);
         assertThat(appsWithRevokedConsent).hasSize(1);
         App appWithRevokedConsent = appsWithRevokedConsent.get(0);
@@ -2080,6 +2201,24 @@
     }
 
     @Test
+    public void testSetConsentForApp_appSearchOnly() throws Exception {
+        mConsentManager = getConsentManagerByConsentSourceOfTruth(Flags.APPSEARCH_ONLY);
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+        mockGetPackageUid(AppConsentDaoFixture.APP10_PACKAGE_NAME, AppConsentDaoFixture.APP10_UID);
+
+        App app = App.create(AppConsentDaoFixture.APP10_PACKAGE_NAME);
+        mConsentManager.revokeConsentForApp(app);
+        verify(mAppSearchConsentManager).revokeConsentForApp(app);
+
+        mConsentManager.restoreConsentForApp(app);
+        verify(mAppSearchConsentManager).restoreConsentForApp(app);
+
+        // TODO (b/274035157): The process crashes with a ClassNotFound exception in static mocking
+        // occasionally. Need to add a Thread.sleep to prevent this crash.
+        Thread.sleep(250);
+    }
+
+    @Test
     public void clearConsentForUninstalledApp_ppApiOnly()
             throws PackageManager.NameNotFoundException, IOException {
         mockGetPackageUid(AppConsentDaoFixture.APP10_PACKAGE_NAME, AppConsentDaoFixture.APP10_UID);
@@ -2123,6 +2262,17 @@
     }
 
     @Test
+    public void clearConsentForUninstalledApp_appSearchOnly() throws Exception {
+        mConsentManager = getConsentManagerByConsentSourceOfTruth(Flags.APPSEARCH_ONLY);
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+        String packageName = AppConsentDaoFixture.APP10_PACKAGE_NAME;
+        mockGetPackageUid(packageName, AppConsentDaoFixture.APP10_UID);
+
+        mConsentManager.clearConsentForUninstalledApp(packageName, AppConsentDaoFixture.APP10_UID);
+        verify(mAppSearchConsentManager).clearConsentForUninstalledApp(packageName);
+    }
+
+    @Test
     public void clearConsentForUninstalledAppWithoutUid_ppApiOnly() throws IOException {
         mDatastore.put(AppConsentDaoFixture.APP10_DATASTORE_KEY, true);
         mDatastore.put(AppConsentDaoFixture.APP20_DATASTORE_KEY, true);
@@ -2431,6 +2581,15 @@
     }
 
     @Test
+    public void testResetAllAppConsentAndAppData_appSearchOnly() throws Exception {
+        mConsentManager = getConsentManagerByConsentSourceOfTruth(Flags.APPSEARCH_ONLY);
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+
+        mConsentManager.resetAppsAndBlockedApps();
+        verify(mAppSearchConsentManager).clearAllAppConsentData();
+    }
+
+    @Test
     public void testResetAllowedAppConsentAndAppData_ppApiOnly()
             throws IOException, PackageManager.NameNotFoundException {
         doNothing().when(mCustomAudienceDaoMock).deleteAllCustomAudienceData();
@@ -2701,6 +2860,15 @@
     }
 
     @Test
+    public void testResetAllowedAppConsentAndAppData_appSearchOnly() throws Exception {
+        mConsentManager = getConsentManagerByConsentSourceOfTruth(Flags.APPSEARCH_ONLY);
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+
+        mConsentManager.resetApps();
+        verify(mAppSearchConsentManager).clearKnownAppsWithConsent();
+    }
+
+    @Test
     public void testNotificationDisplayedRecorded_PpApiOnly() throws RemoteException {
         int consentSourceOfTruth = Flags.PPAPI_ONLY;
         ConsentManager spyConsentManager =
@@ -2768,6 +2936,27 @@
     }
 
     @Test
+    public void testNotificationDisplayedRecorded_appSearchOnly() throws RemoteException {
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(
+                        /* isGiven */ false, consentSourceOfTruth);
+
+        doReturn(false).when(mAppSearchConsentManager).wasNotificationDisplayed();
+        assertThat(spyConsentManager.wasNotificationDisplayed()).isFalse();
+        verify(mAppSearchConsentManager).wasNotificationDisplayed();
+
+        doReturn(true).when(mAppSearchConsentManager).wasNotificationDisplayed();
+        spyConsentManager.recordNotificationDisplayed();
+
+        assertThat(spyConsentManager.wasNotificationDisplayed()).isTrue();
+
+        verify(mAppSearchConsentManager, times(2)).wasNotificationDisplayed();
+        verify(mAppSearchConsentManager).recordNotificationDisplayed();
+    }
+
+    @Test
     public void testGaUxNotificationDisplayedRecorded_PpApiOnly() throws RemoteException {
         int consentSourceOfTruth = Flags.PPAPI_ONLY;
         ConsentManager spyConsentManager =
@@ -2835,6 +3024,25 @@
         assertThat(mConsentDatastore.get(GA_UX_NOTIFICATION_DISPLAYED_ONCE)).isTrue();
     }
 
+    @Test
+    public void testGaUxNotificationDisplayedRecorded_appSearchOnly() throws RemoteException {
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(
+                        /* isGiven */ false, consentSourceOfTruth);
+
+        when(mAppSearchConsentManager.wasGaUxNotificationDisplayed()).thenReturn(false);
+        assertThat(spyConsentManager.wasGaUxNotificationDisplayed()).isFalse();
+        verify(mAppSearchConsentManager).wasGaUxNotificationDisplayed();
+
+        when(mAppSearchConsentManager.wasGaUxNotificationDisplayed()).thenReturn(true);
+        spyConsentManager.recordGaUxNotificationDisplayed();
+        assertThat(spyConsentManager.wasGaUxNotificationDisplayed()).isTrue();
+
+        verify(mAppSearchConsentManager, times(2)).wasGaUxNotificationDisplayed();
+        verify(mAppSearchConsentManager).recordGaUxNotificationDisplayed();
+    }
 
     @Test
     public void testNotificationDisplayedRecorded_notSupportedFlag() throws RemoteException {
@@ -3052,7 +3260,7 @@
                         mAppInstallDaoMock,
                         mAdServicesManager,
                         mConsentDatastore,
-                        mAppSearchConsentService,
+                        mAppSearchConsentManager,
                         mMockFlags,
                         Flags.PPAPI_ONLY);
         doNothing().when(mBlockedTopicsManager).blockTopic(any());
@@ -3159,9 +3367,9 @@
     }
 
     @Test
-    public void testConsentPerApiIsGivenAfterEnabling_AppSearchOnly()
-            throws RemoteException, IOException {
+    public void testConsentPerApiIsGivenAfterEnabling_AppSearchOnly() throws RemoteException {
         when(mMockFlags.getGaUxFeatureEnabled()).thenReturn(true);
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
         boolean isGiven = true;
         int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
         AdServicesApiType apiType = AdServicesApiType.TOPICS;
@@ -3171,13 +3379,138 @@
         spyConsentManager.enable(mContextSpy, AdServicesApiType.TOPICS);
         verify(spyConsentManager)
                 .setPerApiConsentToSourceOfTruth(eq(/* isGiven */ true), eq(apiType));
-        verify(mAppSearchConsentService).setConsent(eq(apiType.toPpApiDatastoreKey()), eq(isGiven));
-        when(mAppSearchConsentService.getConsent(AdServicesApiType.CONSENT_TOPICS))
+        verify(mAppSearchConsentManager).setConsent(eq(apiType.toPpApiDatastoreKey()), eq(isGiven));
+        when(mAppSearchConsentManager.getConsent(AdServicesApiType.CONSENT_TOPICS))
                 .thenReturn(true);
         assertThat(spyConsentManager.getConsent(AdServicesApiType.TOPICS).isGiven()).isTrue();
     }
 
     @Test
+    public void testGetDefaultConsent_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        when(mAppSearchConsentManager.getConsent(eq(ConsentConstants.DEFAULT_CONSENT)))
+                .thenReturn(false);
+        assertThat(spyConsentManager.getDefaultConsent()).isFalse();
+        verify(mAppSearchConsentManager).getConsent(eq(ConsentConstants.DEFAULT_CONSENT));
+    }
+
+    @Test
+    public void testGetTopicsDefaultConsent_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        when(mAppSearchConsentManager.getConsent(eq(ConsentConstants.TOPICS_DEFAULT_CONSENT)))
+                .thenReturn(false);
+        assertThat(spyConsentManager.getTopicsDefaultConsent()).isFalse();
+        verify(mAppSearchConsentManager).getConsent(eq(ConsentConstants.TOPICS_DEFAULT_CONSENT));
+    }
+
+    @Test
+    public void testGetFledgeDefaultConsent_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        when(mAppSearchConsentManager.getConsent(eq(ConsentConstants.FLEDGE_DEFAULT_CONSENT)))
+                .thenReturn(false);
+        assertThat(spyConsentManager.getFledgeDefaultConsent()).isFalse();
+        verify(mAppSearchConsentManager).getConsent(eq(ConsentConstants.FLEDGE_DEFAULT_CONSENT));
+    }
+
+    @Test
+    public void testGetMeasurementDefaultConsent_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        when(mAppSearchConsentManager.getConsent(eq(ConsentConstants.MEASUREMENT_DEFAULT_CONSENT)))
+                .thenReturn(false);
+        assertThat(spyConsentManager.getMeasurementDefaultConsent()).isFalse();
+        verify(mAppSearchConsentManager)
+                .getConsent(eq(ConsentConstants.MEASUREMENT_DEFAULT_CONSENT));
+    }
+
+    @Test
+    public void testGetDefaultAdIdState_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        when(mAppSearchConsentManager.getConsent(eq(ConsentConstants.DEFAULT_AD_ID_STATE)))
+                .thenReturn(false);
+        assertThat(spyConsentManager.getDefaultAdIdState()).isFalse();
+        verify(mAppSearchConsentManager).getConsent(eq(ConsentConstants.DEFAULT_AD_ID_STATE));
+    }
+
+    @Test
+    public void testRecordDefaultConsent_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        spyConsentManager.recordDefaultConsent(true);
+        verify(mAppSearchConsentManager).setConsent(eq(ConsentConstants.DEFAULT_CONSENT), eq(true));
+    }
+
+    @Test
+    public void testRecordTopicsDefaultConsent_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        spyConsentManager.recordTopicsDefaultConsent(true);
+        verify(mAppSearchConsentManager)
+                .setConsent(eq(ConsentConstants.TOPICS_DEFAULT_CONSENT), eq(true));
+    }
+
+    @Test
+    public void testRecordFledgeDefaultConsent_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        spyConsentManager.recordFledgeDefaultConsent(true);
+        verify(mAppSearchConsentManager)
+                .setConsent(eq(ConsentConstants.FLEDGE_DEFAULT_CONSENT), eq(true));
+    }
+
+    @Test
+    public void testRecordMeasurementDefaultConsent_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        spyConsentManager.recordMeasurementDefaultConsent(true);
+        verify(mAppSearchConsentManager)
+                .setConsent(eq(ConsentConstants.MEASUREMENT_DEFAULT_CONSENT), eq(true));
+    }
+
+    @Test
+    public void testRecordDefaultAdIdState_AppSearchOnly() throws RemoteException {
+        doReturn(true).when(mMockFlags).getEnableAppsearchConsentData();
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(false, consentSourceOfTruth);
+
+        spyConsentManager.recordDefaultAdIdState(true);
+        verify(mAppSearchConsentManager)
+                .setConsent(eq(ConsentConstants.DEFAULT_AD_ID_STATE), eq(true));
+    }
+
+    @Test
     public void testAllThreeConsentsPerApiAreGivenAggregatedConsentIsSet_PpApiOnly()
             throws RemoteException, IOException {
         when(mMockFlags.getGaUxFeatureEnabled()).thenReturn(true);
@@ -3340,6 +3673,31 @@
         assertThat(mConsentDatastore.get(MANUAL_INTERACTION_WITH_CONSENT_RECORDED)).isTrue();
     }
 
+    @Test
+    public void testManualInteractionWithConsentRecorded_appSearchOnly() throws RemoteException {
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(
+                        /* isGiven */ false, consentSourceOfTruth);
+
+        when(mAppSearchConsentManager.getUserManualInteractionWithConsent()).thenReturn(UNKNOWN);
+        assertThat(spyConsentManager.getUserManualInteractionWithConsent()).isEqualTo(UNKNOWN);
+        verify(mAppSearchConsentManager).getUserManualInteractionWithConsent();
+        verify(mMockIAdServicesManager, never()).getUserManualInteractionWithConsent();
+
+        spyConsentManager.recordUserManualInteractionWithConsent(MANUAL_INTERACTIONS_RECORDED);
+        verify(mAppSearchConsentManager)
+                .recordUserManualInteractionWithConsent(MANUAL_INTERACTIONS_RECORDED);
+        when(mAppSearchConsentManager.getUserManualInteractionWithConsent())
+                .thenReturn(MANUAL_INTERACTIONS_RECORDED);
+        assertThat(spyConsentManager.getUserManualInteractionWithConsent())
+                .isEqualTo(MANUAL_INTERACTIONS_RECORDED);
+
+        verify(mMockIAdServicesManager, never()).getUserManualInteractionWithConsent();
+        verify(mMockIAdServicesManager, never()).recordUserManualInteractionWithConsent(anyInt());
+    }
+
     // Note this method needs to be invoked after other private variables are initialized.
     private ConsentManager getConsentManagerByConsentSourceOfTruth(int consentSourceOfTruth) {
         return new ConsentManager(
@@ -3353,7 +3711,7 @@
                 mAppInstallDaoMock,
                 mAdServicesManager,
                 mConsentDatastore,
-                mAppSearchConsentService,
+                mAppSearchConsentManager,
                 mMockFlags,
                 consentSourceOfTruth);
     }
@@ -3377,7 +3735,7 @@
         doNothing().when(mMockIAdServicesManager).recordGaUxNotificationDisplayed();
         doReturn(UNKNOWN).when(mMockIAdServicesManager).getUserManualInteractionWithConsent();
         doNothing().when(mMockIAdServicesManager).recordUserManualInteractionWithConsent(anyInt());
-        doReturn(isGiven).when(mAppSearchConsentService).getConsent(CONSENT_KEY);
+        doReturn(isGiven).when(mAppSearchConsentManager).getConsent(CONSENT_KEY);
         return consentManager;
     }
 
@@ -3406,7 +3764,7 @@
         doNothing().when(mMockIAdServicesManager).recordGaUxNotificationDisplayed();
         doReturn(UNKNOWN).when(mMockIAdServicesManager).getUserManualInteractionWithConsent();
         doNothing().when(mMockIAdServicesManager).recordUserManualInteractionWithConsent(anyInt());
-        doReturn(isGiven).when(mAppSearchConsentService).getConsent(any());
+        doReturn(isGiven).when(mAppSearchConsentManager).getConsent(any());
         return consentManager;
     }
 
@@ -3608,4 +3966,31 @@
                                 PrivacySandboxFeatureType.PRIVACY_SANDBOX_RECONSENT.name()))
                 .isTrue();
     }
+
+    @Test
+    public void testCurrentPrivacySandboxFeature_appSearchOnly() throws RemoteException {
+        int consentSourceOfTruth = Flags.APPSEARCH_ONLY;
+        when(mMockFlags.getEnableAppsearchConsentData()).thenReturn(true);
+        ConsentManager spyConsentManager =
+                getSpiedConsentManagerForMigrationTesting(
+                        /* isGiven */ false, consentSourceOfTruth);
+
+        when(mAppSearchConsentManager.getCurrentPrivacySandboxFeature())
+                .thenReturn(PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED);
+        assertThat(spyConsentManager.getCurrentPrivacySandboxFeature())
+                .isEqualTo(PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED);
+
+        spyConsentManager.setCurrentPrivacySandboxFeature(
+                PrivacySandboxFeatureType.PRIVACY_SANDBOX_FIRST_CONSENT);
+        verify(mAppSearchConsentManager)
+                .setCurrentPrivacySandboxFeature(
+                        eq(PrivacySandboxFeatureType.PRIVACY_SANDBOX_FIRST_CONSENT));
+        when(mAppSearchConsentManager.getCurrentPrivacySandboxFeature())
+                .thenReturn(PrivacySandboxFeatureType.PRIVACY_SANDBOX_FIRST_CONSENT);
+        assertThat(spyConsentManager.getCurrentPrivacySandboxFeature())
+                .isEqualTo(PrivacySandboxFeatureType.PRIVACY_SANDBOX_FIRST_CONSENT);
+
+        verify(mMockIAdServicesManager, never()).getCurrentPrivacySandboxFeature();
+        verify(mMockIAdServicesManager, never()).setCurrentPrivacySandboxFeature(anyString());
+    }
 }