arc: Persist stability metrics into local state

In case of a crash, metrics from previous session is not available when
generating initial stability logs, which is probably why aggregated
crash counts on UMA don't have ARC state associated.

This patch introduces StabilityMetricsManager, which persists stability
metrics into local state and record value from local state to UMA when
needed.

Bug: 929583
Change-Id: I747493f2462454baa7fc187421932ea71d52c9a1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1469810
Commit-Queue: Shao-Chuan Lee <shaochuan@chromium.org>
Reviewed-by: Hidehiko Abe <hidehiko@chromium.org>
Reviewed-by: Yury Khmel <khmel@chromium.org>
Reviewed-by: Mark Pearson <mpearson@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Gabriel Charette <gab@chromium.org>
Cr-Commit-Position: refs/heads/master@{#637591}
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index f3cc92c..657dff3 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -216,6 +216,7 @@
 #include "chrome/browser/chromeos/settings/stats_reporting_controller.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/settings/cros_settings_names.h"
+#include "components/arc/metrics/stability_metrics_manager.h"
 #endif  // defined(OS_CHROMEOS)
 
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
@@ -999,6 +1000,7 @@
 #if defined(OS_CHROMEOS)
   chromeos::CrosSettings::Initialize(local_state);
   chromeos::StatsReportingController::Initialize(local_state);
+  arc::StabilityMetricsManager::Initialize(local_state);
 #endif  // defined(OS_CHROMEOS)
 
   {
@@ -1935,6 +1937,7 @@
   CHECK(metrics::MetricsService::UmaMetricsProperlyShutdown());
 
 #if defined(OS_CHROMEOS)
+  arc::StabilityMetricsManager::Shutdown();
   chromeos::StatsReportingController::Shutdown();
   chromeos::CrosSettings::Shutdown();
 #endif  // defined(OS_CHROMEOS)
diff --git a/chrome/browser/chromeos/arc/arc_optin_uma.cc b/chrome/browser/chromeos/arc/arc_optin_uma.cc
index 1b2d8a8..38a3a9d 100644
--- a/chrome/browser/chromeos/arc/arc_optin_uma.cc
+++ b/chrome/browser/chromeos/arc/arc_optin_uma.cc
@@ -11,8 +11,11 @@
 #include "chrome/browser/chromeos/arc/arc_util.h"
 #include "chrome/browser/chromeos/arc/policy/arc_policy_util.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "components/arc/arc_util.h"
+#include "components/arc/metrics/stability_metrics_manager.h"
 
 namespace arc {
 
@@ -59,6 +62,29 @@
 
 }  // namespace
 
+void UpdateEnabledStateByUserTypeUMA() {
+  const Profile* profile = ProfileManager::GetPrimaryUserProfile();
+  // Don't record UMA for the set of cases:
+  // * No primary profile is set at this moment.
+  // * Primary profile matches the built-in profile used for signing in or the
+  //   lock screen.
+  // * Primary profile matches guest session.
+  // * Primary profile is in incognito mode.
+  if (!profile || chromeos::ProfileHelper::IsSigninProfile(profile) ||
+      chromeos::ProfileHelper::IsLockScreenAppProfile(profile) ||
+      profile->IsOffTheRecord() || profile->IsGuestSession()) {
+    return;
+  }
+
+  base::Optional<bool> enabled_state;
+  if (auto* stability_metrics_manager = StabilityMetricsManager::Get())
+    enabled_state = stability_metrics_manager->GetArcEnabledState();
+
+  base::UmaHistogramEnumeration(
+      GetHistogramName("Arc.StateByUserType.", profile),
+      ComputeEnabledState(enabled_state.value_or(false), profile));
+}
+
 void UpdateOptInActionUMA(OptInActionType type) {
   UMA_HISTOGRAM_ENUMERATION("Arc.OptInAction", type);
 }
@@ -67,17 +93,6 @@
   UMA_HISTOGRAM_ENUMERATION("Arc.OptInCancel", reason);
 }
 
-void UpdateEnabledStateUMA(bool enabled) {
-  // Equivalent to UMA_HISTOGRAM_BOOLEAN with the stability flag set.
-  UMA_STABILITY_HISTOGRAM_ENUMERATION("Arc.State", enabled ? 1 : 0, 2);
-}
-
-void UpdateEnabledStateByUserTypeUMA(bool enabled, const Profile* profile) {
-  base::UmaHistogramEnumeration(
-      GetHistogramName("Arc.StateByUserType.", profile),
-      ComputeEnabledState(enabled, profile));
-}
-
 void UpdateOptInFlowResultUMA(OptInFlowResult result) {
   UMA_HISTOGRAM_ENUMERATION("Arc.OptInResult", result);
 }
diff --git a/chrome/browser/chromeos/arc/arc_optin_uma.h b/chrome/browser/chromeos/arc/arc_optin_uma.h
index 52f48bb..e327466 100644
--- a/chrome/browser/chromeos/arc/arc_optin_uma.h
+++ b/chrome/browser/chromeos/arc/arc_optin_uma.h
@@ -212,11 +212,17 @@
   kMaxValue = DISABLED_NOT_ALLOWED,
 };
 
+// Called from the Chrome OS metrics provider to record Arc.StateByUserType
+// strictly once per every metrics recording interval. This way they are in
+// every record uploaded to the server and therefore can be used to split and
+// compare analysis data for all other metrics.
+// TODO(shaochuan): Decouple profile-related logic and move recording to
+// components/arc.
+void UpdateEnabledStateByUserTypeUMA();
+
 void UpdateOptInActionUMA(OptInActionType type);
 void UpdateOptInCancelUMA(OptInCancelReason reason);
 void UpdateOptInFlowResultUMA(OptInFlowResult result);
-void UpdateEnabledStateUMA(bool enabled);
-void UpdateEnabledStateByUserTypeUMA(bool enabled, const Profile* profile);
 void UpdateProvisioningResultUMA(ProvisioningResult result,
                                  const Profile* profile);
 void UpdateSecondarySigninResultUMA(ProvisioningResult result);
