Revert "Refactor HaTS service to manage Clank HaTS surveys"

This reverts commit 0076d35594bb0c3e0a3f46d2242b6d597ee59a37.

Reason for revert: Related to failure: https://ci.chromium.org/ui/b/8764172966563883329

Original change's description:
> Refactor HaTS service to manage Clank HaTS surveys
>
> Refactors the service and integrates the permission prompt survey
> trigger
>
> Bug: 1498980, 1495484
> Change-Id: I92853f0261bd81ad6e5b296c0c58a819bbf6b8f2
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5004674
> Reviewed-by: David Roger <[email protected]>
> Commit-Queue: Florian Jacky <[email protected]>
> Code-Coverage: [email protected] <[email protected]>
> Reviewed-by: Wenyu Fu <[email protected]>
> Reviewed-by: Dominic Battre <[email protected]>
> Auto-Submit: Florian Jacky <[email protected]>
> Cr-Commit-Position: refs/heads/main@{#1226063}

Bug: 1498980, 1495484
Change-Id: I936c40b8bab85c99e413a418af5fb4e15fe77727
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5040565
Bot-Commit: Rubber Stamper <[email protected]>
Commit-Queue: Yifan Luo <[email protected]>
Owners-Override: Yifan Luo <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1226091}
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 531b5cf..d43a0317c 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -47,7 +47,6 @@
 #include "components/paint_preview/features/features.h"
 #include "components/password_manager/core/browser/features/password_features.h"
 #include "components/password_manager/core/common/password_manager_features.h"
-#include "components/permissions/features.h"
 #include "components/policy/core/common/features.h"
 #include "components/privacy_sandbox/privacy_sandbox_features.h"
 #include "components/query_tiles/switches.h"
@@ -113,7 +112,6 @@
     &features::kGenericSensorExtraClasses,
     &features::kBackForwardCache,
     &features::kBackForwardTransitions,
-    &features::kBlockMidiByDefault,
     &features::kMetricsSettingsAndroid,
     &features::kNetworkServiceInProcess,
     &shared_highlighting::kPreemptiveLinkToTextGeneration,
@@ -378,7 +376,7 @@
     &password_manager::features::kRecoverFromNeverSaveAndroid,
     &password_manager::features::
         kUnifiedPasswordManagerLocalPasswordsMigrationWarning,
-    &permissions::features::kPermissionsPromptSurvey,
+    &features::kBlockMidiByDefault,
     &privacy_sandbox::kPrivacySandboxFirstPartySetsUI,
     &privacy_sandbox::kPrivacySandboxSettings3,
     &privacy_sandbox::kPrivacySandboxSettings4,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 5b80db0..2f06dbd 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -359,7 +359,6 @@
             "PasskeyManagementUsingAccountSettingsAndroid";
     public static final String PASSWORD_GENERATION_BOTTOM_SHEET = "PasswordGenerationBottomSheet";
     public static final String PASSWORD_EDIT_DIALOG_WITH_DETAILS = "PasswordEditDialogWithDetails";
-    public static final String PERMISSION_PROMPT_SURVEY = "PermissionPromptSurvey";
     public static final String PORTALS = "Portals";
     public static final String PORTALS_CROSS_ORIGIN = "PortalsCrossOrigin";
     public static final String PREEMPTIVE_LINK_TO_TEXT_GENERATION =
diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index c26bff9..dff5883 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -34,9 +34,6 @@
 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
 #include "chrome/browser/subresource_filter/subresource_filter_profile_context_factory.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/browser/ui/hats/hats_service_factory.h"
-#include "chrome/browser/ui/hats/survey_config.h"
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
 #include "chrome/common/channel_info.h"
@@ -47,7 +44,6 @@
 #include "components/permissions/constants.h"
 #include "components/permissions/contexts/bluetooth_chooser_context.h"
 #include "components/permissions/features.h"
-#include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/permissions/permission_request.h"
 #include "components/permissions/permission_uma_util.h"
 #include "components/permissions/permission_util.h"
@@ -77,7 +73,10 @@
 #include "components/permissions/permission_request_manager.h"
 #else
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/permission_bubble/permission_prompt.h"
+#include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/vector_icons/vector_icons.h"
 #endif
 
@@ -262,10 +261,11 @@
   return PermissionsClient::GetOverrideIconId(request_type);
 }
 
+#if !BUILDFLAG(IS_ANDROID)
 // Triggers the prompt HaTS survey if enabled by field trials for this
 // combination of prompt parameters.
 void ChromePermissionsClient::TriggerPromptHatsSurveyIfEnabled(
-    content::WebContents* web_contents,
+    content::BrowserContext* context,
     permissions::RequestType request_type,
     absl::optional<permissions::PermissionAction> action,
     permissions::PermissionPromptDisposition prompt_disposition,
@@ -275,8 +275,7 @@
     bool is_post_prompt,
     const GURL& gurl,
     base::OnceCallback<void()> hats_shown_callback) {
-  Profile* profile =
-      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  Profile* profile = Profile::FromBrowserContext(context);
   absl::optional<GURL> recorded_gurl =
       profile->GetPrefs()->GetBoolean(
           unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled)
@@ -316,13 +315,12 @@
   auto survey_data = permissions::PermissionHatsTriggerHelper::
       SurveyProductSpecificData::PopulateFrom(prompt_parameters);
 
-  hats_service->LaunchSurveyForWebContents(
-      trigger_and_probability->first, web_contents,
-      survey_data.survey_bits_data, survey_data.survey_string_data,
-      std::move(hats_shown_callback), base::DoNothing());
+  hats_service->LaunchSurvey(trigger_and_probability->first,
+                             std::move(hats_shown_callback), base::DoNothing(),
+                             survey_data.survey_bits_data,
+                             survey_data.survey_string_data);
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 permissions::PermissionIgnoredReason
 ChromePermissionsClient::DetermineIgnoreReason(
     content::WebContents* web_contents) {
@@ -386,6 +384,7 @@
     }
   }
 
+#if !BUILDFLAG(IS_ANDROID)
   auto content_setting_type = RequestTypeToContentSettingsType(request_type);
   if (content_setting_type.has_value()) {
     permissions::PermissionHatsTriggerHelper::
@@ -394,10 +393,12 @@
   }
 
   TriggerPromptHatsSurveyIfEnabled(
-      web_contents, request_type, absl::make_optional(action),
-      prompt_disposition, prompt_disposition_reason, gesture_type,
+      web_contents->GetBrowserContext(), request_type,
+      absl::make_optional(action), prompt_disposition,
+      prompt_disposition_reason, gesture_type,
       absl::make_optional(prompt_display_duration), true,
       web_contents->GetLastCommittedURL(), base::DoNothing());
+#endif  // !BUILDFLAG(IS_ANDROID)
 }
 
 absl::optional<bool>
diff --git a/chrome/browser/permissions/chrome_permissions_client.h b/chrome/browser/permissions/chrome_permissions_client.h
index 1c6115e..57c7368d 100644
--- a/chrome/browser/permissions/chrome_permissions_client.h
+++ b/chrome/browser/permissions/chrome_permissions_client.h
@@ -58,8 +58,9 @@
   CreatePermissionUiSelectors(
       content::BrowserContext* browser_context) override;
 
+#if !BUILDFLAG(IS_ANDROID)
   void TriggerPromptHatsSurveyIfEnabled(
-      content::WebContents* web_contents,
+      content::BrowserContext* context,
       permissions::RequestType request_type,
       absl::optional<permissions::PermissionAction> action,
       permissions::PermissionPromptDisposition prompt_disposition,
@@ -70,7 +71,6 @@
       const GURL& gurl,
       base::OnceCallback<void()> hats_shown_callback_) override;
 
-#if !BUILDFLAG(IS_ANDROID)
   permissions::PermissionIgnoredReason DetermineIgnoreReason(
       content::WebContents* web_contents) override;
 #endif
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 2c72490..82fc470 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -76,7 +76,7 @@
 #include "chrome/browser/tpcd/experiment/tpcd_pref_names.h"
 #include "chrome/browser/tracing/chrome_tracing_delegate.h"
 #include "chrome/browser/ui/browser_ui_prefs.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
+#include "chrome/browser/ui/hats/hats_service.h"
 #include "chrome/browser/ui/network_profile_bubble.h"
 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
 #include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
@@ -138,7 +138,6 @@
 #include "components/password_manager/core/browser/password_manager.h"
 #include "components/payments/core/payment_prefs.h"
 #include "components/performance_manager/public/user_tuning/prefs.h"
-#include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/permissions/pref_names.h"
 #include "components/plus_addresses/plus_address_prefs.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
@@ -307,6 +306,7 @@
 #include "components/live_caption/live_caption_controller.h"
 #include "components/live_caption/live_translate_controller.h"
 #include "components/ntp_tiles/custom_links_manager_impl.h"
+#include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/user_notes/user_notes_prefs.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
@@ -1695,7 +1695,6 @@
   dom_distiller::DistilledPagePrefs::RegisterProfilePrefs(registry);
   dom_distiller::RegisterProfilePrefs(registry);
   DownloadPrefs::RegisterProfilePrefs(registry);
-  permissions::PermissionHatsTriggerHelper::RegisterProfilePrefs(registry);
   history_clusters::prefs::RegisterProfilePrefs(registry);
   HostContentSettingsMap::RegisterProfilePrefs(registry);
   image_fetcher::ImageCache::RegisterProfilePrefs(registry);
@@ -1848,7 +1847,7 @@
   extensions::TabsCaptureVisibleTabFunction::RegisterProfilePrefs(registry);
   first_run::RegisterProfilePrefs(registry);
   gcm::RegisterProfilePrefs(registry);
-  HatsServiceDesktop::RegisterProfilePrefs(registry);
+  HatsService::RegisterProfilePrefs(registry);
   NtpCustomBackgroundService::RegisterProfilePrefs(registry);
   media_router::RegisterAccessCodeProfilePrefs(registry);
   media_router::RegisterProfilePrefs(registry);
@@ -2057,6 +2056,7 @@
   registry->RegisterIntegerPref(prefs::kHighEfficiencyChipExpandedCount, 0);
   registry->RegisterTimePref(prefs::kLastHighEfficiencyChipExpandedTimestamp,
                              base::Time());
+  permissions::PermissionHatsTriggerHelper::RegisterProfilePrefs(registry);
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 839daf21..98241e2 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -187,7 +187,6 @@
 #include "chrome/browser/translate/translate_ranker_factory.h"
 #include "chrome/browser/ui/cookie_controls/cookie_controls_service_factory.h"
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
-#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.h"
 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
 #include "chrome/browser/ui/tabs/pinned_tab_service_factory.h"
@@ -948,7 +947,6 @@
 #endif
   ProfilePasswordStoreFactory::GetInstance();
   payments::CanMakePaymentQueryFactory::GetInstance();
-  HatsServiceFactory::GetInstance();
 #if !BUILDFLAG(IS_ANDROID)
   payments::PaymentRequestDisplayManagerFactory::GetInstance();
   performance_manager::SiteDataCacheFacadeFactory::GetInstance();
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a1bfc289..97c456a 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -114,10 +114,6 @@
     "find_bar/find_bar_state.h",
     "find_bar/find_bar_state_factory.cc",
     "find_bar/find_bar_state_factory.h",
-    "hats/hats_service.cc",
-    "hats/hats_service.h",
-    "hats/hats_service_factory.cc",
-    "hats/hats_service_factory.h",
     "hats/survey_config.cc",
     "hats/survey_config.h",
     "idle_bubble.h",
@@ -832,8 +828,6 @@
       "android/fast_checkout/fast_checkout_view_impl.h",
       "android/fast_checkout/ui_view_android_utils.cc",
       "android/fast_checkout/ui_view_android_utils.h",
-      "android/hats/hats_service_android.cc",
-      "android/hats/hats_service_android.h",
       "android/hats/survey_client_android.cc",
       "android/hats/survey_client_android.h",
       "android/hats/survey_config_android.cc",
@@ -1222,8 +1216,10 @@
       "global_media_controls/supplemental_device_picker_producer.h",
       "hats/hats_helper.cc",
       "hats/hats_helper.h",
-      "hats/hats_service_desktop.cc",
-      "hats/hats_service_desktop.h",
+      "hats/hats_service.cc",
+      "hats/hats_service.h",
+      "hats/hats_service_factory.cc",
+      "hats/hats_service_factory.h",
       "hats/trust_safety_sentiment_service.cc",
       "hats/trust_safety_sentiment_service.h",
       "hats/trust_safety_sentiment_service_factory.cc",