diff --git a/chrome/browser/chromeos/arc/arc_session_manager.cc b/chrome/browser/chromeos/arc/arc_session_manager.cc
index cd35f5b..980e291 100644
--- a/chrome/browser/chromeos/arc/arc_session_manager.cc
+++ b/chrome/browser/chromeos/arc/arc_session_manager.cc
@@ -25,12 +25,10 @@
 #include "chrome/browser/chromeos/arc/policy/arc_android_management_checker.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_resources.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/policy/profile_policy_connector_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_launcher.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/app_list/arc/arc_fast_app_reinstall_starter.h"
@@ -51,6 +49,7 @@
 #include "components/arc/arc_util.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
 #include "components/arc/metrics/arc_metrics_service.h"
+#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/prefs/pref_service.h"
 #include "components/session_manager/core/session_manager.h"
 #include "content/public/browser/browser_thread.h"
@@ -135,6 +134,24 @@
   return true;
 }
 
+void ResetStabilityMetrics() {
+  // TODO(shaochuan): Make this an event observable by StabilityMetricsManager
+  // and eliminate this null check.
+  auto* stability_metrics_manager = StabilityMetricsManager::Get();
+  if (!stability_metrics_manager)
+    return;
+  stability_metrics_manager->ResetMetrics();
+}
+
+void SetArcEnabledStateMetric(bool enabled) {
+  // TODO(shaochuan): Make this an event observable by StabilityMetricsManager
+  // and eliminate this null check.
+  auto* stability_metrics_manager = StabilityMetricsManager::Get();
+  if (!stability_metrics_manager)
+    return;
+  stability_metrics_manager->SetArcEnabledState(enabled);
+}
+
 }  // namespace
 
 // This class is used to track statuses on OptIn flow. It is created in case ARC
@@ -205,6 +222,7 @@
   chromeos::SessionManagerClient* client = GetSessionManagerClient();
   if (client)
     client->AddObserver(this);
+  ResetStabilityMetrics();
 }
 
 ArcSessionManager::~ArcSessionManager() {
@@ -438,7 +456,9 @@
   DCHECK(!profile_);
   DCHECK(IsArcAllowedForProfile(profile));
   profile_ = profile;
-  UpdatePersistentUMAState();
+  // RequestEnable() requires |profile_| set, therefore shouldn't have been
+  // called at this point.
+  SetArcEnabledStateMetric(false);
 }
 
 void ArcSessionManager::Initialize() {
@@ -607,36 +627,6 @@
   SetArcPlayStoreEnabledForProfile(profile_, false);
 }
 
-void ArcSessionManager::RecordArcState() {
-  // Only record legacy enabled state if ARC is allowed in the first place, so
-  // we do not split the ARC population by devices that cannot run ARC.
-  if (should_record_legacy_enabled_state_)
-    UpdateEnabledStateUMA(enabled_state_uma_);
-
-  if (IsAllowed()) {
-    UpdateEnabledStateByUserTypeUMA(enable_requested_, profile_);
-    ArcMetricsService* service =
-        ArcMetricsService::GetForBrowserContext(profile_);
-    service->RecordNativeBridgeUMA();
-    return;
-  }
-
-  const Profile* profile = ProfileManager::GetPrimaryUserProfile();
-  // Don't record UMA for the set of cases:
-  // * No primary profile is set at this moment.
-  // * Primary profile matches the built-in profile used for signing in or the
-  //   lock screen.
-  // * Primary profile matches guest session.
-  // * Primary profile is in incognito mode.
-  if (!profile || chromeos::ProfileHelper::IsSigninProfile(profile) ||
-      chromeos::ProfileHelper::IsLockScreenAppProfile(profile) ||
-      profile->IsOffTheRecord() || profile->IsGuestSession()) {
-    return;
-  }
-
-  UpdateEnabledStateByUserTypeUMA(enabled_state_uma_, profile);
-}
-
 void ArcSessionManager::RequestEnable() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(profile_);
@@ -646,7 +636,7 @@
     return;
   }
   enable_requested_ = true;
-  UpdatePersistentUMAState();
+  SetArcEnabledStateMetric(true);
 
   VLOG(1) << "ARC opt-in. Starting ARC session.";
 
@@ -757,7 +747,7 @@
 
   directly_started_ = false;
   enable_requested_ = false;
-  UpdatePersistentUMAState();
+  SetArcEnabledStateMetric(false);
   scoped_opt_in_tracker_.reset();
   pai_starter_.reset();
   fast_app_reinstall_starter_.reset();
@@ -1184,11 +1174,6 @@
   arc_session_runner_->RequestStartMiniInstance();
 }
 
-void ArcSessionManager::UpdatePersistentUMAState() {
-  should_record_legacy_enabled_state_ = IsAllowed();
-  enabled_state_uma_ = enable_requested_;
-}
-
 std::ostream& operator<<(std::ostream& os,
                          const ArcSessionManager::State& state) {
 #define MAP_STATE(name)                \
diff --git a/chrome/browser/chromeos/arc/arc_session_manager.h b/chrome/browser/chromeos/arc/arc_session_manager.h
index 86b5ddf..3c6e427 100644
--- a/chrome/browser/chromeos/arc/arc_session_manager.h
+++ b/chrome/browser/chromeos/arc/arc_session_manager.h
@@ -200,12 +200,6 @@
   // this.
   void RequestArcDataRemoval();
 
-  // Called from the Chrome OS metrics provider to record Arc.State and similar
-  // values strictly once per every metrics recording interval. This way they
-  // are in every record uploaded to the server and therefore can be used to
-  // split and compare analysis data for all other metrics.
-  void RecordArcState();
-
   // ArcSupportHost:::ErrorDelegate:
   void OnWindowClosed() override;
   void OnRetryClicked() override;
@@ -350,11 +344,6 @@
   // chromeos::SessionManagerClient::Observer:
   void EmitLoginPromptVisibleCalled() override;
 
-  // Updates |should_record_legacy_enabled_state_| and |enabled_state_uma_|
-  // whenever |profile_| or |enable_requested_| is changed (except Shutdown()).
-  // TODO(crbug.com/929583): Remove this temporary fix.
-  void UpdatePersistentUMAState();
-
   std::unique_ptr<ArcSessionRunner> arc_session_runner_;
 
   // Unowned pointer. Keeps current profile.
@@ -391,13 +380,6 @@
   base::Time arc_start_time_;
   base::Closure attempt_user_exit_callback_;
 
-  // ARC state depends on |profile_| and |enable_requested_| which is reset in
-  // Shutdown(). Since UMA recording happens after Shutdown() on browser
-  // shutdown, here we persist relevant state for UMA recording purposes.
-  // TODO(crbug.com/929583): Remove this temporary fix.
-  bool should_record_legacy_enabled_state_ = false;
-  bool enabled_state_uma_ = false;
-
   // Must be the last member.
   base::WeakPtrFactory<ArcSessionManager> weak_ptr_factory_;
 
diff --git a/chrome/browser/metrics/chromeos_metrics_provider.cc b/chrome/browser/metrics/chromeos_metrics_provider.cc
index 8613211..979919b 100644
--- a/chrome/browser/metrics/chromeos_metrics_provider.cc
+++ b/chrome/browser/metrics/chromeos_metrics_provider.cc
@@ -18,11 +18,12 @@
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/arc/arc_session_manager.h"
+#include "chrome/browser/chromeos/arc/arc_optin_uma.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/system/statistics_provider.h"
+#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/metrics/metrics_service.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
@@ -235,16 +236,15 @@
     stability_proto->set_unclean_system_shutdown_count(count);
     pref->SetInteger(prefs::kStabilitySystemUncleanShutdownCount, 0);
   }
-}
 
-void ChromeOSMetricsProvider::ProvidePreviousSessionData(
-    metrics::ChromeUserMetricsExtension* uma_proto) {
-  ProvideStabilityMetrics(uma_proto->mutable_system_profile());
-  // The enrollment status and ARC state of a client are not likely to change
-  // between browser restarts.  Hence, it's safe and useful to attach these
-  // values to a previous session log.
-  RecordEnrollmentStatus();
-  RecordArcState();
+  // Use current enrollment status for initial stability logs, since it's not
+  // likely to change between browser restarts.
+  UMA_STABILITY_HISTOGRAM_ENUMERATION(
+      "UMA.EnrollmentStatus", GetEnrollmentStatus(), ENROLLMENT_STATUS_MAX);
+
+  // Record ARC-related stability metrics that should be included in initial
+  // stability logs and all regular UMA logs.
+  arc::StabilityMetricsManager::Get()->RecordMetricsToUMA();
 }
 
 void ChromeOSMetricsProvider::ProvideCurrentSessionData(
@@ -256,9 +256,7 @@
       uma_proto->add_sampled_profile()->Swap(&profile);
     }
   }
-
-  RecordEnrollmentStatus();
-  RecordArcState();
+  arc::UpdateEnabledStateByUserTypeUMA();
 }
 
 void ChromeOSMetricsProvider::WriteBluetoothProto(
@@ -350,14 +348,3 @@
   full_hardware_class_ = full_hardware_class;
   callback.Run();
 }
-
-void ChromeOSMetricsProvider::RecordEnrollmentStatus() {
-  UMA_STABILITY_HISTOGRAM_ENUMERATION(
-      "UMA.EnrollmentStatus", GetEnrollmentStatus(), ENROLLMENT_STATUS_MAX);
-}
-
-void ChromeOSMetricsProvider::RecordArcState() {
-  arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
-  if (arc_session_manager)
-    arc_session_manager->RecordArcState();
-}
diff --git a/chrome/browser/metrics/chromeos_metrics_provider.h b/chrome/browser/metrics/chromeos_metrics_provider.h
index 547e7a5e..c814c7a 100644
--- a/chrome/browser/metrics/chromeos_metrics_provider.h
+++ b/chrome/browser/metrics/chromeos_metrics_provider.h
@@ -69,8 +69,6 @@
       metrics::SystemProfileProto* system_profile_proto) override;
   void ProvideStabilityMetrics(
       metrics::SystemProfileProto* system_profile_proto) override;
-  void ProvidePreviousSessionData(
-      metrics::ChromeUserMetricsExtension* uma_proto) override;
   void ProvideCurrentSessionData(
       metrics::ChromeUserMetricsExtension* uma_proto) override;
 
@@ -93,12 +91,6 @@
   // Writes info about paired Bluetooth devices on this system.
   void WriteBluetoothProto(metrics::SystemProfileProto* system_profile_proto);
 
-  // Record the device enrollment status.
-  void RecordEnrollmentStatus();
-
-  // Record whether ARC is enabled or not for ARC capable devices.
-  void RecordArcState();
-
   // For collecting systemwide performance data via the UMA channel.
   std::unique_ptr<metrics::ProfileProvider> profile_provider_;
 
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 2b89485..5060969 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -507,6 +507,7 @@
 #endif  // !defined(OS_ANDROID)
 
 #if defined(OS_CHROMEOS)
+  arc::prefs::RegisterLocalStatePrefs(registry);
   ChromeOSMetricsProvider::RegisterPrefs(registry);
   chromeos::ArcKioskAppManager::RegisterPrefs(registry);
   chromeos::AudioDevicesPrefHandlerImpl::RegisterPrefs(registry);
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index eff5825..11017d0ba 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -43,6 +43,8 @@
     "lock_screen/arc_lock_screen_bridge.h",
     "metrics/arc_metrics_service.cc",
     "metrics/arc_metrics_service.h",
+    "metrics/stability_metrics_manager.cc",
+    "metrics/stability_metrics_manager.h",
     "midis/arc_midis_bridge.cc",
     "midis/arc_midis_bridge.h",
     "net/always_on_vpn_manager.cc",