diff --git a/chrome/browser/ui/android/hats/hats_service_android.cc b/chrome/browser/ui/android/hats/hats_service_android.cc
deleted file mode 100644
index 459c182..0000000
--- a/chrome/browser/ui/android/hats/hats_service_android.cc
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/android/hats/hats_service_android.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/functional/bind.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/notreached.h"
-#include "base/ranges/algorithm.h"
-#include "base/task/sequenced_task_runner.h"
-#include "chrome/browser/android/resource_mapper.h"
-#include "chrome/browser/prefs/incognito_mode_prefs.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profiles_state.h"
-#include "chrome/browser/sessions/exit_type_service.h"
-#include "chrome/browser/ui/android/hats/survey_client_android.h"
-#include "chrome/browser/ui/android/hats/survey_ui_delegate_android.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/messages/android/message_wrapper.h"
-#include "components/resources/android/theme_resources.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/base/l10n/l10n_util.h"
-
-constexpr char kHatsShouldShowSurveyReasonAndroidHistogram[] =
-    "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReasonAndroid";
-
-HatsServiceAndroid::DelayedSurveyTask::DelayedSurveyTask(
-    HatsServiceAndroid* hats_service,
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback)
-    : web_contents_(web_contents),
-      hats_service_(hats_service),
-      trigger_(trigger),
-      product_specific_bits_data_(product_specific_bits_data),
-      product_specific_string_data_(product_specific_string_data),
-      success_callback_(std::move(success_callback)),
-      failure_callback_(std::move(failure_callback)) {}
-
-HatsServiceAndroid::DelayedSurveyTask::~DelayedSurveyTask() = default;
-
-void HatsServiceAndroid::DelayedSurveyTask::Launch() {
-  CHECK(web_contents());
-  if (!web_contents() ||
-      web_contents()->GetVisibility() != content::Visibility::VISIBLE) {
-    return;
-  }
-
-  message_ = std::make_unique<messages::MessageWrapper>(
-      messages::MessageIdentifier::CHROME_SURVEY, std::move(success_callback_),
-      base::BindOnce(&HatsServiceAndroid::DelayedSurveyTask::DismissCallback,
-                     weak_ptr_factory_.GetWeakPtr()));
-
-  hats::SurveyUiDelegateAndroid delegate(
-      message_.get(), web_contents()->GetTopLevelNativeWindow());
-
-  // Create survey client with delegate.
-  hats::SurveyClientAndroid survey_client(trigger_, &delegate,
-                                          hats_service_->profile());
-  survey_client.LaunchSurvey(web_contents()->GetTopLevelNativeWindow(),
-                             product_specific_bits_data_,
-                             product_specific_string_data_);
-}
-
-void HatsServiceAndroid::DelayedSurveyTask::DismissCallback(
-    messages::DismissReason dismiss_reason) {
-  if (dismiss_reason != messages::DismissReason::PRIMARY_ACTION &&
-      !failure_callback_.is_null()) {
-    std::move(failure_callback_).Run();
-  }
-
-  ShouldShowSurveyReasonsAndroid reason;
-  switch (dismiss_reason) {
-    case messages::DismissReason::UNKNOWN:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidUnknown;
-      break;
-    case messages::DismissReason::PRIMARY_ACTION:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidAccepted;
-      break;
-    case messages::DismissReason::SECONDARY_ACTION:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidSecondaryAction;
-      break;
-    case messages::DismissReason::TIMER:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidExpired;
-      break;
-    case messages::DismissReason::GESTURE:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidDismissedByGesture;
-      break;
-    case messages::DismissReason::TAB_SWITCHED:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidTabSwitched;
-      break;
-    case messages::DismissReason::TAB_DESTROYED:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidTabDestroyed;
-      break;
-    case messages::DismissReason::ACTIVITY_DESTROYED:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidActivityDestroyed;
-      break;
-    case messages::DismissReason::SCOPE_DESTROYED:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidScopeDestroyed;
-      break;
-    case messages::DismissReason::DISMISSED_BY_FEATURE:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidDismissedByFeature;
-      break;
-    case messages::DismissReason::COUNT:
-      reason = ShouldShowSurveyReasonsAndroid::kAndroidUnknown;
-      NOTREACHED();
-  }
-  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonAndroidHistogram,
-                            reason);
-  hats_service_->RemoveTask(*this);
-}
-
-base::WeakPtr<HatsServiceAndroid::DelayedSurveyTask>
-HatsServiceAndroid::DelayedSurveyTask::GetWeakPtr() {
-  return weak_ptr_factory_.GetWeakPtr();
-}
-
-HatsServiceAndroid::HatsServiceAndroid(Profile* profile)
-    : HatsService(profile) {}
-
-HatsServiceAndroid::~HatsServiceAndroid() = default;
-
-void HatsServiceAndroid::LaunchSurvey(
-    const std::string& trigger,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  NOTIMPLEMENTED();
-}
-
-void HatsServiceAndroid::LaunchSurveyForWebContents(
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback) {
-  // By using a delayed survey with a delay of 0, we can centralize the object
-  // lifecycle management duties for native clank survey triggers.
-  LaunchDelayedSurveyForWebContents(
-      trigger, web_contents, 0, product_specific_bits_data,
-      product_specific_string_data, false, std::move(success_callback),
-      std::move(failure_callback));
-}
-
-bool HatsServiceAndroid::LaunchDelayedSurvey(
-    const std::string& trigger,
-    int timeout_ms,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  NOTIMPLEMENTED();
-  return false;
-}
-
-bool HatsServiceAndroid::LaunchDelayedSurveyForWebContents(
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    int timeout_ms,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data,
-    bool require_same_origin,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback) {
-  CHECK(web_contents);
-  CHECK(!require_same_origin);  // Currently not supported on Android
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (survey_configs_by_triggers_.find(trigger) ==
-      survey_configs_by_triggers_.end()) {
-    // Survey configuration is not available.
-    if (!failure_callback.is_null()) {
-      std::move(failure_callback).Run();
-    }
-    return false;
-  }
-  auto result = pending_tasks_.emplace(
-      this, trigger, web_contents, product_specific_bits_data,
-      product_specific_string_data, std::move(success_callback),
-      std::move(failure_callback));
-  if (!result.second) {
-    return false;
-  }
-  auto success =
-      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-          FROM_HERE,
-          base::BindOnce(&HatsServiceAndroid::DelayedSurveyTask::Launch,
-                         const_cast<HatsServiceAndroid::DelayedSurveyTask&>(
-                             *(result.first))
-                             .GetWeakPtr()),
-          base::Milliseconds(timeout_ms));
-  if (!success) {
-    pending_tasks_.erase(result.first);
-  }
-  return success;
-}
-
-bool HatsServiceAndroid::CanShowAnySurvey(bool user_prompted) const {
-  NOTIMPLEMENTED();  // Survey throttling happens on the clank side
-  return false;
-}
-
-bool HatsServiceAndroid::CanShowSurvey(const std::string& trigger) const {
-  NOTIMPLEMENTED();  // Survey throttling happens on the clank side
-  return false;
-}
-
-void HatsServiceAndroid::RecordSurveyAsShown(std::string trigger_id) {
-  // Record the trigger associated with the trigger_id. This is recorded
-  // instead of the trigger ID itself, as the ID is specific to individual
-  // survey versions. There should be a cooldown before a user is prompted to
-  // take a survey from the same trigger, regardless of whether the survey was
-  // updated.
-  auto trigger_survey_config =
-      base::ranges::find(survey_configs_by_triggers_, trigger_id,
-                         [](const SurveyConfigs::value_type& pair) {
-                           return pair.second.trigger_id;
-                         });
-
-  DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
-  std::string trigger = trigger_survey_config->first;
-
-  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonAndroidHistogram,
-                            ShouldShowSurveyReasonsAndroid::kYes);
-}
-
-void HatsServiceAndroid::RemoveTask(const DelayedSurveyTask& task) {
-  pending_tasks_.erase(task);
-}
diff --git a/chrome/browser/ui/android/hats/hats_service_android.h b/chrome/browser/ui/android/hats/hats_service_android.h
deleted file mode 100644
index 1a8512cd..0000000
--- a/chrome/browser/ui/android/hats/hats_service_android.h
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
-#define CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
-
-#include <memory>
-#include <set>
-#include <string>
-
-#include "base/functional/callback.h"
-#include "base/functional/callback_forward.h"
-#include "base/functional/callback_helpers.h"
-#include "base/gtest_prod_util.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "components/messages/android/message_enums.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_observer.h"
-
-namespace messages {
-class MessageWrapper;
-}
-
-// The name of the histogram which records if a survey was shown, or if not, the
-// reason why not.
-extern const char kHatsShouldShowSurveyReasonAndroidHistogram[];
-
-// This class provides the client side logic for determining if a
-// survey should be shown for any trigger based on input from a finch
-// configuration. It is created on a per profile basis.
-class HatsServiceAndroid : public HatsService {
- public:
-  class DelayedSurveyTask {
-   public:
-    DelayedSurveyTask(HatsServiceAndroid* hats_service,
-                      const std::string& trigger,
-                      content::WebContents* web_contents,
-                      const SurveyBitsData& product_specific_bits_data,
-                      const SurveyStringData& product_specific_string_data,
-                      base::OnceClosure success_callback,
-                      base::OnceClosure failure_callback);
-
-    // Not copyable or movable
-    DelayedSurveyTask(const DelayedSurveyTask&) = delete;
-    DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
-
-    ~DelayedSurveyTask();
-
-    // Asks |hats_service_| to launch the survey with id |trigger_| for tab
-    // |web_contents_|.
-    void Launch();
-
-    void DismissCallback(messages::DismissReason reason);
-
-    content::WebContents* web_contents() const { return web_contents_.get(); }
-
-    // Returns a weak pointer to this object.
-    base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
-
-    bool operator<(const HatsServiceAndroid::DelayedSurveyTask& other) const {
-      return trigger_ < other.trigger_ ? true
-                                       : web_contents() < other.web_contents();
-    }
-
-    messages::MessageWrapper* GetMessageForTesting() { return message_.get(); }
-
-   private:
-    raw_ptr<content::WebContents> web_contents_;
-    raw_ptr<HatsServiceAndroid> hats_service_;
-
-    std::unique_ptr<messages::MessageWrapper> message_;
-    std::string trigger_;
-    SurveyBitsData product_specific_bits_data_;
-    SurveyStringData product_specific_string_data_;
-    base::OnceClosure success_callback_;
-    base::OnceClosure failure_callback_;
-    base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
-  };
-
-  // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused.
-  enum class ShouldShowSurveyReasonsAndroid {
-    kYes = 0,
-    kAndroidUnknown = 1,   // Catch all for Android invitation dismissals.
-                           // Should be investigated if this regularly occurs.
-    kAndroidAccepted = 2,  // Invitation accepted
-    kAndroidSecondaryAction =
-        3,  // Not in use by the default survey implementation. May be used by
-            // customized trigger implementations.
-    kAndroidExpired = 4,  // Survey invitation expired and was automatically
-                          // dismissed. Default timeout is 10s, see
-                          // `ChromeMessageAutodismissDurationProvider.java`
-    kAndroidDismissedByGesture = 5,  // Dismissed by swiping the dialog away
-    kAndroidTabSwitched = 6,
-    kAndroidTabDestroyed = 7,
-    kAndroidActivityDestroyed = 8,
-    kAndroidScopeDestroyed = 9,
-    kAndroidDismissedByFeature =
-        10,  // Another survey was already launched, leading to the current one
-             // being aborted.
-    kMaxValue = kAndroidDismissedByFeature,
-  };
-
-  explicit HatsServiceAndroid(Profile* profile);
-
-  HatsServiceAndroid(const HatsServiceAndroid&) = delete;
-  HatsServiceAndroid& operator=(const HatsServiceAndroid&) = delete;
-
-  ~HatsServiceAndroid() override;
-
-  void LaunchSurvey(
-      const std::string& trigger,
-      base::OnceClosure success_callback = base::DoNothing(),
-      base::OnceClosure failure_callback = base::DoNothing(),
-      const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {}) override;
-
-  void LaunchSurveyForWebContents(
-      const std::string& trigger,
-      content::WebContents* web_contents,
-      const SurveyBitsData& product_specific_bits_data,
-      const SurveyStringData& product_specific_string_data,
-      base::OnceClosure success_callback = base::DoNothing(),
-      base::OnceClosure failure_callback = base::DoNothing()) override;
-
-  bool LaunchDelayedSurvey(
-      const std::string& trigger,
-      int timeout_ms,
-      const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {}) override;
-
-  bool LaunchDelayedSurveyForWebContents(
-      const std::string& trigger,
-      content::WebContents* web_contents,
-      int timeout_ms,
-      const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {},
-      bool require_same_origin = false,
-      base::OnceClosure success_callback = base::DoNothing(),
-      base::OnceClosure failure_callback = base::DoNothing()) override;
-
-  // Currently not implemented
-  bool CanShowAnySurvey(bool user_prompted) const override;
-
-  // Currently not implemented
-  bool CanShowSurvey(const std::string& trigger) const override;
-
-  void RecordSurveyAsShown(std::string trigger_id) override;
-
-  DelayedSurveyTask& GetFirstTaskForTesting() {
-    return const_cast<DelayedSurveyTask&>(*pending_tasks_.begin());
-  }
-
- protected:
-  // Remove |task| from the set of |pending_tasks_|.
-  void RemoveTask(const DelayedSurveyTask& task);
-
- private:
-  friend class DelayedSurveyTask;
-  FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
-
-  std::set<DelayedSurveyTask> pending_tasks_;
-  base::WeakPtrFactory<HatsServiceAndroid> weak_ptr_factory_{this};
-};
-
-#endif  // CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
index 1204fc6f..c3aeed2 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
@@ -106,7 +106,7 @@
 
     /**
      * Rolls a random number to see if the user was eligible for the survey. The user will skip the
-     * roll if: 1. User is a first time user; 2. User has performed the roll for the survey today;
+     * roll if: 1. User is a first time user; 2. User has performed the roll for the survey today; 
      * 3. Max number is not setup correctly.
      *
      * @return Whether the user is eligible (i.e. the random number rolled was 0).
diff --git a/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc b/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
index 1d4989a..16dc7df1 100644
--- a/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
+++ b/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
@@ -6,15 +6,18 @@
 
 #include "base/functional/bind.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/android/hats/hats_service_android.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/android/hats/survey_client_android.h"
+#include "chrome/browser/ui/android/hats/survey_ui_delegate_android.h"
 #include "chrome/browser/ui/android/hats/test/test_survey_utils_bridge.h"
-#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/test/base/chrome_test_utils.h"
 #include "components/messages/android/message_dispatcher_bridge.h"
+#include "components/messages/android/message_wrapper.h"
 #include "components/messages/android/test/messages_test_helper.h"
 #include "content/public/test/browser_test.h"
 #include "net/dns/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/android/window_android.h"
 
 namespace hats {
 namespace {
@@ -42,29 +45,6 @@
 
   content::WaiterHelper waiter_helper_;
 };
-
-class SurveyObserver {
- public:
-  SurveyObserver() = default;
-  void Accept() { accepted_ = true; }
-
-  void Dismiss() { dismissed_ = true; }
-
-  bool IsAccepted() { return accepted_; }
-
-  bool IsDismissed() { return dismissed_; }
-
-  base::WeakPtr<SurveyObserver> GetWeakPtr() {
-    return weak_ptr_factory_.GetWeakPtr();
-  }
-
- private:
-  bool accepted_ = false;
-  bool dismissed_ = false;
-
-  base::WeakPtrFactory<SurveyObserver> weak_ptr_factory_{this};
-};
-
 }  // namespace
 
 class SurveyClientAndroidBrowserTest : public AndroidBrowserTest {
@@ -97,39 +77,63 @@
     return web_contents()->GetTopLevelNativeWindow();
   }
 
-  HatsServiceAndroid* GetHatsService() {
-    HatsServiceAndroid* service =
-        static_cast<HatsServiceAndroid*>(HatsServiceFactory::GetForProfile(
-            Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
-            true));
-    return service;
+  messages::MessageWrapper* createMessage() {
+    message_ = std::make_unique<messages::MessageWrapper>(
+        messages::MessageIdentifier::TEST_MESSAGE,
+        base::BindOnce(&SurveyClientAndroidBrowserTest::MessageAccepted,
+                       base::Unretained(this)),
+        base::BindOnce(&SurveyClientAndroidBrowserTest::MessageDeclined,
+                       base::Unretained(this)));
+    return message_.get();
   }
 
+  bool message_accepted() { return message_accepted_; }
+
+  bool message_declined() { return message_declined_; }
+
  protected:
+  void MessageAccepted() { message_accepted_ = true; }
+
+  void MessageDeclined(messages::DismissReason dismiss_reason) {
+    message_declined_ = true;
+  }
+
   messages::MessagesTestHelper messages_test_helper_;
+
+ private:
+  std::unique_ptr<messages::MessageWrapper> message_;
+  bool message_accepted_;
+  bool message_declined_;
 };
 
-IN_PROC_BROWSER_TEST_F(SurveyClientAndroidBrowserTest, LaunchSurvey) {
+IN_PROC_BROWSER_TEST_F(SurveyClientAndroidBrowserTest,
+                       CreateSurveyWithMessage) {
   EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
+  auto* message = createMessage();
+  std::unique_ptr<SurveyUiDelegateAndroid> delegate =
+      std::make_unique<SurveyUiDelegateAndroid>(message, window_android());
+  EXPECT_TRUE(delegate.get());
 
-  SurveyObserver observer;
-
-  auto* hatsService = GetHatsService();
+  // Create survey client with delegate.
+  std::unique_ptr<SurveyClientAndroid> survey_client =
+      std::make_unique<SurveyClientAndroid>(
+          kTestSurveyTrigger, delegate.get(),
+          ProfileManager::GetActiveUserProfile());
   {
     MessageWaiter waiter(messages_test_helper_);
-    hatsService->LaunchSurveyForWebContents(
-        kTestSurveyTrigger, web_contents(), kTestSurveyProductSpecificBitsData,
-        kTestSurveyProductSpecificStringData,
-        base::BindOnce(&SurveyObserver::Accept, observer.GetWeakPtr()),
-        base::BindOnce(&SurveyObserver::Dismiss, observer.GetWeakPtr()));
+    survey_client->LaunchSurvey(window_android(),
+                                kTestSurveyProductSpecificBitsData,
+                                kTestSurveyProductSpecificStringData);
     EXPECT_TRUE(waiter.Wait());
   }
 
-  hatsService->GetFirstTaskForTesting()
-      .GetMessageForTesting()
-      ->HandleActionClick(base::android::AttachCurrentThread());
+  EXPECT_EQ(1, messages_test_helper_.GetMessageCount(window_android()));
+  EXPECT_EQ(static_cast<int>(messages::MessageIdentifier::TEST_MESSAGE),
+            messages_test_helper_.GetMessageIdentifier(window_android(), 0));
 
-  EXPECT_TRUE(observer.IsAccepted());
+  messages::MessageDispatcherBridge::Get()->DismissMessage(
+      message, messages::DismissReason::UNKNOWN);
+  EXPECT_TRUE(message_declined());
 }
 
 }  // namespace hats
diff --git a/chrome/browser/ui/hats/DEPS b/chrome/browser/ui/hats/DEPS
index aa788a9..f207e6f 100644
--- a/chrome/browser/ui/hats/DEPS
+++ b/chrome/browser/ui/hats/DEPS
@@ -6,5 +6,5 @@
 
   "trust_safety_sentiment_service_browsertest\.cc": [
     "+chrome/browser/ui/views/page_info",
-  ],
+  ]
 }
diff --git a/chrome/browser/ui/hats/hats_service.cc b/chrome/browser/ui/hats/hats_service.cc
index 5964190..b15f911 100644
--- a/chrome/browser/ui/hats/hats_service.cc
+++ b/chrome/browser/ui/hats/hats_service.cc
@@ -4,12 +4,596 @@
 
 #include "chrome/browser/ui/hats/hats_service.h"
 
+#include <memory>
+#include <utility>
+
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "base/json/values_util.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
+#include "base/ranges/algorithm.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/sessions/exit_type_service.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "components/metrics_services_manager/metrics_services_manager.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/network_change_notifier.h"
+
+constexpr char kHatsShouldShowSurveyReasonHistogram[] =
+    "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReason";
+
+namespace {
+
+// TODO(crbug.com/1160661): When the minimum time between any survey, and the
+// minimum time between a specific survey, are the same, the logic supporting
+// the latter check is superfluous.
+constexpr base::TimeDelta kMinimumTimeBetweenSurveyStarts = base::Days(180);
+
+constexpr base::TimeDelta kMinimumTimeBetweenAnySurveyStarts = base::Days(180);
+
+constexpr base::TimeDelta kMinimumTimeBetweenSurveyChecks = base::Days(1);
+
+constexpr base::TimeDelta kMinimumProfileAge = base::Days(30);
+
+// Preferences Data Model
+// The kHatsSurveyMetadata pref points to a dictionary.
+// The valid keys and value types for this dictionary are as follows:
+// [trigger].last_major_version        ---> Integer
+// [trigger].last_survey_started_time  ---> Time
+// [trigger].is_survey_full            ---> Bool
+// [trigger].last_survey_check_time    ---> Time
+// any_last_survey_started_time        ---> Time
+
+std::string GetMajorVersionPath(const std::string& trigger) {
+  return trigger + ".last_major_version";
+}
+
+std::string GetLastSurveyStartedTime(const std::string& trigger) {
+  return trigger + ".last_survey_started_time";
+}
+
+std::string GetIsSurveyFull(const std::string& trigger) {
+  return trigger + ".is_survey_full";
+}
+
+std::string GetLastSurveyCheckTime(const std::string& trigger) {
+  return trigger + ".last_survey_check_time";
+}
+
+constexpr char kAnyLastSurveyStartedTimePath[] = "any_last_survey_started_time";
+
+}  // namespace
+
 HatsService::SurveyMetadata::SurveyMetadata() = default;
 
 HatsService::SurveyMetadata::~SurveyMetadata() = default;
 
+HatsService::DelayedSurveyTask::DelayedSurveyTask(
+    HatsService* hats_service,
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data,
+    bool require_same_origin)
+    : hats_service_(hats_service),
+      trigger_(trigger),
+      product_specific_bits_data_(product_specific_bits_data),
+      product_specific_string_data_(product_specific_string_data),
+      require_same_origin_(require_same_origin) {
+  Observe(web_contents);
+}
+
+HatsService::DelayedSurveyTask::~DelayedSurveyTask() = default;
+
+base::WeakPtr<HatsService::DelayedSurveyTask>
+HatsService::DelayedSurveyTask::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+void HatsService::DelayedSurveyTask::Launch() {
+  hats_service_->LaunchSurveyForWebContents(trigger_, web_contents(),
+                                            product_specific_bits_data_,
+                                            product_specific_string_data_);
+  hats_service_->RemoveTask(*this);
+}
+
+void HatsService::DelayedSurveyTask::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!require_same_origin_ || !navigation_handle ||
+      !navigation_handle->IsInPrimaryMainFrame() ||
+      navigation_handle->IsSameDocument() ||
+      (navigation_handle->HasCommitted() &&
+       navigation_handle->IsSameOrigin())) {
+    return;
+  }
+
+  hats_service_->RemoveTask(*this);
+}
+
+void HatsService::DelayedSurveyTask::WebContentsDestroyed() {
+  hats_service_->RemoveTask(*this);
+}
+
 HatsService::HatsService(Profile* profile) : profile_(profile) {
   hats::GetActiveSurveyConfigs(survey_configs_by_triggers_);
 }
 
 HatsService::~HatsService() = default;
+
+// static
+void HatsService::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterDictionaryPref(
+      prefs::kHatsSurveyMetadata,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+}
+
+void HatsService::LaunchSurvey(
+    const std::string& trigger,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  if (!ShouldShowSurvey(trigger)) {
+    std::move(failure_callback).Run();
+    return;
+  }
+
+  LaunchSurveyForBrowser(
+      chrome::FindLastActiveWithProfile(profile_), trigger,
+      std::move(success_callback), std::move(failure_callback),
+      product_specific_bits_data, product_specific_string_data);
+}
+
+bool HatsService::LaunchDelayedSurvey(
+    const std::string& trigger,
+    int timeout_ms,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  return base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&HatsService::LaunchSurvey, weak_ptr_factory_.GetWeakPtr(),
+                     trigger, base::DoNothing(), base::DoNothing(),
+                     product_specific_bits_data, product_specific_string_data),
+      base::Milliseconds(timeout_ms));
+}
+
+bool HatsService::LaunchDelayedSurveyForWebContents(
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    int timeout_ms,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data,
+    bool require_same_origin) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (!web_contents) {
+    return false;
+  }
+  auto result = pending_tasks_.emplace(
+      this, trigger, web_contents, product_specific_bits_data,
+      product_specific_string_data, require_same_origin);
+  if (!result.second) {
+    return false;
+  }
+  auto success =
+      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+          FROM_HERE,
+          base::BindOnce(
+              &HatsService::DelayedSurveyTask::Launch,
+              const_cast<HatsService::DelayedSurveyTask&>(*(result.first))
+                  .GetWeakPtr()),
+          base::Milliseconds(timeout_ms));
+  if (!success) {
+    pending_tasks_.erase(result.first);
+  }
+  return success;
+}
+
+void HatsService::RecordSurveyAsShown(std::string trigger_id) {
+  // Record the trigger associated with the trigger_id. This is recorded instead
+  // of the trigger ID itself, as the ID is specific to individual survey
+  // versions. There should be a cooldown before a user is prompted to take a
+  // survey from the same trigger, regardless of whether the survey was updated.
+  auto trigger_survey_config =
+      base::ranges::find(survey_configs_by_triggers_, trigger_id,
+                         [](const SurveyConfigs::value_type& pair) {
+                           return pair.second.trigger_id;
+                         });
+
+  DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
+  std::string trigger = trigger_survey_config->first;
+
+  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                            ShouldShowSurveyReasons::kYes);
+
+  ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
+  base::Value::Dict& pref_data = update.Get();
+  pref_data.SetByDottedPath(
+      GetMajorVersionPath(trigger),
+      static_cast<int>(version_info::GetVersion().components()[0]));
+  pref_data.SetByDottedPath(GetLastSurveyStartedTime(trigger),
+                            base::TimeToValue(base::Time::Now()));
+  pref_data.SetByDottedPath(kAnyLastSurveyStartedTimePath,
+                            base::TimeToValue(base::Time::Now()));
+}
+
+void HatsService::HatsNextDialogClosed() {
+  hats_next_dialog_exists_ = false;
+}
+
+void HatsService::SetSurveyMetadataForTesting(
+    const HatsService::SurveyMetadata& metadata) {
+  const std::string& trigger = kHatsSurveyTriggerSettings;
+  ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
+  base::Value::Dict& pref_data = update.Get();
+  if (!metadata.last_major_version.has_value() &&
+      !metadata.last_survey_started_time.has_value() &&
+      !metadata.is_survey_full.has_value() &&
+      !metadata.last_survey_check_time.has_value()) {
+    pref_data.RemoveByDottedPath(trigger);
+  }
+
+  if (metadata.last_major_version.has_value()) {
+    pref_data.SetByDottedPath(GetMajorVersionPath(trigger),
+                              *metadata.last_major_version);
+  } else {
+    pref_data.RemoveByDottedPath(GetMajorVersionPath(trigger));
+  }
+
+  if (metadata.last_survey_started_time.has_value()) {
+    pref_data.SetByDottedPath(
+        GetLastSurveyStartedTime(trigger),
+        base::TimeToValue(*metadata.last_survey_started_time));
+  } else {
+    pref_data.RemoveByDottedPath(GetLastSurveyStartedTime(trigger));
+  }
+
+  if (metadata.any_last_survey_started_time.has_value()) {
+    pref_data.SetByDottedPath(
+        kAnyLastSurveyStartedTimePath,
+        base::TimeToValue(*metadata.any_last_survey_started_time));
+  } else {
+    pref_data.RemoveByDottedPath(kAnyLastSurveyStartedTimePath);
+  }
+
+  if (metadata.is_survey_full.has_value()) {
+    pref_data.SetByDottedPath(GetIsSurveyFull(trigger),
+                              *metadata.is_survey_full);
+  } else {
+    pref_data.RemoveByDottedPath(GetIsSurveyFull(trigger));
+  }
+
+  if (metadata.last_survey_check_time.has_value()) {
+    pref_data.SetByDottedPath(
+        GetLastSurveyCheckTime(trigger),
+        base::TimeToValue(*metadata.last_survey_check_time));
+  } else {
+    pref_data.RemoveByDottedPath(GetLastSurveyCheckTime(trigger));
+  }
+}
+
+void HatsService::GetSurveyMetadataForTesting(
+    HatsService::SurveyMetadata* metadata) const {
+  const std::string& trigger = kHatsSurveyTriggerSettings;
+  ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
+  base::Value::Dict& pref_data = update.Get();
+
+  absl::optional<int> last_major_version =
+      pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
+  if (last_major_version.has_value()) {
+    metadata->last_major_version = last_major_version;
+  }
+
+  absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
+      pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
+  if (last_survey_started_time.has_value()) {
+    metadata->last_survey_started_time = last_survey_started_time;
+  }
+
+  absl::optional<base::Time> any_last_survey_started_time = base::ValueToTime(
+      pref_data.FindByDottedPath(kAnyLastSurveyStartedTimePath));
+  if (any_last_survey_started_time.has_value()) {
+    metadata->any_last_survey_started_time = any_last_survey_started_time;
+  }
+
+  absl::optional<bool> is_survey_full =
+      pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
+  if (is_survey_full.has_value()) {
+    metadata->is_survey_full = is_survey_full;
+  }
+
+  absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
+      pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
+  if (last_survey_check_time.has_value()) {
+    metadata->last_survey_check_time = last_survey_check_time;
+  }
+}
+
+void HatsService::RemoveTask(const DelayedSurveyTask& task) {
+  pending_tasks_.erase(task);
+}
+
+bool HatsService::HasPendingTasks() {
+  return !pending_tasks_.empty();
+}
+
+void HatsService::LaunchSurveyForWebContents(
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  if (ShouldShowSurvey(trigger) && web_contents &&
+      web_contents->GetVisibility() == content::Visibility::VISIBLE) {
+    LaunchSurveyForBrowser(chrome::FindBrowserWithTab(web_contents), trigger,
+                           base::DoNothing(), base::DoNothing(),
+                           product_specific_bits_data,
+                           product_specific_string_data);
+  }
+}
+
+void HatsService::LaunchSurveyForBrowser(
+    Browser* browser,
+    const std::string& trigger,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  if (!browser ||
+      (!browser->is_type_normal() && !browser->is_type_devtools()) ||
+      !profiles::IsRegularOrGuestSession(browser)) {
+    // Never show HaTS bubble for Incognito mode.
+    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                              ShouldShowSurveyReasons::kNoNotRegularBrowser);
+    std::move(failure_callback).Run();
+    return;
+  }
+  if (IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) ==
+      policy::IncognitoModeAvailability::kDisabled) {
+    // Incognito mode needs to be enabled to create an off-the-record profile
+    // for HaTS dialog.
+    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                              ShouldShowSurveyReasons::kNoIncognitoDisabled);
+    std::move(failure_callback).Run();
+    return;
+  }
+  // Checking survey's status could be costly due to a network request, so
+  // we check it at the last.
+  CheckSurveyStatusAndMaybeShow(browser, trigger, std::move(success_callback),
+                                std::move(failure_callback),
+                                product_specific_bits_data,
+                                product_specific_string_data);
+}
+
+bool HatsService::CanShowSurvey(const std::string& trigger) const {
+  // Do not show if a survey dialog already exists.
+  if (hats_next_dialog_exists_) {
+    UMA_HISTOGRAM_ENUMERATION(
+        kHatsShouldShowSurveyReasonHistogram,
+        ShouldShowSurveyReasons::kNoSurveyAlreadyInProgress);
+    return false;
+  }
+
+  // Survey should not be loaded if the corresponding survey config is
+  // unavailable.
+  const auto config_iterator = survey_configs_by_triggers_.find(trigger);
+  if (config_iterator == survey_configs_by_triggers_.end()) {
+    UMA_HISTOGRAM_ENUMERATION(
+        kHatsShouldShowSurveyReasonHistogram,
+        ShouldShowSurveyReasons::kNoTriggerStringMismatch);
+    return false;
+  }
+  const hats::SurveyConfig config = config_iterator->second;
+
+  // Always show the survey in demo mode. This check is duplicated in
+  // CanShowAnySurvey, but because of the semantics of that function, must be
+  // included here.
+  if (base::FeatureList::IsEnabled(
+          features::kHappinessTrackingSurveysForDesktopDemo)) {
+    return true;
+  }
+
+  if (!CanShowAnySurvey(config.user_prompted)) {
+    return false;
+  }
+
+  // Survey can not be loaded and shown if there is no network connection.
+  if (net::NetworkChangeNotifier::IsOffline()) {
+    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                              ShouldShowSurveyReasons::kNoOffline);
+    return false;
+  }
+
+  const base::Value::Dict& pref_data =
+      profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+  absl::optional<int> last_major_version =
+      pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
+  if (last_major_version.has_value() &&
+      static_cast<uint32_t>(*last_major_version) ==
+          version_info::GetVersion().components()[0]) {
+    UMA_HISTOGRAM_ENUMERATION(
+        kHatsShouldShowSurveyReasonHistogram,
+        ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone);
+    return false;
+  }
+
+  if (!config.user_prompted) {
+    absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
+        pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
+    if (last_survey_started_time.has_value()) {
+      base::TimeDelta elapsed_time_since_last_start =
+          base::Time::Now() - *last_survey_started_time;
+      if (elapsed_time_since_last_start < kMinimumTimeBetweenSurveyStarts) {
+        UMA_HISTOGRAM_ENUMERATION(
+            kHatsShouldShowSurveyReasonHistogram,
+            ShouldShowSurveyReasons::kNoLastSurveyTooRecent);
+        return false;
+      }
+    }
+  }
+
+  // If an attempt to check with the HaTS servers whether a survey should be
+  // delivered was made too recently, another survey cannot be shown.
+  absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
+      pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
+  if (last_survey_check_time.has_value()) {
+    base::TimeDelta elapsed_time_since_last_check =
+        base::Time::Now() - *last_survey_check_time;
+    if (elapsed_time_since_last_check < kMinimumTimeBetweenSurveyChecks) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool HatsService::CanShowAnySurvey(bool user_prompted) const {
+  // HaTS requires metrics consent to run. This is also how HaTS can be disabled
+  // by policy.
+  if (!g_browser_process->GetMetricsServicesManager()
+           ->IsMetricsConsentGiven()) {
+    return false;
+  }
+
+  // HaTs can also be disabled by policy if metrics consent is given.
+  if (!profile_->GetPrefs()->GetBoolean(
+          policy::policy_prefs::kFeedbackSurveysEnabled)) {
+    return false;
+  }
+
+  // Surveys can always be shown in Demo mode.
+  if (base::FeatureList::IsEnabled(
+          features::kHappinessTrackingSurveysForDesktopDemo)) {
+    return true;
+  }
+
+  // Do not show surveys if Chrome's last exit was a crash. This avoids
+  // biasing survey results unnecessarily.
+  if (ExitTypeService::GetLastSessionExitType(profile_) == ExitType::kCrashed) {
+    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                              ShouldShowSurveyReasons::kNoLastSessionCrashed);
+    return false;
+  }
+
+  // Some surveys may be "user prompted", which means the user has already been
+  // asked in context if they would like to take a survey (in a less
+  // confrontational manner than the standard HaTS prompt). The bar for whether
+  // a user is eligible is thus lower for these types of surveys.
+  if (!user_prompted) {
+    const base::Value::Dict& pref_data =
+        profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+
+    // If the profile is too new, measured as the age of the profile directory,
+    // the user is ineligible.
+    base::Time now = base::Time::Now();
+    if ((now - profile_->GetCreationTime()) < kMinimumProfileAge) {
+      UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                                ShouldShowSurveyReasons::kNoProfileTooNew);
+      return false;
+    }
+
+    // If a user has received any HaTS survey too recently, they are also
+    // ineligible.
+    absl::optional<base::Time> last_any_started_time =
+        base::ValueToTime(pref_data.Find(kAnyLastSurveyStartedTimePath));
+    if (last_any_started_time.has_value()) {
+      base::TimeDelta elapsed_time_any_started = now - *last_any_started_time;
+      if (elapsed_time_any_started < kMinimumTimeBetweenAnySurveyStarts) {
+        UMA_HISTOGRAM_ENUMERATION(
+            kHatsShouldShowSurveyReasonHistogram,
+            ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent);
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool HatsService::ShouldShowSurvey(const std::string& trigger) const {
+  if (!CanShowSurvey(trigger)) {
+    return false;
+  }
+
+  auto probability = survey_configs_by_triggers_.at(trigger).probability;
+  bool should_show_survey = base::RandDouble() < probability;
+  if (!should_show_survey) {
+    UMA_HISTOGRAM_ENUMERATION(
+        kHatsShouldShowSurveyReasonHistogram,
+        ShouldShowSurveyReasons::kNoBelowProbabilityLimit);
+  }
+
+  return should_show_survey;
+}
+
+void HatsService::CheckSurveyStatusAndMaybeShow(
+    Browser* browser,
+    const std::string& trigger,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  // Check the survey status in profile first.
+  // We record the survey's over capacity information in user profile to avoid
+  // duplicated checks since the survey won't change once it is full.
+  const base::Value::Dict& pref_data =
+      profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+  absl::optional<int> is_full =
+      pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
+  if (is_full.has_value() && is_full) {
+    std::move(failure_callback).Run();
+    return;
+  }
+
+  CHECK(survey_configs_by_triggers_.find(trigger) !=
+        survey_configs_by_triggers_.end());
+  auto survey_config = survey_configs_by_triggers_[trigger];
+
+  // Check that the |product_specific_bits_data| matches the fields for this
+  // trigger. If fields are set for a trigger, they must be provided.
+  CHECK_EQ(product_specific_bits_data.size(),
+           survey_config.product_specific_bits_data_fields.size());
+  for (auto field_value : product_specific_bits_data) {
+    CHECK(base::Contains(survey_config.product_specific_bits_data_fields,
+                         field_value.first));
+  }
+
+  // Check that the |product_specific_string_data| matches the fields for this
+  // trigger. If fields are set for a trigger, they must be provided.
+  CHECK_EQ(product_specific_string_data.size(),
+           survey_config.product_specific_string_data_fields.size());
+  for (auto field_value : product_specific_string_data) {
+    CHECK(base::Contains(survey_config.product_specific_string_data_fields,
+                         field_value.first));
+  }
+
+  // As soon as the HaTS Next dialog is created it will attempt to contact
+  // the HaTS servers to check for a survey.
+  ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
+  update->SetByDottedPath(GetLastSurveyCheckTime(trigger),
+                          base::TimeToValue(base::Time::Now()));
+
+  DCHECK(!hats_next_dialog_exists_);
+  browser->window()->ShowHatsDialog(
+      survey_configs_by_triggers_[trigger].trigger_id,
+      std::move(success_callback), std::move(failure_callback),
+      product_specific_bits_data, product_specific_string_data);
+  hats_next_dialog_exists_ = true;
+}
diff --git a/chrome/browser/ui/hats/hats_service.h b/chrome/browser/ui/hats/hats_service.h
index 381d2b2a..5eba9b9d 100644
--- a/chrome/browser/ui/hats/hats_service.h
+++ b/chrome/browser/ui/hats/hats_service.h
@@ -5,11 +5,13 @@
 #ifndef CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_
 #define CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_
 
+#include <memory>
+#include <set>
 #include <string>
 
 #include "base/containers/flat_map.h"
+#include "base/feature_list.h"
 #include "base/functional/callback.h"
-#include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
@@ -17,7 +19,6 @@
 #include "base/time/time.h"
 #include "chrome/browser/ui/hats/survey_config.h"
 #include "components/keyed_service/core/keyed_service.h"
-#include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -25,8 +26,17 @@
 class WebContents;
 }
 
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class Browser;
 class Profile;
 
+// The name of the histogram which records if a survey was shown, or if not, the
+// reason why not.
+extern const char kHatsShouldShowSurveyReasonHistogram[];
+
 // Key-value mapping type for survey's product specific bits data.
 typedef std::map<std::string, bool> SurveyBitsData;
 
@@ -52,105 +62,200 @@
     absl::optional<base::Time> any_last_survey_started_time;
   };
 
+  class DelayedSurveyTask : public content::WebContentsObserver {
+   public:
+    DelayedSurveyTask(HatsService* hats_service,
+                      const std::string& trigger,
+                      content::WebContents* web_contents,
+                      const SurveyBitsData& product_specific_bits_data,
+                      const SurveyStringData& product_specific_string_data,
+                      bool require_same_origin);
+
+    // Not copyable or movable
+    DelayedSurveyTask(const DelayedSurveyTask&) = delete;
+    DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
+
+    ~DelayedSurveyTask() override;
+
+    // Asks |hats_service_| to launch the survey with id |trigger_| for tab
+    // |web_contents_|.
+    void Launch();
+
+    // content::WebContentsObserver
+    void DidFinishNavigation(
+        content::NavigationHandle* navigation_handle) override;
+    void WebContentsDestroyed() override;
+
+    // Returns a weak pointer to this object.
+    base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
+
+    bool operator<(const HatsService::DelayedSurveyTask& other) const {
+      return trigger_ < other.trigger_ ? true
+                                       : web_contents() < other.web_contents();
+    }
+
+   private:
+    raw_ptr<HatsService> hats_service_;
+    std::string trigger_;
+    SurveyBitsData product_specific_bits_data_;
+    SurveyStringData product_specific_string_data_;
+    bool require_same_origin_;
+    base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
+  };
+
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class ShouldShowSurveyReasons {
+    kYes = 0,
+    kNoOffline = 1,
+    kNoLastSessionCrashed = 2,
+    kNoReceivedSurveyInCurrentMilestone = 3,
+    kNoProfileTooNew = 4,
+    kNoLastSurveyTooRecent = 5,
+    kNoBelowProbabilityLimit = 6,
+    kNoTriggerStringMismatch = 7,
+    kNoNotRegularBrowser = 8,
+    kNoIncognitoDisabled = 9,
+    kNoCookiesBlocked = 10,            // Unused.
+    kNoThirdPartyCookiesBlocked = 11,  // Unused.
+    kNoSurveyUnreachable = 12,
+    kNoSurveyOverCapacity = 13,
+    kNoSurveyAlreadyInProgress = 14,
+    kNoAnyLastSurveyTooRecent = 15,
+    kNoRejectedByHatsService = 16,
+    kMaxValue = kNoRejectedByHatsService,
+  };
+
   explicit HatsService(Profile* profile);
+
   HatsService(const HatsService&) = delete;
   HatsService& operator=(const HatsService&) = delete;
 
   ~HatsService() override;
 
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
   // Launches survey with identifier |trigger| if appropriate.
   // |success_callback| is called when the survey is shown to the user.
   // |failure_callback| is called if the survey does not launch for any reason.
   // |product_specific_bits_data| and |product_specific_string_data| must
   // contain key-value pairs where the keys match the field names set for the
-  // survey in survey_config.cc, and the values are those which will be
-  // associated with the survey response.
+  // survey in hats_service.cc, and the values are those which will be
+  // associated with the survey response. Field's matches are CHECK enforced.
   virtual void LaunchSurvey(
       const std::string& trigger,
       base::OnceClosure success_callback = base::DoNothing(),
       base::OnceClosure failure_callback = base::DoNothing(),
       const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {}) = 0;
+      const SurveyStringData& product_specific_string_data = {});
 
   // Launches survey (with id |trigger|) with a timeout |timeout_ms| if
-  // appropriate.
-  // |product_specific_bits_data| and |product_specific_string_data| must
-  // contain key-value pairs where the keys match the field names set for the
-  // survey in survey_config.cc, and the values are those which will be
-  // associated with the survey response.
-  // |web_contents| specifies the `WebContents` where the survey should be
-  // displayed. Returns false if the underlying task posting fails.
-  virtual void LaunchSurveyForWebContents(
-      const std::string& trigger,
-      content::WebContents* web_contents,
-      const SurveyBitsData& product_specific_bits_data,
-      const SurveyStringData& product_specific_string_data,
-      base::OnceClosure success_callback = base::DoNothing(),
-      base::OnceClosure failure_callback = base::DoNothing()) = 0;
-
-  // Launches survey (with id |trigger|) with a timeout |timeout_ms| if
-  // appropriate.
-  // |product_specific_bits_data| and |product_specific_string_data| must
-  // contain key-value pairs where the keys match the field names set for the
-  // survey in survey_config.cc, and the values are those which will be
-  // associated with the survey response.
+  // appropriate. Survey will be shown at the active window/tab by the
+  // time of launching. Rejects (and returns false) if the underlying task
+  // posting fails.
   virtual bool LaunchDelayedSurvey(
       const std::string& trigger,
       int timeout_ms,
       const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {}) = 0;
+      const SurveyStringData& product_specific_string_data = {});
 
   // Launches survey (with id |trigger|) with a timeout |timeout_ms| for tab
   // |web_contents| if appropriate. |web_contents| required to be non-nullptr.
-  // Launch is cancelled if |web_contents| killed before end of timeout.
+  // Launch is cancelled if |web_contents| killed before end of timeout. Launch
+  // is also cancelled if |web_contents| not visible at the time of launch.
   // Rejects (and returns false) if there is already an identical delayed-task
   // (same |trigger| and same |web_contents|) waiting to be fulfilled. Also
-  // rejects if the underlying task posting fails.
-  // If |require_same_origin| is set, additionally requires that |web_contents|
-  // remain on the same origin.
-  // |success_callback| is called when the survey is shown to the user.
-  // |failure_callback| is called if the survey does not launch for any reason.
+  // rejects if the underlying task posting fails. If |require_same_origin| is
+  // set, additionally requires that |web_contents| remain on the same origin.
   virtual bool LaunchDelayedSurveyForWebContents(
       const std::string& trigger,
       content::WebContents* web_contents,
       int timeout_ms,
       const SurveyBitsData& product_specific_bits_data = {},
       const SurveyStringData& product_specific_string_data = {},
-      bool require_same_origin = false,
-      base::OnceClosure success_callback = base::DoNothing(),
-      base::OnceClosure failure_callback = base::DoNothing()) = 0;
+      bool require_same_origin = false);
 
-  // Whether the user is eligible for any survey (of the type |user_prompted|
-  // or not) to be shown. A return value of false is always a true-negative,
-  // and means the user is currently ineligible for all surveys. A return value
-  // of true should not be interpreted as a guarantee that requests to show a
-  // survey will succeed.
-  virtual bool CanShowAnySurvey(bool user_prompted) const = 0;
+  // Updates the user preferences to record that the survey associated with
+  // |survey_id| was shown to the user. |trigger_id| is the HaTS next Trigger
+  // ID for the survey.
+  void RecordSurveyAsShown(std::string trigger_id);
+
+  // Indicates to the service that the HaTS Next dialog has been closed.
+  // Virtual to allow mocking in tests.
+  virtual void HatsNextDialogClosed();
+
+  void SetSurveyMetadataForTesting(const SurveyMetadata& metadata);
+  void GetSurveyMetadataForTesting(HatsService::SurveyMetadata* metadata) const;
+  bool HasPendingTasks();
 
   // Whether the survey specified by |trigger| can be shown to the user. This
   // is a pre-check that calculates as many conditions as possible, but could
   // still return a false positive due to client-side rate limiting, a change
   // in network conditions, or intervening calls to this API.
-  virtual bool CanShowSurvey(const std::string& trigger) const = 0;
+  bool CanShowSurvey(const std::string& trigger) const;
 
-  // Updates the user preferences to record that the survey associated with
-  // |survey_id| was shown to the user. |trigger_id| is the HaTS next Trigger
-  // ID for the survey.
-  virtual void RecordSurveyAsShown(std::string trigger_id) = 0;
+  // Whether the user is eligible for any survey (of the type |user_prompted|
+  // or not) to be shown. A return value of false is always a true-negative, and
+  // means the user is currently ineligible for all surveys. A return value of
+  // true should not be interpreted as a guarantee that requests to show a
+  // survey will succeed. Virtual to allow mocking in tests.
+  virtual bool CanShowAnySurvey(bool user_prompted) const;
 
- protected:
-  hats::SurveyConfigs survey_configs_by_triggers_;
-  using SurveyConfigs = base::flat_map<std::string, hats::SurveyConfig>;
-
-  Profile* profile() const { return profile_; }
+  // Returns whether a HaTS Next dialog currently exists, regardless of whether
+  // it is being shown or not.
+  bool hats_next_dialog_exists_for_testing() {
+    return hats_next_dialog_exists_;
+  }
 
  private:
   friend class DelayedSurveyTask;
   FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
 
+  using SurveyConfigs = base::flat_map<std::string, hats::SurveyConfig>;
+
+  void LaunchSurveyForWebContents(
+      const std::string& trigger,
+      content::WebContents* web_contents,
+      const SurveyBitsData& product_specific_bits_data,
+      const SurveyStringData& product_specific_string_data);
+
+  void LaunchSurveyForBrowser(
+      Browser* browser,
+      const std::string& trigger,
+      base::OnceClosure success_callback,
+      base::OnceClosure failure_callback,
+      const SurveyBitsData& product_specific_bits_data,
+      const SurveyStringData& product_specific_string_data);
+
+  // Returns true is the survey trigger specified should be shown.
+  bool ShouldShowSurvey(const std::string& trigger) const;
+
+  // Check whether the survey is reachable and under capacity and show it.
+  // |success_callback| is called when the survey is shown to the user.
+  // |failure_callback| is called if the survey does not launch for any reason.
+  // The matches of field names with the `SurveyConfig` are CHECK enforced.
+  void CheckSurveyStatusAndMaybeShow(
+      Browser* browser,
+      const std::string& trigger,
+      base::OnceClosure success_callback,
+      base::OnceClosure failure_callback,
+      const SurveyBitsData& product_specific_bits_data,
+      const SurveyStringData& product_specific_string_data);
+
+  // Remove |task| from the set of |pending_tasks_|.
+  void RemoveTask(const DelayedSurveyTask& task);
+
   // Profile associated with this service.
   const raw_ptr<Profile> profile_;
 
+  std::set<DelayedSurveyTask> pending_tasks_;
+
+  SurveyConfigs survey_configs_by_triggers_;
+
+  // Whether a HaTS Next dialog currently exists (regardless of whether it
+  // is being shown to the user).
+  bool hats_next_dialog_exists_ = false;
+
   base::WeakPtrFactory<HatsService> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/hats/hats_service_browsertest.cc b/chrome/browser/ui/hats/hats_service_browsertest.cc
index 12e84b4..8843038 100644
--- a/chrome/browser/ui/hats/hats_service_browsertest.cc
+++ b/chrome/browser/ui/hats/hats_service_browsertest.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/profiles/profile_impl.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
+#include "chrome/browser/ui/hats/hats_service.h"
 #include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_features.h"
@@ -80,9 +80,9 @@
 
   ~HatsServiceBrowserTestBase() override = default;
 
-  HatsServiceDesktop* GetHatsService() {
-    HatsServiceDesktop* service = static_cast<HatsServiceDesktop*>(
-        HatsServiceFactory::GetForProfile(browser()->profile(), true));
+  HatsService* GetHatsService() {
+    HatsService* service =
+        HatsServiceFactory::GetForProfile(browser()->profile(), true);
     return service;
   }
 
@@ -218,21 +218,20 @@
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, SameMajorVersionNoShow) {
   SetMetricsConsent(true);
   base::HistogramTester histogram_tester;
-  HatsServiceDesktop::SurveyMetadata metadata;
+  HatsService::SurveyMetadata metadata;
   metadata.last_major_version = version_info::GetVersion().components()[0];
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsServiceDesktop::ShouldShowSurveyReasons::
-          kNoReceivedSurveyInCurrentMilestone,
+      HatsService::ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone,
       1);
   EXPECT_FALSE(HatsNextDialogCreated());
 }
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, DifferentMajorVersionShow) {
   SetMetricsConsent(true);
-  HatsServiceDesktop::SurveyMetadata metadata;
+  HatsService::SurveyMetadata metadata;
   metadata.last_major_version = 42;
   ASSERT_NE(42u, version_info::GetVersion().components()[0]);
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
@@ -244,13 +243,13 @@
                        SurveyStartedBeforeRequiredElapsedTimeNoShow) {
   SetMetricsConsent(true);
   base::HistogramTester histogram_tester;
-  HatsServiceDesktop::SurveyMetadata metadata;
+  HatsService::SurveyMetadata metadata;
   metadata.last_survey_started_time = base::Time::Now();
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsServiceDesktop::ShouldShowSurveyReasons::kNoLastSurveyTooRecent, 1);
+      HatsService::ShouldShowSurveyReasons::kNoLastSurveyTooRecent, 1);
   EXPECT_FALSE(HatsNextDialogCreated());
 }
 
@@ -258,15 +257,14 @@
                        SurveyStartedBeforeElapsedTimeBetweenAnySurveys) {
   SetMetricsConsent(true);
   base::HistogramTester histogram_tester;
-  HatsServiceDesktop::SurveyMetadata metadata;
+  HatsService::SurveyMetadata metadata;
   metadata.any_last_survey_started_time = base::Time::Now();
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
   EXPECT_FALSE(HatsNextDialogCreated());
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsServiceDesktop::ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent,
-      1);
+      HatsService::ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, ProfileTooYoungToShow) {
@@ -278,7 +276,7 @@
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsServiceDesktop::ShouldShowSurveyReasons::kNoProfileTooNew, 1);
+      HatsService::ShouldShowSurveyReasons::kNoProfileTooNew, 1);
   EXPECT_FALSE(HatsNextDialogCreated());
 }
 
@@ -307,7 +305,7 @@
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, CheckedWithinADayNoShow) {
   SetMetricsConsent(true);
-  HatsServiceDesktop::SurveyMetadata metadata;
+  HatsService::SurveyMetadata metadata;
   metadata.last_survey_check_time = base::Time::Now() - base::Hours(23);
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -316,7 +314,7 @@
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, CheckedAfterADayToShow) {
   SetMetricsConsent(true);
-  HatsServiceDesktop::SurveyMetadata metadata;
+  HatsService::SurveyMetadata metadata;
   metadata.last_survey_check_time = base::Time::Now() - base::Days(1);
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -325,7 +323,7 @@
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, SurveyAlreadyFullNoShow) {
   SetMetricsConsent(true);
-  HatsServiceDesktop::SurveyMetadata metadata;
+  HatsService::SurveyMetadata metadata;
   metadata.is_survey_full = true;
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -516,7 +514,7 @@
 
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
 
-  HatsServiceDesktop::SurveyMetadata metadata;
+  HatsService::SurveyMetadata metadata;
   GetHatsService()->GetSurveyMetadataForTesting(&metadata);
   EXPECT_TRUE(metadata.last_survey_check_time.has_value());
 }
diff --git a/chrome/browser/ui/hats/hats_service_desktop.cc b/chrome/browser/ui/hats/hats_service_desktop.cc
deleted file mode 100644
index e56b76f..0000000
--- a/chrome/browser/ui/hats/hats_service_desktop.cc
+++ /dev/null
@@ -1,637 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/containers/contains.h"
-#include "base/feature_list.h"
-#include "base/functional/bind.h"
-#include "base/functional/callback_helpers.h"
-#include "base/json/values_util.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/rand_util.h"
-#include "base/ranges/algorithm.h"
-#include "base/task/sequenced_task_runner.h"
-#include "base/values.h"
-#include "base/version.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/prefs/incognito_mode_prefs.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profiles_state.h"
-#include "chrome/browser/sessions/exit_type_service.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/common/pref_names.h"
-#include "components/metrics_services_manager/metrics_services_manager.h"
-#include "components/policy/core/common/policy_pref_names.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/scoped_user_pref_update.h"
-#include "components/version_info/version_info.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-#include "net/base/network_change_notifier.h"
-
-constexpr char kHatsShouldShowSurveyReasonHistogram[] =
-    "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReason";
-
-namespace {
-
-// TODO(crbug.com/1160661): When the minimum time between any survey, and the
-// minimum time between a specific survey, are the same, the logic supporting
-// the latter check is superfluous.
-constexpr base::TimeDelta kMinimumTimeBetweenSurveyStarts = base::Days(180);
-
-constexpr base::TimeDelta kMinimumTimeBetweenAnySurveyStarts = base::Days(180);
-
-constexpr base::TimeDelta kMinimumTimeBetweenSurveyChecks = base::Days(1);
-
-constexpr base::TimeDelta kMinimumProfileAge = base::Days(30);
-
-// Preferences Data Model
-// The kHatsSurveyMetadata pref points to a dictionary.
-// The valid keys and value types for this dictionary are as follows:
-// [trigger].last_major_version        ---> Integer
-// [trigger].last_survey_started_time  ---> Time
-// [trigger].is_survey_full            ---> Bool
-// [trigger].last_survey_check_time    ---> Time
-// any_last_survey_started_time        ---> Time
-
-std::string GetMajorVersionPath(const std::string& trigger) {
-  return trigger + ".last_major_version";
-}
-
-std::string GetLastSurveyStartedTime(const std::string& trigger) {
-  return trigger + ".last_survey_started_time";
-}
-
-std::string GetIsSurveyFull(const std::string& trigger) {
-  return trigger + ".is_survey_full";
-}
-
-std::string GetLastSurveyCheckTime(const std::string& trigger) {
-  return trigger + ".last_survey_check_time";
-}
-
-constexpr char kAnyLastSurveyStartedTimePath[] = "any_last_survey_started_time";
-}  // namespace
-
-HatsServiceDesktop::DelayedSurveyTask::DelayedSurveyTask(
-    HatsServiceDesktop* hats_service,
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data,
-    bool require_same_origin,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback)
-    : hats_service_(hats_service),
-      trigger_(trigger),
-      product_specific_bits_data_(product_specific_bits_data),
-      product_specific_string_data_(product_specific_string_data),
-      require_same_origin_(require_same_origin),
-      success_callback_(std::move(success_callback)),
-      failure_callback_(std::move(failure_callback)) {
-  Observe(web_contents);
-}
-
-HatsServiceDesktop::DelayedSurveyTask::~DelayedSurveyTask() = default;
-
-base::WeakPtr<HatsServiceDesktop::DelayedSurveyTask>
-HatsServiceDesktop::DelayedSurveyTask::GetWeakPtr() {
-  return weak_ptr_factory_.GetWeakPtr();
-}
-
-void HatsServiceDesktop::DelayedSurveyTask::Launch() {
-  CHECK(web_contents());
-  if (web_contents() &&
-      web_contents()->GetVisibility() == content::Visibility::VISIBLE) {
-    hats_service_->LaunchSurveyForWebContents(trigger_, web_contents(),
-                                              product_specific_bits_data_,
-                                              product_specific_string_data_);
-    hats_service_->RemoveTask(*this);
-  }
-}
-
-void HatsServiceDesktop::DelayedSurveyTask::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
-  if (!require_same_origin_ || !navigation_handle ||
-      !navigation_handle->IsInPrimaryMainFrame() ||
-      navigation_handle->IsSameDocument() ||
-      (navigation_handle->HasCommitted() &&
-       navigation_handle->IsSameOrigin())) {
-    return;
-  }
-
-  if (!failure_callback_.is_null()) {
-    std::move(failure_callback_).Run();
-  }
-  hats_service_->RemoveTask(*this);
-}
-
-void HatsServiceDesktop::DelayedSurveyTask::WebContentsDestroyed() {
-  if (!failure_callback_.is_null()) {
-    std::move(failure_callback_).Run();
-  }
-  hats_service_->RemoveTask(*this);
-}
-
-HatsServiceDesktop::HatsServiceDesktop(Profile* profile)
-    : HatsService(profile) {}
-
-HatsServiceDesktop::~HatsServiceDesktop() = default;
-
-// static
-void HatsServiceDesktop::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* registry) {
-  registry->RegisterDictionaryPref(
-      prefs::kHatsSurveyMetadata,
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-}
-
-void HatsServiceDesktop::LaunchSurvey(
-    const std::string& trigger,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  if (!ShouldShowSurvey(trigger)) {
-    if (!failure_callback.is_null()) {
-      std::move(failure_callback).Run();
-    }
-    return;
-  }
-  LaunchSurveyForBrowser(
-      chrome::FindLastActiveWithProfile(profile()), trigger,
-      std::move(success_callback), std::move(failure_callback),
-      product_specific_bits_data, product_specific_string_data);
-}
-
-void HatsServiceDesktop::LaunchSurveyForWebContents(
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback) {
-  if (ShouldShowSurvey(trigger) && web_contents &&
-      web_contents->GetVisibility() == content::Visibility::VISIBLE) {
-    LaunchSurveyForBrowser(
-        chrome::FindBrowserWithTab(web_contents), trigger,
-        std::move(success_callback), std::move(failure_callback),
-        product_specific_bits_data, product_specific_string_data);
-  }
-}
-
-bool HatsServiceDesktop::LaunchDelayedSurvey(
-    const std::string& trigger,
-    int timeout_ms,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  return base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&HatsServiceDesktop::LaunchSurvey,
-                     weak_ptr_factory_.GetWeakPtr(), trigger, base::DoNothing(),
-                     base::DoNothing(), product_specific_bits_data,
-                     product_specific_string_data),
-      base::Milliseconds(timeout_ms));
-}
-
-bool HatsServiceDesktop::LaunchDelayedSurveyForWebContents(
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    int timeout_ms,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data,
-    bool require_same_origin,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (survey_configs_by_triggers_.find(trigger) ==
-      survey_configs_by_triggers_.end()) {
-    // Survey configuration is not available.
-    if (!failure_callback.is_null()) {
-      std::move(failure_callback).Run();
-    }
-    return false;
-  }
-  if (!web_contents) {
-    if (!failure_callback.is_null()) {
-      std::move(failure_callback).Run();
-    }
-    return false;
-  }
-  auto result = pending_tasks_.emplace(
-      this, trigger, web_contents, product_specific_bits_data,
-      product_specific_string_data, require_same_origin,
-      std::move(success_callback), std::move(failure_callback));
-  if (!result.second) {
-    return false;
-  }
-  auto success =
-      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-          FROM_HERE,
-          base::BindOnce(&HatsServiceDesktop::DelayedSurveyTask::Launch,
-                         const_cast<HatsServiceDesktop::DelayedSurveyTask&>(
-                             *(result.first))
-                             .GetWeakPtr()),
-          base::Milliseconds(timeout_ms));
-  if (!success) {
-    pending_tasks_.erase(result.first);
-  }
-  return success;
-}
-
-void HatsServiceDesktop::SetSurveyMetadataForTesting(
-    const HatsService::SurveyMetadata& metadata) {
-  const std::string& trigger = kHatsSurveyTriggerSettings;
-  ScopedDictPrefUpdate update(profile()->GetPrefs(),
-                              prefs::kHatsSurveyMetadata);
-  base::Value::Dict& pref_data = update.Get();
-  if (!metadata.last_major_version.has_value() &&
-      !metadata.last_survey_started_time.has_value() &&
-      !metadata.is_survey_full.has_value() &&
-      !metadata.last_survey_check_time.has_value()) {
-    pref_data.RemoveByDottedPath(trigger);
-  }
-
-  if (metadata.last_major_version.has_value()) {
-    pref_data.SetByDottedPath(GetMajorVersionPath(trigger),
-                              *metadata.last_major_version);
-  } else {
-    pref_data.RemoveByDottedPath(GetMajorVersionPath(trigger));
-  }
-
-  if (metadata.last_survey_started_time.has_value()) {
-    pref_data.SetByDottedPath(
-        GetLastSurveyStartedTime(trigger),
-        base::TimeToValue(*metadata.last_survey_started_time));
-  } else {
-    pref_data.RemoveByDottedPath(GetLastSurveyStartedTime(trigger));
-  }
-
-  if (metadata.any_last_survey_started_time.has_value()) {
-    pref_data.SetByDottedPath(
-        kAnyLastSurveyStartedTimePath,
-        base::TimeToValue(*metadata.any_last_survey_started_time));
-  } else {
-    pref_data.RemoveByDottedPath(kAnyLastSurveyStartedTimePath);
-  }
-
-  if (metadata.is_survey_full.has_value()) {
-    pref_data.SetByDottedPath(GetIsSurveyFull(trigger),
-                              *metadata.is_survey_full);
-  } else {
-    pref_data.RemoveByDottedPath(GetIsSurveyFull(trigger));
-  }
-
-  if (metadata.last_survey_check_time.has_value()) {
-    pref_data.SetByDottedPath(
-        GetLastSurveyCheckTime(trigger),
-        base::TimeToValue(*metadata.last_survey_check_time));
-  } else {
-    pref_data.RemoveByDottedPath(GetLastSurveyCheckTime(trigger));
-  }
-}
-
-void HatsServiceDesktop::GetSurveyMetadataForTesting(
-    HatsService::SurveyMetadata* metadata) const {
-  const std::string& trigger = kHatsSurveyTriggerSettings;
-  ScopedDictPrefUpdate update(profile()->GetPrefs(),
-                              prefs::kHatsSurveyMetadata);
-  base::Value::Dict& pref_data = update.Get();
-
-  absl::optional<int> last_major_version =
-      pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
-  if (last_major_version.has_value()) {
-    metadata->last_major_version = last_major_version;
-  }
-
-  absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
-      pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
-  if (last_survey_started_time.has_value()) {
-    metadata->last_survey_started_time = last_survey_started_time;
-  }
-
-  absl::optional<base::Time> any_last_survey_started_time = base::ValueToTime(
-      pref_data.FindByDottedPath(kAnyLastSurveyStartedTimePath));
-  if (any_last_survey_started_time.has_value()) {
-    metadata->any_last_survey_started_time = any_last_survey_started_time;
-  }
-
-  absl::optional<bool> is_survey_full =
-      pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
-  if (is_survey_full.has_value()) {
-    metadata->is_survey_full = is_survey_full;
-  }
-
-  absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
-      pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
-  if (last_survey_check_time.has_value()) {
-    metadata->last_survey_check_time = last_survey_check_time;
-  }
-}
-
-bool HatsServiceDesktop::HasPendingTasks() {
-  return !pending_tasks_.empty();
-}
-
-bool HatsServiceDesktop::CanShowSurvey(const std::string& trigger) const {
-  // Survey should not be loaded if the corresponding survey config is
-  // unavailable.
-  const auto config_iterator = survey_configs_by_triggers_.find(trigger);
-
-  if (config_iterator == survey_configs_by_triggers_.end()) {
-    UMA_HISTOGRAM_ENUMERATION(
-        kHatsShouldShowSurveyReasonHistogram,
-        ShouldShowSurveyReasons::kNoTriggerStringMismatch);
-    return false;
-  }
-  const hats::SurveyConfig config = config_iterator->second;
-
-  // Do not show if a survey dialog already exists.
-  if (hats_next_dialog_exists_) {
-    UMA_HISTOGRAM_ENUMERATION(
-        kHatsShouldShowSurveyReasonHistogram,
-        ShouldShowSurveyReasons::kNoSurveyAlreadyInProgress);
-    return false;
-  }
-
-  // Always show the survey in demo mode. This check is duplicated in
-  // CanShowAnySurvey, but because of the semantics of that function, must be
-  // included here.
-  if (base::FeatureList::IsEnabled(
-          features::kHappinessTrackingSurveysForDesktopDemo)) {
-    return true;
-  }
-
-  if (!CanShowAnySurvey(config.user_prompted)) {
-    return false;
-  }
-
-  // Survey can not be loaded and shown if there is no network connection.
-  if (net::NetworkChangeNotifier::IsOffline()) {
-    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                              ShouldShowSurveyReasons::kNoOffline);
-    return false;
-  }
-
-  const base::Value::Dict& pref_data =
-      profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
-  absl::optional<int> last_major_version =
-      pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
-  if (last_major_version.has_value() &&
-      static_cast<uint32_t>(*last_major_version) ==
-          version_info::GetVersion().components()[0]) {
-    UMA_HISTOGRAM_ENUMERATION(
-        kHatsShouldShowSurveyReasonHistogram,
-        ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone);
-    return false;
-  }
-
-  if (!config.user_prompted) {
-    absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
-        pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
-    if (last_survey_started_time.has_value()) {
-      base::TimeDelta elapsed_time_since_last_start =
-          base::Time::Now() - *last_survey_started_time;
-      if (elapsed_time_since_last_start < kMinimumTimeBetweenSurveyStarts) {
-        UMA_HISTOGRAM_ENUMERATION(
-            kHatsShouldShowSurveyReasonHistogram,
-            ShouldShowSurveyReasons::kNoLastSurveyTooRecent);
-        return false;
-      }
-    }
-  }
-
-  // If an attempt to check with the HaTS servers whether a survey should be
-  // delivered was made too recently, another survey cannot be shown.
-  absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
-      pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
-  if (last_survey_check_time.has_value()) {
-    base::TimeDelta elapsed_time_since_last_check =
-        base::Time::Now() - *last_survey_check_time;
-    if (elapsed_time_since_last_check < kMinimumTimeBetweenSurveyChecks) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool HatsServiceDesktop::CanShowAnySurvey(bool user_prompted) const {
-  // HaTS requires metrics consent to run. This is also how HaTS can be
-  // disabled by policy.
-  if (!g_browser_process->GetMetricsServicesManager()
-           ->IsMetricsConsentGiven()) {
-    return false;
-  }
-
-  // HaTs can also be disabled by policy if metrics consent is given.
-  if (!profile()->GetPrefs()->GetBoolean(
-          policy::policy_prefs::kFeedbackSurveysEnabled)) {
-    return false;
-  }
-
-  // Surveys can always be shown in Demo mode.
-  if (base::FeatureList::IsEnabled(
-          features::kHappinessTrackingSurveysForDesktopDemo)) {
-    return true;
-  }
-
-  // Do not show surveys if Chrome's last exit was a crash. This avoids
-  // biasing survey results unnecessarily.
-  if (ExitTypeService::GetLastSessionExitType(profile()) ==
-      ExitType::kCrashed) {
-    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                              ShouldShowSurveyReasons::kNoLastSessionCrashed);
-    return false;
-  }
-
-  // Some surveys may be "user prompted", which means the user has already
-  // been asked in context if they would like to take a survey (in a less
-  // confrontational manner than the standard HaTS prompt). The bar for
-  // whether a user is eligible is thus lower for these types of surveys.
-  if (!user_prompted) {
-    const base::Value::Dict& pref_data =
-        profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
-
-    // If the profile is too new, measured as the age of the profile
-    // directory, the user is ineligible.
-    base::Time now = base::Time::Now();
-    if ((now - profile()->GetCreationTime()) < kMinimumProfileAge) {
-      UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                                ShouldShowSurveyReasons::kNoProfileTooNew);
-      return false;
-    }
-
-    // If a user has received any HaTS survey too recently, they are also
-    // ineligible.
-    absl::optional<base::Time> last_any_started_time =
-        base::ValueToTime(pref_data.Find(kAnyLastSurveyStartedTimePath));
-    if (last_any_started_time.has_value()) {
-      base::TimeDelta elapsed_time_any_started = now - *last_any_started_time;
-      if (elapsed_time_any_started < kMinimumTimeBetweenAnySurveyStarts) {
-        UMA_HISTOGRAM_ENUMERATION(
-            kHatsShouldShowSurveyReasonHistogram,
-            ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent);
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-void HatsServiceDesktop::RecordSurveyAsShown(std::string trigger_id) {
-  // Record the trigger associated with the trigger_id. This is recorded
-  // instead of the trigger ID itself, as the ID is specific to individual
-  // survey versions. There should be a cooldown before a user is prompted to
-  // take a survey from the same trigger, regardless of whether the survey was
-  // updated.
-  auto trigger_survey_config =
-      base::ranges::find(survey_configs_by_triggers_, trigger_id,
-                         [](const SurveyConfigs::value_type& pair) {
-                           return pair.second.trigger_id;
-                         });
-
-  DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
-  std::string trigger = trigger_survey_config->first;
-
-  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                            ShouldShowSurveyReasons::kYes);
-
-  ScopedDictPrefUpdate update(profile()->GetPrefs(),
-                              prefs::kHatsSurveyMetadata);
-  base::Value::Dict& pref_data = update.Get();
-  pref_data.SetByDottedPath(
-      GetMajorVersionPath(trigger),
-      static_cast<int>(version_info::GetVersion().components()[0]));
-  pref_data.SetByDottedPath(GetLastSurveyStartedTime(trigger),
-                            base::TimeToValue(base::Time::Now()));
-  pref_data.SetByDottedPath(kAnyLastSurveyStartedTimePath,
-                            base::TimeToValue(base::Time::Now()));
-}
-
-void HatsServiceDesktop::HatsNextDialogClosed() {
-  hats_next_dialog_exists_ = false;
-}
-
-void HatsServiceDesktop::RemoveTask(const DelayedSurveyTask& task) {
-  pending_tasks_.erase(task);
-}
-
-bool HatsServiceDesktop::ShouldShowSurvey(const std::string& trigger) const {
-  if (!CanShowSurvey(trigger)) {
-    return false;
-  }
-
-  auto probability = survey_configs_by_triggers_.at(trigger).probability;
-  bool should_show_survey = base::RandDouble() < probability;
-  if (!should_show_survey) {
-    UMA_HISTOGRAM_ENUMERATION(
-        kHatsShouldShowSurveyReasonHistogram,
-        ShouldShowSurveyReasons::kNoBelowProbabilityLimit);
-  }
-
-  return should_show_survey;
-}
-
-void HatsServiceDesktop::LaunchSurveyForBrowser(
-    Browser* browser,
-    const std::string& trigger,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  if (!browser ||
-      (!browser->is_type_normal() && !browser->is_type_devtools()) ||
-      !profiles::IsRegularOrGuestSession(browser)) {
-    // Never show HaTS bubble for Incognito mode.
-    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                              ShouldShowSurveyReasons::kNoNotRegularBrowser);
-    if (!failure_callback.is_null()) {
-      std::move(failure_callback).Run();
-    }
-    return;
-  }
-  if (IncognitoModePrefs::GetAvailability(profile()->GetPrefs()) ==
-      policy::IncognitoModeAvailability::kDisabled) {
-    // Incognito mode needs to be enabled to create an off-the-record profile
-    // for HaTS dialog.
-    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                              ShouldShowSurveyReasons::kNoIncognitoDisabled);
-    if (!failure_callback.is_null()) {
-      std::move(failure_callback).Run();
-    }
-    return;
-  }
-  // Checking survey's status could be costly due to a network request, so
-  // we check it at the last.
-  CheckSurveyStatusAndMaybeShow(browser, trigger, std::move(success_callback),
-                                std::move(failure_callback),
-                                product_specific_bits_data,
-                                product_specific_string_data);
-}
-
-void HatsServiceDesktop::CheckSurveyStatusAndMaybeShow(
-    Browser* browser,
-    const std::string& trigger,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  // Check the survey status in profile first.
-  // We record the survey's over capacity information in user profile to avoid
-  // duplicated checks since the survey won't change once it is full.
-  const base::Value::Dict& pref_data =
-      profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
-  absl::optional<int> is_full =
-      pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
-  if (is_full.has_value() && is_full) {
-    if (!failure_callback.is_null()) {
-      std::move(failure_callback).Run();
-    }
-    return;
-  }
-
-  CHECK(survey_configs_by_triggers_.find(trigger) !=
-        survey_configs_by_triggers_.end());
-  auto survey_config = survey_configs_by_triggers_[trigger];
-
-  // Check that the |product_specific_bits_data| matches the fields for this
-  // trigger. If fields are set for a trigger, they must be provided.
-  CHECK_EQ(product_specific_bits_data.size(),
-           survey_config.product_specific_bits_data_fields.size());
-  for (auto field_value : product_specific_bits_data) {
-    CHECK(base::Contains(survey_config.product_specific_bits_data_fields,
-                         field_value.first));
-  }
-
-  // Check that the |product_specific_string_data| matches the fields for this
-  // trigger. If fields are set for a trigger, they must be provided.
-  CHECK_EQ(product_specific_string_data.size(),
-           survey_config.product_specific_string_data_fields.size());
-  for (auto field_value : product_specific_string_data) {
-    CHECK(base::Contains(survey_config.product_specific_string_data_fields,
-                         field_value.first));
-  }
-
-  // As soon as the HaTS Next dialog is created it will attempt to contact
-  // the HaTS servers to check for a survey.
-  ScopedDictPrefUpdate update(profile()->GetPrefs(),
-                              prefs::kHatsSurveyMetadata);
-  update->SetByDottedPath(GetLastSurveyCheckTime(trigger),
-                          base::TimeToValue(base::Time::Now()));
-
-  DCHECK(!hats_next_dialog_exists_);
-  browser->window()->ShowHatsDialog(
-      survey_configs_by_triggers_[trigger].trigger_id,
-      std::move(success_callback), std::move(failure_callback),
-      product_specific_bits_data, product_specific_string_data);
-  hats_next_dialog_exists_ = true;
-}
diff --git a/chrome/browser/ui/hats/hats_service_desktop.h b/chrome/browser/ui/hats/hats_service_desktop.h
deleted file mode 100644
index a8824b0..0000000
--- a/chrome/browser/ui/hats/hats_service_desktop.h
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
-#define CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
-
-#include <set>
-#include <string>
-
-#include "base/functional/callback.h"
-#include "base/functional/callback_forward.h"
-#include "base/functional/callback_helpers.h"
-#include "base/gtest_prod_util.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "content/public/browser/web_contents_observer.h"
-
-class Browser;
-
-// Key-value mapping type for survey's product specific bits data.
-typedef std::map<std::string, bool> SurveyBitsData;
-
-// Key-value mapping type for survey's product specific string data.
-typedef std::map<std::string, std::string> SurveyStringData;
-
-// The name of the histogram which records if a survey was shown, or if not, the
-// reason why not.
-extern const char kHatsShouldShowSurveyReasonHistogram[];
-
-// This class provides the client side logic for determining if a
-// survey should be shown for any trigger based on input from a finch
-// configuration. It is created on a per profile basis.
-class HatsServiceDesktop : public HatsService {
- public:
-  class DelayedSurveyTask : public content::WebContentsObserver {
-   public:
-    DelayedSurveyTask(HatsServiceDesktop* hats_service,
-                      const std::string& trigger,
-                      content::WebContents* web_contents,
-                      const SurveyBitsData& product_specific_bits_data,
-                      const SurveyStringData& product_specific_string_data,
-                      bool require_same_origin,
-                      base::OnceClosure success_callback,
-                      base::OnceClosure failure_callback);
-
-    // Not copyable or movable
-    DelayedSurveyTask(const DelayedSurveyTask&) = delete;
-    DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
-
-    ~DelayedSurveyTask() override;
-
-    // Asks |hats_service_| to launch the survey with id |trigger_| for tab
-    // |web_contents_|.
-    void Launch();
-
-    // content::WebContentsObserver
-    void DidFinishNavigation(
-        content::NavigationHandle* navigation_handle) override;
-    void WebContentsDestroyed() override;
-
-    // Returns a weak pointer to this object.
-    virtual base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
-
-    bool operator<(const HatsServiceDesktop::DelayedSurveyTask& other) const {
-      return trigger_ < other.trigger_ ? true
-                                       : web_contents() < other.web_contents();
-    }
-
-   private:
-    raw_ptr<HatsServiceDesktop> hats_service_;
-
-    std::string trigger_;
-    SurveyBitsData product_specific_bits_data_;
-    SurveyStringData product_specific_string_data_;
-    bool require_same_origin_;
-    base::OnceClosure success_callback_;
-    base::OnceClosure failure_callback_;
-    base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
-  };
-
-  // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused.
-  enum class ShouldShowSurveyReasons {
-    kYes = 0,
-    kNoOffline = 1,
-    kNoLastSessionCrashed = 2,
-    kNoReceivedSurveyInCurrentMilestone = 3,
-    kNoProfileTooNew = 4,
-    kNoLastSurveyTooRecent = 5,
-    kNoBelowProbabilityLimit = 6,
-    kNoTriggerStringMismatch = 7,
-    kNoNotRegularBrowser = 8,
-    kNoIncognitoDisabled = 9,
-    kNoCookiesBlocked = 10,            // Unused.
-    kNoThirdPartyCookiesBlocked = 11,  // Unused.
-    kNoSurveyUnreachable = 12,
-    kNoSurveyOverCapacity = 13,
-    kNoSurveyAlreadyInProgress = 14,
-    kNoAnyLastSurveyTooRecent = 15,
-    kNoRejectedByHatsService = 16,
-    kMaxValue = kNoRejectedByHatsService,
-  };
-
-  explicit HatsServiceDesktop(Profile* profile);
-
-  HatsServiceDesktop(const HatsServiceDesktop&) = delete;
-  HatsServiceDesktop& operator=(const HatsServiceDesktop&) = delete;
-
-  ~HatsServiceDesktop() override;
-
-  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
-
-  void LaunchSurvey(
-      const std::string& trigger,
-      base::OnceClosure success_callback = base::DoNothing(),
-      base::OnceClosure failure_callback = base::DoNothing(),
-      const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {}) override;
-
-  void LaunchSurveyForWebContents(
-      const std::string& trigger,
-      content::WebContents* web_contents,
-      const SurveyBitsData& product_specific_bits_data,
-      const SurveyStringData& product_specific_string_data,
-      base::OnceClosure success_callback = base::DoNothing(),
-      base::OnceClosure failure_callback = base::DoNothing()) override;
-
-  bool LaunchDelayedSurvey(
-      const std::string& trigger,
-      int timeout_ms,
-      const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {}) override;
-
-  bool LaunchDelayedSurveyForWebContents(
-      const std::string& trigger,
-      content::WebContents* web_contents,
-      int timeout_ms,
-      const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {},
-      bool require_same_origin = false,
-      base::OnceClosure success_callback = base::DoNothing(),
-      base::OnceClosure failure_callback = base::DoNothing()) override;
-
-  void SetSurveyMetadataForTesting(const HatsService::SurveyMetadata& metadata);
-  void GetSurveyMetadataForTesting(HatsService::SurveyMetadata* metadata) const;
-
-  bool HasPendingTasks();
-
-  bool CanShowSurvey(const std::string& trigger) const override;
-
-  bool CanShowAnySurvey(bool user_prompted) const override;
-
-  void RecordSurveyAsShown(std::string trigger_id) override;
-
-  // Indicates to the service that the HaTS Next dialog has been closed.
-  virtual void HatsNextDialogClosed();
-
-  // Returns whether a HaTS Next dialog currently exists, regardless of whether
-  // it is being shown or not.
-  bool hats_next_dialog_exists_for_testing() {
-    return hats_next_dialog_exists_;
-  }
-
- protected:
- private:
-  FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
-
-  // Remove |task| from the set of |pending_tasks_|.
-  void RemoveTask(const DelayedSurveyTask& task);
-
-  // Returns true is the survey trigger specified should be shown.
-  bool ShouldShowSurvey(const std::string& trigger) const;
-
-  void LaunchSurveyForBrowser(
-      Browser* browser,
-      const std::string& trigger,
-      base::OnceClosure success_callback,
-      base::OnceClosure failure_callback,
-      const SurveyBitsData& product_specific_bits_data,
-      const SurveyStringData& product_specific_string_data);
-
-  // Check whether the survey is reachable and under capacity and show it.
-  // |success_callback| is called when the survey is shown to the user.
-  // |failure_callback| is called if the survey does not launch for any reason.
-  // The matches of field names with the `SurveyConfig` are CHECK
-  // enforced.
-  void CheckSurveyStatusAndMaybeShow(
-      Browser* browser,
-      const std::string& trigger,
-      base::OnceClosure success_callback,
-      base::OnceClosure failure_callback,
-      const SurveyBitsData& product_specific_bits_data,
-      const SurveyStringData& product_specific_string_data);
-
-  std::set<DelayedSurveyTask> pending_tasks_;
-
-  // Whether a HaTS Next dialog currently exists (regardless of whether it
-  // is being shown to the user).
-  bool hats_next_dialog_exists_ = false;
-
-  base::WeakPtrFactory<HatsServiceDesktop> weak_ptr_factory_{this};
-};
-
-#endif  // CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
diff --git a/chrome/browser/ui/hats/hats_service_factory.cc b/chrome/browser/ui/hats/hats_service_factory.cc
index 59b45bc..6d3110d 100644
--- a/chrome/browser/ui/hats/hats_service_factory.cc
+++ b/chrome/browser/ui/hats/hats_service_factory.cc
@@ -7,14 +7,12 @@
 #include "base/no_destructor.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/ui/android/hats/hats_service_android.h"
 #include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
 
 // static
 HatsService* HatsServiceFactory::GetForProfile(Profile* profile,
                                                bool create_if_necessary) {
-  return static_cast<HatsServiceAndroid*>(
+  return static_cast<HatsService*>(
       GetInstance()->GetServiceForBrowserContext(profile, create_if_necessary));
 }
 
@@ -34,11 +32,7 @@
 HatsServiceFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* context) const {
   Profile* profile = Profile::FromBrowserContext(context);
-#if BUILDFLAG(IS_ANDROID)
-  return std::make_unique<HatsServiceAndroid>(profile);
-#else
-  return std::make_unique<HatsServiceDesktop>(profile);
-#endif
+  return std::make_unique<HatsService>(profile);
 }
 
 HatsServiceFactory::~HatsServiceFactory() = default;
diff --git a/chrome/browser/ui/hats/hats_service_factory.h b/chrome/browser/ui/hats/hats_service_factory.h
index d2b67df..1227b43 100644
--- a/chrome/browser/ui/hats/hats_service_factory.h
+++ b/chrome/browser/ui/hats/hats_service_factory.h
@@ -7,8 +7,8 @@
 
 #include "base/no_destructor.h"
 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
-#include "chrome/browser/ui/hats/hats_service.h"
 
+class HatsService;
 class Profile;
 
 class HatsServiceFactory : public ProfileKeyedServiceFactory {
@@ -17,7 +17,6 @@
   HatsServiceFactory& operator=(const HatsServiceFactory&) = delete;
 
   static HatsService* GetForProfile(Profile* profile, bool create_if_necessary);
-
   static HatsServiceFactory* GetInstance();
 
  private:
diff --git a/chrome/browser/ui/hats/mock_hats_service.cc b/chrome/browser/ui/hats/mock_hats_service.cc
index 16e7f95c..d0d09992 100644
--- a/chrome/browser/ui/hats/mock_hats_service.cc
+++ b/chrome/browser/ui/hats/mock_hats_service.cc
@@ -12,8 +12,7 @@
 
 using ::testing::NiceMock;
 
-MockHatsService::MockHatsService(Profile* profile)
-    : HatsServiceDesktop(profile) {}
+MockHatsService::MockHatsService(Profile* profile) : HatsService(profile) {}
 
 MockHatsService::~MockHatsService() = default;
 
diff --git a/chrome/browser/ui/hats/mock_hats_service.h b/chrome/browser/ui/hats/mock_hats_service.h
index 73caaf5..a20db5b7 100644
--- a/chrome/browser/ui/hats/mock_hats_service.h
+++ b/chrome/browser/ui/hats/mock_hats_service.h
@@ -7,8 +7,7 @@
 
 #include <memory>
 
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
-#include "content/public/browser/web_contents.h"
+#include "chrome/browser/ui/hats/hats_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace content {
@@ -18,7 +17,7 @@
 class KeyedService;
 class Profile;
 
-class MockHatsService : public HatsServiceDesktop {
+class MockHatsService : public HatsService {
  public:
   explicit MockHatsService(Profile* profile);
   ~MockHatsService() override;
@@ -31,15 +30,6 @@
                (const SurveyBitsData&)survey_specific_bits_data,
                (const SurveyStringData&)survey_specific_string_data),
               (override));
-  MOCK_METHOD(void,
-              LaunchSurveyForWebContents,
-              (const std::string& trigger,
-               (content::WebContents*)web_contents,
-               (const SurveyBitsData&)survey_specific_bits_data,
-               (const SurveyStringData&)survey_specific_string_data,
-               base::OnceClosure success_callback,
-               base::OnceClosure failure_callback),
-              (override));
   MOCK_METHOD(bool,
               LaunchDelayedSurvey,
               (const std::string& trigger,
@@ -54,9 +44,7 @@
                int timeout_ms,
                (const SurveyBitsData&)survey_specific_bits_data,
                (const SurveyStringData&)survey_specific_string_data,
-               bool require_same_origin,
-               base::OnceClosure success_callback,
-               base::OnceClosure failure_callback),
+               bool require_same_origin),
               (override));
   MOCK_METHOD(void, HatsNextDialogClosed, (), (override));
   MOCK_METHOD(bool, CanShowAnySurvey, (bool user_prompted), (const override));
diff --git a/chrome/browser/ui/hats/survey_config.cc b/chrome/browser/ui/hats/survey_config.cc
index 1b91f555..23f63ed 100644
--- a/chrome/browser/ui/hats/survey_config.cc
+++ b/chrome/browser/ui/hats/survey_config.cc
@@ -3,16 +3,15 @@
 // found in the LICENSE file.
 
 #include "survey_config.h"
-
 #include "base/feature_list.h"
 #include "base/features.h"
-#include "components/permissions/features.h"
-#include "components/permissions/permission_hats_trigger_helper.h"
 
 #if !BUILDFLAG(IS_ANDROID)
 #include "chrome/common/chrome_features.h"
 #include "components/performance_manager/public/features.h"         // nogncheck
 #include "components/permissions/constants.h"                       // nogncheck
+#include "components/permissions/features.h"                        // nogncheck
+#include "components/permissions/permission_hats_trigger_helper.h"  // nogncheck
 #include "components/safe_browsing/core/common/features.h"          // nogncheck
 #include "components/safe_browsing/core/common/safebrowsing_constants.h"  // nogncheck
 #else
@@ -42,6 +41,7 @@
 // The permission prompt trigger permits configuring multiple triggers
 // simultaneously. Each trigger increments a counter at the end -->
 // "permission-prompt0", "permission-prompt1", ...
+constexpr char kHatsSurveyTriggerPermissionsPrompt[] = "permissions-prompt";
 constexpr char kHatsSurveyTriggerPrivacyGuide[] = "privacy-guide";
 constexpr char kHatsSurveyTriggerPrivacySandbox[] = "privacy-sandbox";
 constexpr char kHatsSurveyTriggerRedWarning[] = "red-warning";
@@ -116,8 +116,6 @@
 constexpr char kHatsNextSurveyTriggerIDTesting[] =
     "HLpeYy5Av0ugnJ3q1cK0XzzA8UHv";
 
-constexpr char kHatsSurveyTriggerPermissionsPrompt[] = "permissions-prompt";
-
 namespace {
 
 constexpr char kHatsSurveyProbability[] = "probability";
@@ -141,29 +139,6 @@
   default_survey.product_specific_string_data_fields = {"Test Field 3"};
   survey_configs.emplace_back(default_survey);
 
-  // Permissions surveys.
-  for (auto& trigger_id_pair : permissions::PermissionHatsTriggerHelper::
-           GetPermissionPromptTriggerIdPairs(
-               kHatsSurveyTriggerPermissionsPrompt)) {
-    // trigger_id_pair has structure <trigger_name, trigger_id>. trigger_name is
-    // a unique name used by the HaTS service integration, and trigger_id is an
-    // ID that specifies a survey in the Listnr backend.
-    survey_configs.emplace_back(
-        &permissions::features::kPermissionsPromptSurvey, trigger_id_pair.first,
-        trigger_id_pair.second,
-        std::vector<std::string>{
-            permissions::kPermissionsPromptSurveyHadGestureKey},
-        std::vector<std::string>{
-            permissions::kPermissionsPromptSurveyPromptDispositionKey,
-            permissions::kPermissionsPromptSurveyPromptDispositionReasonKey,
-            permissions::kPermissionsPromptSurveyActionKey,
-            permissions::kPermissionsPromptSurveyRequestTypeKey,
-            permissions::kPermissionsPromptSurveyReleaseChannelKey,
-            permissions::kPermissionsPromptSurveyDisplayTimeKey,
-            permissions::kPermissionPromptSurveyOneTimePromptsDecidedBucketKey,
-            permissions::kPermissionPromptSurveyUrlKey});
-  }
-
 #if !BUILDFLAG(IS_ANDROID)
   // Dev tools surveys.
   survey_configs.emplace_back(&features::kHaTSDesktopDevToolsIssuesCOEP,
@@ -458,6 +433,29 @@
       &features::kHappinessTrackingSurveysForDesktopWhatsNew,
       kHatsSurveyTriggerWhatsNew);
 
+  // Permissions surveys.
+  for (auto& trigger_id_pair : permissions::PermissionHatsTriggerHelper::
+           GetPermissionPromptTriggerIdPairs(
+               kHatsSurveyTriggerPermissionsPrompt)) {
+    // trigger_id_pair has structure <trigger_name, trigger_id>. trigger_name is
+    // a unique name used by the HaTS service integration, and trigger_id is an
+    // ID that specifies a survey in the Listnr backend.
+    survey_configs.emplace_back(
+        &permissions::features::kPermissionsPromptSurvey, trigger_id_pair.first,
+        trigger_id_pair.second,
+        std::vector<std::string>{
+            permissions::kPermissionsPromptSurveyHadGestureKey},
+        std::vector<std::string>{
+            permissions::kPermissionsPromptSurveyPromptDispositionKey,
+            permissions::kPermissionsPromptSurveyPromptDispositionReasonKey,
+            permissions::kPermissionsPromptSurveyActionKey,
+            permissions::kPermissionsPromptSurveyRequestTypeKey,
+            permissions::kPermissionsPromptSurveyReleaseChannelKey,
+            permissions::kPermissionsPromptSurveyDisplayTimeKey,
+            permissions::kPermissionPromptSurveyOneTimePromptsDecidedBucketKey,
+            permissions::kPermissionPromptSurveyUrlKey});
+  }
+
   // Performance Controls surveys.
   survey_configs.emplace_back(
       &performance_manager::features::kPerformanceControlsPerformanceSurvey,
diff --git a/chrome/browser/ui/hats/survey_config.h b/chrome/browser/ui/hats/survey_config.h
index e83e67c..734e2bc7 100644
--- a/chrome/browser/ui/hats/survey_config.h
+++ b/chrome/browser/ui/hats/survey_config.h
@@ -30,6 +30,7 @@
 extern const char kHatsSurveyTriggerPerformanceControlsBatteryPerformance[];
 extern const char kHatsSurveyTriggerPerformanceControlsHighEfficiencyOptOut[];
 extern const char kHatsSurveyTriggerPerformanceControlsBatterySaverOptOut[];
+extern const char kHatsSurveyTriggerPermissionsPrompt[];
 extern const char kHatsSurveyTriggerPrivacyGuide[];
 extern const char kHatsSurveyTriggerPrivacySandbox[];
 extern const char kHatsSurveyTriggerRedWarning[];
@@ -73,8 +74,6 @@
 extern const char kHatsSurveyTriggerAndroidStartupSurvey[];
 #endif
 
-extern const char kHatsSurveyTriggerPermissionsPrompt[];
-
 extern const char kHatsSurveyTriggerTesting[];
 // The Trigger ID for a test HaTS Next survey which is available for testing
 // and demo purposes when the migration feature flag is enabled.
diff --git a/chrome/browser/ui/views/hats/hats_browsertest.cc b/chrome/browser/ui/views/hats/hats_browsertest.cc
index e5431d9..35f9eb8 100644
--- a/chrome/browser/ui/views/hats/hats_browsertest.cc
+++ b/chrome/browser/ui/views/hats/hats_browsertest.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/devtools/devtools_window_testing.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
+#include "chrome/browser/ui/hats/hats_service.h"
 #include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/hats/mock_hats_service.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
@@ -49,7 +49,7 @@
 
 // The locale expected by the test survey. This value is checked in
 // hats_next_mock.html for tests that expect a loaded response.
-const char kTestLocale[] = "lt";
+const std::string kTestLocale = "lt";
 
 }  // namespace
 
@@ -212,7 +212,7 @@
   // Because no loaded state was provided, only a rejection should be recorded.
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsServiceDesktop::ShouldShowSurveyReasons::kNoRejectedByHatsService, 1);
+      HatsService::ShouldShowSurveyReasons::kNoRejectedByHatsService, 1);
 }
 
 // Test that a survey which first reports as loaded, then reports closure, only
@@ -238,7 +238,7 @@
   // The only recorded sample should indicate that the survey was shown.
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsServiceDesktop::ShouldShowSurveyReasons::kYes, 1);
+      HatsService::ShouldShowSurveyReasons::kYes, 1);
 }
 
 // Test that if the survey does not indicate it is ready for display before the
@@ -259,7 +259,7 @@
   EXPECT_EQ(1, failure_count);
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsServiceDesktop::ShouldShowSurveyReasons::kNoSurveyUnreachable, 1);
+      HatsService::ShouldShowSurveyReasons::kNoSurveyUnreachable, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, UnknownURLFragment) {
diff --git a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
index 8b1ebe3..59849e7b 100644
--- a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
+++ b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
@@ -8,7 +8,6 @@
 #include "base/json/json_writer.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/notreached.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/devtools/devtools_window.h"
@@ -16,7 +15,6 @@
 #include "chrome/browser/profiles/profile_destroyer.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/browser/ui/hats/hats_service_desktop.h"
 #include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/app_menu_button.h"
@@ -215,7 +213,7 @@
     // such as a survey still being in test mode, or an invalid survey ID.
     base::UmaHistogramEnumeration(
         kHatsShouldShowSurveyReasonHistogram,
-        HatsServiceDesktop::ShouldShowSurveyReasons::kNoRejectedByHatsService);
+        HatsService::ShouldShowSurveyReasons::kNoRejectedByHatsService);
     std::move(failure_callback_).Run();
   }
   CloseWidget();
@@ -286,16 +284,12 @@
 }
 
 HatsNextWebDialog::~HatsNextWebDialog() {
-#if IS_ANDROID
-  NOTIMPLEMENTED();  // This class is for desktop only. Enforce assumption.
-#endif
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (otr_profile_) {
     otr_profile_->RemoveObserver(this);
     ProfileDestroyer::DestroyOTRProfileWhenAppropriate(otr_profile_);
   }
-  HatsServiceDesktop* service = static_cast<HatsServiceDesktop*>(
-      HatsServiceFactory::GetForProfile(browser_->profile(), false));
+  auto* service = HatsServiceFactory::GetForProfile(browser_->profile(), false);
   DCHECK(service);
   service->HatsNextDialogClosed();
 
@@ -346,7 +340,7 @@
 void HatsNextWebDialog::LoadTimedOut() {
   base::UmaHistogramEnumeration(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsServiceDesktop::ShouldShowSurveyReasons::kNoSurveyUnreachable);
+      HatsService::ShouldShowSurveyReasons::kNoSurveyUnreachable);
   CloseWidget();
   std::move(failure_callback_).Run();
 }
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
index 45b419a..9d07fc7 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
@@ -960,7 +960,7 @@
       {});
 
   EXPECT_CALL(*mock_hats_service(),
-              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
+              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
       .Times(1);
   const std::vector<std::string> module_ids = {"recipe_tasks", "cart"};
   handler_->OnModulesLoadedWithData(module_ids);
@@ -977,7 +977,7 @@
       {});
 
   EXPECT_CALL(*mock_hats_service(),
-              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
+              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
       .Times(0);
   const std::vector<std::string> module_ids = {"recipe_tasks"};
   handler_->OnModulesLoadedWithData(module_ids);
diff --git a/chrome/browser/ui/webui/settings/hats_handler_unittest.cc b/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
index f9d5cd2..ca7cef7a 100644
--- a/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
@@ -110,7 +110,7 @@
   EXPECT_CALL(*mock_hats_service_,
               LaunchDelayedSurveyForWebContents(
                   kHatsSurveyTriggerSettingsPrivacy, web_contents(), 15000,
-                  expected_product_specific_data, _, true, _, _))
+                  expected_product_specific_data, _, true))
       .Times(2);
   base::Value::List args;
   args.Append(
@@ -128,7 +128,7 @@
   // Check that completing a privacy guide triggers a privacy guide hats.
   EXPECT_CALL(*mock_hats_service_, LaunchDelayedSurveyForWebContents(
                                        kHatsSurveyTriggerPrivacyGuide,
-                                       web_contents(), 15000, _, _, true, _, _))
+                                       web_contents(), 15000, _, _, true))
       .Times(1);
   base::Value::List args;
   args.Append(static_cast<int>(
@@ -159,7 +159,7 @@
   EXPECT_CALL(*mock_hats_service_,
               LaunchDelayedSurveyForWebContents(
                   kHatsSurveyTriggerSettingsSecurity, web_contents(), 15000, _,
-                  expected_product_specific_data, true, _, _))
+                  expected_product_specific_data, true))
       .Times(1);
 
   base::Value::List args;
@@ -196,7 +196,7 @@
   // Enable targeting for users who have not seen the Privacy Sandbox page and
   // ensure the handler does not attempt to launch the survey.
   EXPECT_CALL(*mock_hats_service_,
-              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
+              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
       .Times(0);
 
   profile()->GetPrefs()->SetBoolean(prefs::kPrivacySandboxPageViewed, true);
@@ -221,7 +221,7 @@
   EXPECT_CALL(*mock_hats_service_,
               LaunchDelayedSurveyForWebContents(
                   kHatsSurveyTriggerPrivacySandbox, web_contents(), 10000,
-                  expected_product_specific_data, _, true, _, _));
+                  expected_product_specific_data, _, true));
   base::Value::List args;
   args.Append(static_cast<int>(
       HatsHandler::TrustSafetyInteraction::OPENED_PRIVACY_SANDBOX));
@@ -302,7 +302,7 @@
     EXPECT_CALL(*mock_hats_service_,
                 LaunchDelayedSurveyForWebContents(
                     survey, web_contents(), 20000,
-                    expected_product_specific_data, _, true, _, _));
+                    expected_product_specific_data, _, true));
     base::Value::List args;
     args.Append(static_cast<int>(interaction));
     handler()->HandleTrustSafetyInteractionOccurred(args);
diff --git a/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc b/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
index 8489f63..37a5250 100644
--- a/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
@@ -56,7 +56,7 @@
           browser()->profile(), base::BindRepeating(&BuildMockHatsService)));
   EXPECT_CALL(*mock_hats_service_,
               LaunchDelayedSurveyForWebContents(kHatsSurveyTriggerSettings, _,
-                                                _, _, _, _, _, _));
+                                                _, _, _, _));
   ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));
   base::RunLoop().RunUntilIdle();
 }