@@ -330,6 +332,7 @@
     "intent_helper/intent_filter_unittest.cc",
     "intent_helper/link_handler_model_unittest.cc",
     "metrics/arc_metrics_service_unittest.cc",
+    "metrics/stability_metrics_manager_unittest.cc",
     "net/always_on_vpn_manager_unittest.cc",
     "net/arc_net_host_impl_unittest.cc",
     "power/arc_power_bridge_unittest.cc",
diff --git a/components/arc/arc_prefs.cc b/components/arc/arc_prefs.cc
index ac7a53d..1b88dcd 100644
--- a/components/arc/arc_prefs.cc
+++ b/components/arc/arc_prefs.cc
@@ -12,6 +12,9 @@
 namespace arc {
 namespace prefs {
 
+// ======== PROFILE PREFS ========
+// See below for local state prefs.
+
 // A bool preference indicating whether traffic other than the VPN connection
 // set via kAlwaysOnVpnPackage should be blackholed.
 const char kAlwaysOnVpnLockdown[] = "arc.vpn.always_on.lockdown";
@@ -135,6 +138,18 @@
 const char kVoiceInteractionNotificationEnabled[] =
     "settings.voice_interaction.notification.enabled";
 
+// ======== LOCAL STATE PREFS ========
+
+// A dictionary preference that keeps track of stability metric values, which is
+// maintained by StabilityMetricsManager. Persisting values in local state is
+// required to include these metrics in the initial stability log in case of a
+// crash.
+const char kStabilityMetrics[] = "arc.metrics.stability";
+
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(kStabilityMetrics);
+}
+
 void RegisterProfilePrefs(PrefRegistrySimple* registry) {
   // TODO(dspaid): Implement a mechanism to allow this to sync on first boot
   // only.
diff --git a/components/arc/arc_prefs.h b/components/arc/arc_prefs.h
index cf17451..1e0dc0a 100644
--- a/components/arc/arc_prefs.h
+++ b/components/arc/arc_prefs.h
@@ -12,38 +12,37 @@
 namespace arc {
 namespace prefs {
 
-// Sorted in lexicographical order.
+// Profile prefs in lexicographical order. See below for local state prefs.
 ARC_EXPORT extern const char kAlwaysOnVpnLockdown[];
 ARC_EXPORT extern const char kAlwaysOnVpnPackage[];
 ARC_EXPORT extern const char kArcActiveDirectoryPlayUserId[];
 ARC_EXPORT extern const char kArcApps[];
 ARC_EXPORT extern const char kArcBackupRestoreEnabled[];
+ARC_EXPORT extern const char kArcCompatibleFilesystemChosen[];
 ARC_EXPORT extern const char kArcDataRemoveRequested[];
 ARC_EXPORT extern const char kArcEnabled[];
 ARC_EXPORT extern const char kArcFastAppReinstallPackages[];
 ARC_EXPORT extern const char kArcFastAppReinstallStarted[];
 ARC_EXPORT extern const char kArcInitialSettingsPending[];
-ARC_EXPORT extern const char kArcPolicyComplianceReported[];
-ARC_EXPORT extern const char kArcTermsAccepted[];
-ARC_EXPORT extern const char kArcTermsShownInOobe[];
 ARC_EXPORT extern const char kArcLocationServiceEnabled[];
 ARC_EXPORT extern const char kArcPackages[];
 ARC_EXPORT extern const char kArcPaiStarted[];
+ARC_EXPORT extern const char kArcPolicyComplianceReported[];
 ARC_EXPORT extern const char kArcProvisioningInitiatedFromOobe[];
-ARC_EXPORT extern const char kArcPushInstallAppsRequested[];
 ARC_EXPORT extern const char kArcPushInstallAppsPending[];
+ARC_EXPORT extern const char kArcPushInstallAppsRequested[];
 ARC_EXPORT extern const char kArcSetNotificationsEnabledDeferred[];
 ARC_EXPORT extern const char kArcSignedIn[];
 ARC_EXPORT extern const char kArcSkippedReportingNotice[];
 ARC_EXPORT extern const char kArcSupervisionTransition[];
-ARC_EXPORT extern const char kArcCompatibleFilesystemChosen[];
+ARC_EXPORT extern const char kArcTermsAccepted[];
+ARC_EXPORT extern const char kArcTermsShownInOobe[];
 ARC_EXPORT extern const char kEcryptfsMigrationStrategy[];
 ARC_EXPORT extern const char kEngagementTimeBackground[];
 ARC_EXPORT extern const char kEngagementTimeDayId[];
 ARC_EXPORT extern const char kEngagementTimeForeground[];
 ARC_EXPORT extern const char kEngagementTimeOsVersion[];
 ARC_EXPORT extern const char kEngagementTimeTotal[];
-
 // TODO(b/110211045): Move Assistant related prefs to ash.
 ARC_EXPORT extern const char kVoiceInteractionContextEnabled[];
 ARC_EXPORT extern const char kVoiceInteractionEnabled[];
@@ -52,6 +51,10 @@
 ARC_EXPORT extern const char kVoiceInteractionLaunchWithMicOpen[];
 ARC_EXPORT extern const char kVoiceInteractionNotificationEnabled[];
 
+// Local state prefs in lexicographical order.
+ARC_EXPORT extern const char kStabilityMetrics[];
+
+void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
 void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
 }  // namespace prefs
diff --git a/components/arc/metrics/arc_metrics_constants.h b/components/arc/metrics/arc_metrics_constants.h
index 59a8286..4e48ecdc 100644
--- a/components/arc/metrics/arc_metrics_constants.h
+++ b/components/arc/metrics/arc_metrics_constants.h
@@ -7,6 +7,22 @@
 
 namespace arc {
 
+// Native bridge types for UMA recording (Arc.NativeBridge). These values are
+// persisted to logs, and should therefore never be renumbered nor reused.
+// Should be synced with ArcNativeBridgeType in
+// tools/metrics/histograms/enums.xml.
+enum class NativeBridgeType {
+  // Native bridge value has not been received from the container yet.
+  UNKNOWN = 0,
+  // Native bridge is not used.
+  NONE = 1,
+  // Using houdini translator.
+  HOUDINI = 2,
+  // Using ndk-translation translator.
+  NDK_TRANSLATION = 3,
+  kMaxValue = NDK_TRANSLATION,
+};
+
 // Defines ARC App user interaction types to track how users use ARC apps.
 // These enums are used to define the buckets for an enumerated UMA histogram
 // and need to be synced with tools/metrics/histograms/enums.xml.
diff --git a/components/arc/metrics/arc_metrics_service.cc b/components/arc/metrics/arc_metrics_service.cc
index b5ef30f..ad2859d6 100644
--- a/components/arc/metrics/arc_metrics_service.cc
+++ b/components/arc/metrics/arc_metrics_service.cc
@@ -24,6 +24,7 @@
 #include "components/arc/arc_prefs.h"
 #include "components/arc/arc_util.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
+#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/exo/wm_helper.h"
 #include "components/prefs/pref_service.h"
 #include "components/session_manager/core/session_manager.h"
@@ -141,7 +142,6 @@
     : arc_bridge_service_(bridge_service),
       arc_window_delegate_(std::make_unique<ArcWindowDelegateImpl>(this)),
       process_observer_(this),
-      native_bridge_type_(NativeBridgeType::UNKNOWN),
       pref_service_(user_prefs::UserPrefs::Get(context)),
       clock_(base::DefaultClock::GetInstance()),
       tick_clock_(base::DefaultTickClock::GetInstance()),
@@ -161,6 +161,9 @@
   save_engagement_time_to_prefs_timer_.Start(
       FROM_HERE, kSaveEngagementTimeToPrefsPeriod, this,
       &ArcMetricsService::SaveEngagementTimeToPrefs);
+
+  StabilityMetricsManager::Get()->SetArcNativeBridgeType(
+      NativeBridgeType::UNKNOWN);
 }
 
 ArcMetricsService::~ArcMetricsService() {
@@ -297,29 +300,26 @@
 }
 
 void ArcMetricsService::ReportNativeBridge(
-    mojom::NativeBridgeType native_bridge_type) {
+    mojom::NativeBridgeType mojo_native_bridge_type) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  VLOG(2) << "Mojo native bridge type is " << native_bridge_type;
+  VLOG(2) << "Mojo native bridge type is " << mojo_native_bridge_type;
 
-  // Save value for RecordNativeBridgeUMA instead of recording
-  // immediately since it must appear in every metrics interval
-  // uploaded to UMA.
-  switch (native_bridge_type) {
+  NativeBridgeType native_bridge_type = NativeBridgeType::UNKNOWN;
+  switch (mojo_native_bridge_type) {
     case mojom::NativeBridgeType::NONE:
-      native_bridge_type_ = NativeBridgeType::NONE;
-      return;
+      native_bridge_type = NativeBridgeType::NONE;
+      break;
     case mojom::NativeBridgeType::HOUDINI:
-      native_bridge_type_ = NativeBridgeType::HOUDINI;
-      return;
+      native_bridge_type = NativeBridgeType::HOUDINI;
+      break;
     case mojom::NativeBridgeType::NDK_TRANSLATION:
-      native_bridge_type_ = NativeBridgeType::NDK_TRANSLATION;
-      return;
+      native_bridge_type = NativeBridgeType::NDK_TRANSLATION;
+      break;
   }
-  NOTREACHED() << native_bridge_type;
-}
+  DCHECK_NE(native_bridge_type, NativeBridgeType::UNKNOWN)
+      << mojo_native_bridge_type;
 
-void ArcMetricsService::RecordNativeBridgeUMA() {
-  UMA_HISTOGRAM_ENUMERATION("Arc.NativeBridge", native_bridge_type_);
+  StabilityMetricsManager::Get()->SetArcNativeBridgeType(native_bridge_type);
 }
 
 void ArcMetricsService::OnWindowActivated(
diff --git a/components/arc/metrics/arc_metrics_service.h b/components/arc/metrics/arc_metrics_service.h
index faffff74..6c4d88fa 100644
--- a/components/arc/metrics/arc_metrics_service.h
+++ b/components/arc/metrics/arc_metrics_service.h
@@ -49,20 +49,6 @@
                           public chromeos::PowerManagerClient::Observer,
                           public mojom::MetricsHost {
  public:
-  // These values are persisted to logs, and should therefore never be
-  // renumbered nor reused. They are public for testing only.
-  enum class NativeBridgeType {
-    // Native bridge value has not been received from the container yet.
-    UNKNOWN = 0,
-    // Native bridge is not used.
-    NONE = 1,
-    // Using houdini translator.
-    HOUDINI = 2,
-    // Using ndk-translation translator.
-    NDK_TRANSLATION = 3,
-    kMaxValue = NDK_TRANSLATION,
-  };
-
   // Delegate for handling window focus observation that is used to track ARC
   // app usage metrics.
   class ArcWindowDelegate {
@@ -107,10 +93,6 @@
                           mojom::BootType boot_type) override;
   void ReportNativeBridge(mojom::NativeBridgeType native_bridge_type) override;
 
-  // Records native bridge UMA according to value received from the
-  // container or as UNKNOWN if the value has not been recieved yet.
-  void RecordNativeBridgeUMA();
-
   // wm::ActivationChangeObserver overrides.
   // Records to UMA when a user has interacted with an ARC app window.
   void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason,
@@ -132,10 +114,6 @@
                      const std::string& intent);
   void OnTaskDestroyed(int32_t task_id);
 
-  NativeBridgeType native_bridge_type_for_testing() const {
-    return native_bridge_type_;
-  }
-
  private:
   // Adapter to be able to also observe ProcessInstance events.
   class ProcessObserver : public ConnectionObserver<mojom::ProcessInstance> {
@@ -193,8 +171,6 @@
   ProcessObserver process_observer_;
   base::RepeatingTimer request_process_list_timer_;
 
-  NativeBridgeType native_bridge_type_;
-
   PrefService* const pref_service_;
   const base::Clock* clock_;
   const base::TickClock* tick_clock_;
diff --git a/components/arc/metrics/arc_metrics_service_unittest.cc b/components/arc/metrics/arc_metrics_service_unittest.cc
index 5f86cf1..4e7d091 100644
--- a/components/arc/metrics/arc_metrics_service_unittest.cc
+++ b/components/arc/metrics/arc_metrics_service_unittest.cc
@@ -23,7 +23,9 @@
 #include "components/arc/arc_prefs.h"
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/metrics/arc_metrics_constants.h"
+#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/test/test_browser_context.h"
+#include "components/prefs/testing_pref_service.h"
 #include "components/session_manager/core/session_manager.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -138,6 +140,22 @@
   }
 };
 
+// Helper class that ensures lifetime of StabilityMetricsManager for testing.
+class ScopedStabilityMetricsManager {
+ public:
+  ScopedStabilityMetricsManager() {
+    prefs::RegisterLocalStatePrefs(local_state_.registry());
+    StabilityMetricsManager::Initialize(&local_state_);
+  }
+
+  ~ScopedStabilityMetricsManager() { StabilityMetricsManager::Shutdown(); }
+
+ private:
+  TestingPrefServiceSimple local_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedStabilityMetricsManager);
+};
+
 // Initializes dependencies before creating ArcMetricsService instance.
 ArcMetricsService* CreateArcMetricsService(TestBrowserContext* context) {
   // Register preferences for ARC++ engagement time metrics.
@@ -239,10 +257,11 @@
   content::TestBrowserThreadBundle thread_bundle_;
   std::unique_ptr<ArcServiceManager> arc_service_manager_;
 
-  // DBusThreadManager and SessionManager should outlive TestBrowserContext
-  // which destructs ArcMetricsService in dtor.
+  // DBusThreadManager, SessionManager and StabilityMetricsManager should
+  // outlive TestBrowserContext which destructs ArcMetricsService in dtor.
   DBusThreadManagerLifetimeHelper dbus_thread_manager_lifetime_helper_;
   session_manager::SessionManager session_manager_;
+  ScopedStabilityMetricsManager scoped_stability_metrics_manager_;
   std::unique_ptr<TestBrowserContext> context_;
 
   std::unique_ptr<aura::Window> fake_arc_window_;
@@ -376,46 +395,19 @@
 }
 
 TEST_F(ArcMetricsServiceTest, ReportNativeBridge) {
-  EXPECT_EQ(service()->native_bridge_type_for_testing(),
-            ArcMetricsService::NativeBridgeType::UNKNOWN);
+  // SetArcNativeBridgeType should be called once ArcMetricsService is
+  // constructed.
+  EXPECT_EQ(StabilityMetricsManager::Get()->GetArcNativeBridgeType(),
+            NativeBridgeType::UNKNOWN);
   service()->ReportNativeBridge(mojom::NativeBridgeType::NONE);
-  EXPECT_EQ(service()->native_bridge_type_for_testing(),
-            ArcMetricsService::NativeBridgeType::NONE);
+  EXPECT_EQ(StabilityMetricsManager::Get()->GetArcNativeBridgeType(),
+            NativeBridgeType::NONE);
   service()->ReportNativeBridge(mojom::NativeBridgeType::HOUDINI);
-  EXPECT_EQ(service()->native_bridge_type_for_testing(),
-            ArcMetricsService::NativeBridgeType::HOUDINI);
+  EXPECT_EQ(StabilityMetricsManager::Get()->GetArcNativeBridgeType(),
+            NativeBridgeType::HOUDINI);
   service()->ReportNativeBridge(mojom::NativeBridgeType::NDK_TRANSLATION);
-  EXPECT_EQ(service()->native_bridge_type_for_testing(),
-            ArcMetricsService::NativeBridgeType::NDK_TRANSLATION);
-}
-
-TEST_F(ArcMetricsServiceTest, RecordNativeBridgeUMA) {
-  base::HistogramTester tester;
-  service()->RecordNativeBridgeUMA();
-  tester.ExpectUniqueSample(
-      "Arc.NativeBridge",
-      static_cast<int>(ArcMetricsService::NativeBridgeType::UNKNOWN), 1);
-  service()->ReportNativeBridge(mojom::NativeBridgeType::NONE);
-  // Check that ReportNativeBridge doesn't record histograms.
-  tester.ExpectTotalCount("Arc.NativeBridge", 1);
-  service()->RecordNativeBridgeUMA();
-  tester.ExpectBucketCount(
-      "Arc.NativeBridge",
-      static_cast<int>(ArcMetricsService::NativeBridgeType::NONE), 1);
-  service()->ReportNativeBridge(mojom::NativeBridgeType::HOUDINI);
-  tester.ExpectTotalCount("Arc.NativeBridge", 2);
-  service()->RecordNativeBridgeUMA();
-  tester.ExpectBucketCount(
-      "Arc.NativeBridge",
-      static_cast<int>(ArcMetricsService::NativeBridgeType::HOUDINI), 1);
-  service()->ReportNativeBridge(mojom::NativeBridgeType::NDK_TRANSLATION);
-  tester.ExpectTotalCount("Arc.NativeBridge", 3);
-  service()->RecordNativeBridgeUMA();
-  tester.ExpectBucketCount(
-      "Arc.NativeBridge",
-      static_cast<int>(ArcMetricsService::NativeBridgeType::NDK_TRANSLATION),
-      1);
-  tester.ExpectTotalCount("Arc.NativeBridge", 4);
+  EXPECT_EQ(StabilityMetricsManager::Get()->GetArcNativeBridgeType(),
+            NativeBridgeType::NDK_TRANSLATION);
 }
 
 TEST_F(ArcMetricsServiceTest, RecordArcWindowFocusAction) {
diff --git a/components/arc/metrics/stability_metrics_manager.cc b/components/arc/metrics/stability_metrics_manager.cc
new file mode 100644
index 0000000..8bd1c59
--- /dev/null
+++ b/components/arc/metrics/stability_metrics_manager.cc
@@ -0,0 +1,112 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/arc/metrics/stability_metrics_manager.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "components/arc/arc_prefs.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+
+namespace arc {
+
+namespace {
+
+constexpr char kArcEnabledStateKey[] = "enabled_state";
+constexpr char kArcNativeBridgeTypeKey[] = "native_bridge_type";
+
+StabilityMetricsManager* g_stability_metrics_manager = nullptr;
+
+}  // namespace
+
+// static
+void StabilityMetricsManager::Initialize(PrefService* local_state) {
+  DCHECK(!g_stability_metrics_manager);
+  g_stability_metrics_manager = new StabilityMetricsManager(local_state);
+}
+
+// static
+void StabilityMetricsManager::Shutdown() {
+  DCHECK(g_stability_metrics_manager);
+  delete g_stability_metrics_manager;
+  g_stability_metrics_manager = nullptr;
+}
+
+// static
+StabilityMetricsManager* StabilityMetricsManager::Get() {
+  return g_stability_metrics_manager;
+}
+
+StabilityMetricsManager::StabilityMetricsManager(PrefService* local_state)
+    : local_state_(local_state) {}
+
+StabilityMetricsManager::~StabilityMetricsManager() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void StabilityMetricsManager::RecordMetricsToUMA() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // GetDictionary() should never return null, but since this may be called
+  // early on browser startup, be paranoid here to prevent going into a crash
+  // loop.
+  if (!local_state_->GetDictionary(prefs::kStabilityMetrics)) {
+    NOTREACHED() << "Local state unavailable, not recording stabiltiy metrics.";
+    return;
+  }
+
+  const base::Optional<bool> enabled_state = GetArcEnabledState();
+  if (enabled_state)
+    UMA_STABILITY_HISTOGRAM_ENUMERATION("Arc.State", *enabled_state ? 1 : 0, 2);
+
+  const base::Optional<NativeBridgeType> native_bridge_type =
+      GetArcNativeBridgeType();
+  if (native_bridge_type) {
+    UMA_STABILITY_HISTOGRAM_ENUMERATION(
+        "Arc.NativeBridge", *native_bridge_type,
+        static_cast<int>(NativeBridgeType::kMaxValue) + 1);
+  }
+}
+
+void StabilityMetricsManager::ResetMetrics() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DictionaryPrefUpdate update(local_state_, prefs::kStabilityMetrics);
+  update->Clear();
+}
+
+base::Optional<bool> StabilityMetricsManager::GetArcEnabledState() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const base::DictionaryValue* dict =
+      local_state_->GetDictionary(prefs::kStabilityMetrics);
+  return dict->FindBoolKey(kArcEnabledStateKey);
+}
+
+void StabilityMetricsManager::SetArcEnabledState(bool enabled) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DictionaryPrefUpdate update(local_state_, prefs::kStabilityMetrics);
+  update->SetKey(kArcEnabledStateKey, base::Value(enabled));
+}
+
+base::Optional<NativeBridgeType>
+StabilityMetricsManager::GetArcNativeBridgeType() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const base::DictionaryValue* dict =
+      local_state_->GetDictionary(prefs::kStabilityMetrics);
+  base::Optional<int> native_bridge_type =
+      dict->FindIntKey(kArcNativeBridgeTypeKey);
+  if (native_bridge_type) {
+    return base::make_optional(
+        static_cast<NativeBridgeType>(*native_bridge_type));
+  }
+  return base::nullopt;
+}
+
+void StabilityMetricsManager::SetArcNativeBridgeType(
+    NativeBridgeType native_bridge_type) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DictionaryPrefUpdate update(local_state_, prefs::kStabilityMetrics);
+  update->SetKey(kArcNativeBridgeTypeKey,
+                 base::Value(static_cast<int>(native_bridge_type)));
+}
+
+}  // namespace arc
diff --git a/components/arc/metrics/stability_metrics_manager.h b/components/arc/metrics/stability_metrics_manager.h
new file mode 100644
index 0000000..3e852cc
--- /dev/null
+++ b/components/arc/metrics/stability_metrics_manager.h
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
+#define COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
+
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "components/arc/metrics/arc_metrics_constants.h"
+
+class PrefService;
+
+namespace arc {
+
+// Singleton instance that keeps track of ARC-related stability metrics. In case
+// of a crash, stability metrics from previous session are included in initial
+// stability log, which is generated early on browser startup when ARC services
+// aren't available. This class supports recording stability metrics by
+// persisting them into local state.
+class StabilityMetricsManager {
+ public:
+  static void Initialize(PrefService* local_state);
+  static void Shutdown();
+
+  // May return null if not initialized, which happens only in unit tests.
+  static StabilityMetricsManager* Get();
+
+  // Reads metrics from |local_state_| and record to UMA. Called from
+  // ChromeOSMetricsProvider to include stability metrics in all uploaded UMA
+  // logs.
+  void RecordMetricsToUMA();
+
+  // Resets metrics persisted in |local_state_|. Called from ArcSessionManager
+  // which determines whether stability metrics should be recorded for current
+  // session,
+  void ResetMetrics();
+
+  // Returns current persisted value (if exists) for Arc.State UMA histogram.
+  base::Optional<bool> GetArcEnabledState();
+
+  // Sets value for Arc.State UMA histogram.
+  void SetArcEnabledState(bool enabled);
+
+  // Returns current persisted value (if exists) for Arc.State UMA histogram.
+  base::Optional<NativeBridgeType> GetArcNativeBridgeType();
+
+  // Sets value for Arc.NativeBridgeType UMA histogram.
+  void SetArcNativeBridgeType(NativeBridgeType native_bridge_type);
+
+ private:
+  explicit StabilityMetricsManager(PrefService* local_state);
+  ~StabilityMetricsManager();
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  PrefService* const local_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(StabilityMetricsManager);
+};
+
+}  // namespace arc
+
+#endif  // COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
diff --git a/components/arc/metrics/stability_metrics_manager_unittest.cc b/components/arc/metrics/stability_metrics_manager_unittest.cc
new file mode 100644
index 0000000..5cc11c8
--- /dev/null
+++ b/components/arc/metrics/stability_metrics_manager_unittest.cc
@@ -0,0 +1,105 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/arc/metrics/stability_metrics_manager.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "components/arc/arc_prefs.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace arc {
+
+class StabilityMetricsManagerTest : public testing::Test {
+ protected:
+  StabilityMetricsManagerTest() {
+    prefs::RegisterLocalStatePrefs(local_state.registry());
+    StabilityMetricsManager::Initialize(&local_state);
+  }
+
+  ~StabilityMetricsManagerTest() override {
+    StabilityMetricsManager::Shutdown();
+  }
+
+  static StabilityMetricsManager* manager() {
+    return StabilityMetricsManager::Get();
+  }
+
+ private:
+  TestingPrefServiceSimple local_state;
+
+  DISALLOW_COPY_AND_ASSIGN(StabilityMetricsManagerTest);
+};
+
+TEST_F(StabilityMetricsManagerTest, GetArcEnabledState) {
+  EXPECT_FALSE(manager()->GetArcEnabledState().has_value());
+
+  manager()->SetArcEnabledState(true);
+  EXPECT_EQ(manager()->GetArcEnabledState(), true);
+
+  manager()->SetArcEnabledState(false);
+  EXPECT_EQ(manager()->GetArcEnabledState(), false);
+
+  manager()->ResetMetrics();
+  EXPECT_FALSE(manager()->GetArcEnabledState().has_value());
+}
+
+TEST_F(StabilityMetricsManagerTest, GetArcNativeBridgeType) {
+  EXPECT_FALSE(manager()->GetArcNativeBridgeType().has_value());
+
+  for (NativeBridgeType type :
+       {NativeBridgeType::UNKNOWN, NativeBridgeType::NONE,
+        NativeBridgeType::HOUDINI, NativeBridgeType::NDK_TRANSLATION}) {
+    manager()->SetArcNativeBridgeType(type);
+    EXPECT_EQ(manager()->GetArcNativeBridgeType(), type);
+  }
+
+  manager()->ResetMetrics();
+  EXPECT_FALSE(manager()->GetArcNativeBridgeType().has_value());
+}
+
+TEST_F(StabilityMetricsManagerTest, RecordEnabledStateToUMA) {
+  base::HistogramTester tester;
+
+  manager()->SetArcEnabledState(true);
+  manager()->RecordMetricsToUMA();
+  tester.ExpectBucketCount("Arc.State", 1, 1);
+
+  manager()->SetArcEnabledState(false);
+  manager()->RecordMetricsToUMA();
+  tester.ExpectBucketCount("Arc.State", 0, 1);
+}
+
+TEST_F(StabilityMetricsManagerTest, RecordNativeBridgeTypeToUMA) {
+  base::HistogramTester tester;
+
+  EXPECT_EQ(NativeBridgeType::kMaxValue, NativeBridgeType::NDK_TRANSLATION);
+  for (NativeBridgeType type :
+       {NativeBridgeType::UNKNOWN, NativeBridgeType::NONE,
+        NativeBridgeType::HOUDINI, NativeBridgeType::NDK_TRANSLATION}) {
+    manager()->SetArcNativeBridgeType(type);
+    manager()->RecordMetricsToUMA();
+    tester.ExpectBucketCount("Arc.NativeBridge", static_cast<int>(type), 1);
+  }
+}
+
+TEST_F(StabilityMetricsManagerTest, ResetMetrics) {
+  base::HistogramTester tester;
+
+  manager()->SetArcEnabledState(true);
+  manager()->SetArcNativeBridgeType(NativeBridgeType::NONE);
+  manager()->RecordMetricsToUMA();
+  tester.ExpectBucketCount("Arc.State", 1, 1);
+  tester.ExpectBucketCount("Arc.NativeBridge",
+                           static_cast<int>(NativeBridgeType::NONE), 1);
+
+  manager()->ResetMetrics();
+  manager()->RecordMetricsToUMA();
+  // Bucket counts should remain the same.
+  tester.ExpectBucketCount("Arc.State", 1, 1);
+  tester.ExpectBucketCount("Arc.NativeBridge",
+                           static_cast<int>(NativeBridgeType::NONE), 1);
+}
+
+}  // namespace arc
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 5ad4e400..0a08d764 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -4628,9 +4628,10 @@
   <summary>
     Native bridge used for ARC. Can be unknown and none. This is collected along
     with Arc.State during every metrics recording interval, so it is in every
-    record uploaded to the server. This is required because this value is used
-    to categorize all other data in the dashboard as collected with specific
-    native bridge in use.
+    record uploaded to the server (despite crbug.com/929583 which was fixed in
+    M73). This is required because this value is used to categorize all other
+    data in the dashboard as collected with specific native bridge in use. Since
+    M74 this is also included in initial stability logs.
   </summary>
 </histogram>
 
@@ -4845,11 +4846,12 @@
     Whether ARC is enabled or not. Before M56 this was collected only on login
     and when ARC was enabled or disabled. From M56 forward this is collected
     during every metrics recording interval, so it is in every record uploaded
-    to the server. This is required because this value is used to categorize all
-    other data in the dashboard as collected with ARC enabled or not. This is
-    true even for users who are running on ecryptfs and skipped Ext4 migration;
-    even though ARC Apps do not run until migration is complete on N or later
-    systems.
+    to the server (despite crbug.com/929583 which was fixed in M73). This is
+    required because this value is used to categorize all other data in the
+    dashboard as collected with ARC enabled or not. This is true even for users
+    who are running on ecryptfs and skipped Ext4 migration; even though ARC Apps
+    do not run until migration is complete on N or later systems. Since M74 this
+    is also included in initial stability logs.
   </summary>
 </histogram>