Extract code that is common to status collectors

Currently this change is a no-op, but it is possible that we need to
change it.

Bug: 827386
Change-Id: I2afc355bdc646cf0136b3d00a9a3d7d46cdaf3a2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1558024
Reviewed-by: Aga Wronska <agawronska@chromium.org>
Reviewed-by: Denis Kuznetsov <antrim@chromium.org>
Reviewed-by: Henrique Grandinetti <hgrandinetti@chromium.org>
Reviewed-by: Lutz Justen <ljusten@chromium.org>
Commit-Queue: Marcelo Almeida <marcelocra@chromium.org>
Cr-Commit-Position: refs/heads/master@{#653173}
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 367de34..6430b06 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1673,10 +1673,14 @@
     "policy/server_backed_state_keys_broker.h",
     "policy/single_app_install_event_log.cc",
     "policy/single_app_install_event_log.h",
+    "policy/status_collector/activity_storage.cc",
+    "policy/status_collector/activity_storage.h",
     "policy/status_collector/device_status_collector.cc",
     "policy/status_collector/device_status_collector.h",
     "policy/status_collector/status_collector.cc",
     "policy/status_collector/status_collector.h",
+    "policy/status_collector/status_collector_state.cc",
+    "policy/status_collector/status_collector_state.h",
     "policy/status_uploader.cc",
     "policy/status_uploader.h",
     "policy/system_log_uploader.cc",
diff --git a/chrome/browser/chromeos/policy/status_collector/activity_storage.cc b/chrome/browser/chromeos/policy/status_collector/activity_storage.cc
new file mode 100644
index 0000000..08a85f8
--- /dev/null
+++ b/chrome/browser/chromeos/policy/status_collector/activity_storage.cc
@@ -0,0 +1,251 @@
+// 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 "chrome/browser/chromeos/policy/status_collector/activity_storage.h"
+
+#include "base/base64.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/user_manager/user_manager.h"
+
+namespace policy {
+namespace {
+
+using ::base::Time;
+using ::base::TimeDelta;
+
+// Activity periods are keyed with day and user in format:
+// '<day_timestamp>:<BASE64 encoded user email>'
+constexpr char kActivityKeySeparator = ':';
+
+}  // namespace
+
+ActivityStorage::ActivityStorage(PrefService* pref_service,
+                                 const std::string& pref_name,
+                                 TimeDelta activity_day_start,
+                                 bool is_enterprise_reporting)
+    : pref_service_(pref_service),
+      pref_name_(pref_name),
+      day_start_(activity_day_start),
+      is_enterprise_reporting_(is_enterprise_reporting) {
+  DCHECK(pref_service_);
+  const PrefService::PrefInitializationStatus pref_service_status =
+      pref_service_->GetInitializationStatus();
+  DCHECK(pref_service_status != PrefService::INITIALIZATION_STATUS_WAITING &&
+         pref_service_status != PrefService::INITIALIZATION_STATUS_ERROR);
+}
+
+ActivityStorage::~ActivityStorage() = default;
+
+void ActivityStorage::AddActivityPeriod(Time start,
+                                        Time end,
+                                        Time now,
+                                        const std::string& active_user_email) {
+  DCHECK(start <= end);
+
+  // Maintain the list of active periods in a local_state pref.
+  DictionaryPrefUpdate update(pref_service_, pref_name_);
+  base::DictionaryValue* activity_times = update.Get();
+
+  // Assign the period to day buckets in local time.
+  Time day_start = start.LocalMidnight() + day_start_;
+  if (start < day_start)
+    day_start -= TimeDelta::FromDays(1);
+  while (day_start < end) {
+    day_start += TimeDelta::FromDays(1);
+    int64_t activity = (std::min(end, day_start) - start).InMilliseconds();
+    const std::string key =
+        MakeActivityPeriodPrefKey(TimestampToDayKey(start), active_user_email);
+    int previous_activity = 0;
+    activity_times->GetInteger(key, &previous_activity);
+    activity_times->SetInteger(key, previous_activity + activity);
+
+    // If the user is a child, the child screen time pref may need to be
+    // updated.
+    if (user_manager::UserManager::Get()->IsLoggedInAsChildUser() &&
+        !is_enterprise_reporting_) {
+      StoreChildScreenTime(day_start - TimeDelta::FromDays(1),
+                           TimeDelta::FromMilliseconds(activity), now);
+    }
+
+    start = day_start;
+  }
+}
+
+void ActivityStorage::PruneActivityPeriods(
+    Time base_time,
+    TimeDelta max_past_activity_interval,
+    TimeDelta max_future_activity_interval) {
+  Time min_time = base_time - max_past_activity_interval;
+  Time max_time = base_time + max_future_activity_interval;
+  TrimActivityPeriods(TimestampToDayKey(min_time), 0,
+                      TimestampToDayKey(max_time));
+}
+
+void ActivityStorage::TrimActivityPeriods(int64_t min_day_key,
+                                          int min_day_trim_duration,
+                                          int64_t max_day_key) {
+  const base::DictionaryValue* activity_times =
+      pref_service_->GetDictionary(pref_name_);
+
+  std::unique_ptr<base::DictionaryValue> copy(activity_times->DeepCopy());
+  for (base::DictionaryValue::Iterator it(*activity_times); !it.IsAtEnd();
+       it.Advance()) {
+    int64_t timestamp;
+    std::string active_user_email;
+    if (ParseActivityPeriodPrefKey(it.key(), &timestamp, &active_user_email)) {
+      // Remove data that is too old, or too far in the future.
+      if (timestamp >= min_day_key && timestamp < max_day_key) {
+        if (timestamp == min_day_key) {
+          int new_activity_duration = 0;
+          if (it.value().GetAsInteger(&new_activity_duration)) {
+            new_activity_duration =
+                std::max(new_activity_duration - min_day_trim_duration, 0);
+          }
+          copy->SetInteger(it.key(), new_activity_duration);
+        }
+        continue;
+      }
+    }
+    // The entry is out of range or couldn't be parsed. Remove it.
+    copy->Remove(it.key(), NULL);
+  }
+  pref_service_->Set(pref_name_, *copy);
+}
+
+void ActivityStorage::FilterActivityPeriodsByUsers(
+    const std::vector<std::string>& reporting_users) {
+  const base::DictionaryValue* stored_activity_periods =
+      pref_service_->GetDictionary(pref_name_);
+  base::DictionaryValue filtered_activity_periods;
+  ProcessActivityPeriods(*stored_activity_periods, reporting_users,
+                         &filtered_activity_periods);
+  pref_service_->Set(pref_name_, filtered_activity_periods);
+}
+
+std::vector<ActivityStorage::ActivityPeriod>
+ActivityStorage::GetFilteredActivityPeriods(bool omit_emails) {
+  DictionaryPrefUpdate update(pref_service_, pref_name_);
+  base::DictionaryValue* stored_activity_periods = update.Get();
+
+  base::DictionaryValue filtered_activity_periods;
+  if (omit_emails) {
+    std::vector<std::string> empty_user_list;
+    ProcessActivityPeriods(*stored_activity_periods, empty_user_list,
+                           &filtered_activity_periods);
+    stored_activity_periods = &filtered_activity_periods;
+  }
+
+  std::vector<ActivityPeriod> activity_periods;
+  for (base::DictionaryValue::Iterator it(*stored_activity_periods);
+       !it.IsAtEnd(); it.Advance()) {
+    ActivityPeriod activity_period;
+    if (ParseActivityPeriodPrefKey(it.key(), &activity_period.start_timestamp,
+                                   &activity_period.user_email) &&
+        it.value().GetAsInteger(&activity_period.activity_milliseconds)) {
+      activity_periods.push_back(activity_period);
+    }
+  }
+  return activity_periods;
+}
+
+// static
+std::string ActivityStorage::MakeActivityPeriodPrefKey(
+    int64_t start,
+    const std::string& user_email) {
+  const std::string day_key = base::NumberToString(start);
+  if (user_email.empty())
+    return day_key;
+
+  std::string encoded_email;
+  base::Base64Encode(user_email, &encoded_email);
+  return day_key + kActivityKeySeparator + encoded_email;
+}
+
+// static
+bool ActivityStorage::ParseActivityPeriodPrefKey(const std::string& key,
+                                                 int64_t* start_timestamp,
+                                                 std::string* user_email) {
+  auto separator_pos = key.find(kActivityKeySeparator);
+  if (separator_pos == std::string::npos) {
+    user_email->clear();
+    return base::StringToInt64(key, start_timestamp);
+  }
+  return base::StringToInt64(key.substr(0, separator_pos), start_timestamp) &&
+         base::Base64Decode(key.substr(separator_pos + 1), user_email);
+}
+
+void ActivityStorage::ProcessActivityPeriods(
+    const base::DictionaryValue& activity_times,
+    const std::vector<std::string>& reporting_users,
+    base::DictionaryValue* const filtered_times) {
+  std::set<std::string> reporting_users_set(reporting_users.begin(),
+                                            reporting_users.end());
+  const std::string empty;
+  for (const auto& it : activity_times.DictItems()) {
+    DCHECK(it.second.is_int());
+    int64_t timestamp;
+    std::string user_email;
+    if (!ParseActivityPeriodPrefKey(it.first, &timestamp, &user_email))
+      continue;
+    if (!user_email.empty() && reporting_users_set.count(user_email) == 0) {
+      int value = 0;
+      std::string timestamp_str = MakeActivityPeriodPrefKey(timestamp, empty);
+      const base::Value* prev_value = filtered_times->FindKeyOfType(
+          timestamp_str, base::Value::Type::INTEGER);
+      if (prev_value)
+        value = prev_value->GetInt();
+      filtered_times->SetKey(timestamp_str,
+                             base::Value(value + it.second.GetInt()));
+    } else {
+      filtered_times->SetKey(it.first, it.second.Clone());
+    }
+  }
+}
+
+int64_t ActivityStorage::TimestampToDayKey(Time timestamp) {
+  Time::Exploded exploded;
+  Time day_start = timestamp.LocalMidnight() + day_start_;
+  if (timestamp < day_start)
+    day_start -= TimeDelta::FromDays(1);
+  day_start.LocalExplode(&exploded);
+  Time out_time;
+  bool conversion_success = Time::FromUTCExploded(exploded, &out_time);
+  DCHECK(conversion_success);
+  return out_time.ToJavaTime();
+}
+
+void ActivityStorage::StoreChildScreenTime(Time activity_day_start,
+                                           TimeDelta activity,
+                                           Time now) {
+  DCHECK(user_manager::UserManager::Get()->IsLoggedInAsChildUser() &&
+         !is_enterprise_reporting_);
+
+  // Today's start time.
+  Time today_start = now.LocalMidnight() + day_start_;
+  if (today_start > now)
+    today_start -= TimeDelta::FromDays(1);
+
+  // The activity windows always start and end on the reset time of two
+  // consecutive days, so it is not possible to have a window starting after
+  // the current day's reset time.
+  DCHECK(activity_day_start <= today_start);
+
+  TimeDelta previous_activity = TimeDelta::FromMilliseconds(
+      pref_service_->GetInteger(prefs::kChildScreenTimeMilliseconds));
+
+  // If this activity window belongs to the current day, the screen time pref
+  // should be updated.
+  if (activity_day_start == today_start) {
+    pref_service_->SetInteger(prefs::kChildScreenTimeMilliseconds,
+                              (previous_activity + activity).InMilliseconds());
+    pref_service_->SetTime(prefs::kLastChildScreenTimeSaved, now);
+  }
+  pref_service_->CommitPendingWrite();
+}
+
+}  // namespace policy
diff --git a/chrome/browser/chromeos/policy/status_collector/activity_storage.h b/chrome/browser/chromeos/policy/status_collector/activity_storage.h
new file mode 100644
index 0000000..b39bbee
--- /dev/null
+++ b/chrome/browser/chromeos/policy/status_collector/activity_storage.h
@@ -0,0 +1,115 @@
+// 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 CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_ACTIVITY_STORAGE_H_
+#define CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_ACTIVITY_STORAGE_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+class PrefService;
+
+namespace policy {
+
+// Handles storing activity time periods needed for reporting. Provides
+// filtering of the user identifying data.
+//
+// TODO(crbug.com/827386): refactor this class, removing child related parts.
+class ActivityStorage {
+ public:
+  // Stored activity period.
+  struct ActivityPeriod {
+    // Email can be empty.
+    std::string user_email;
+
+    // Timestamp dating the beginning of the captured activity.
+    int64_t start_timestamp;
+
+    // User's activity in milliseconds.
+    int activity_milliseconds;
+  };
+
+  // Creates activity storage. Activity data will be stored in the given
+  // |pref_service| under |pref_name| preference. Activity data are aggregated
+  // by day. The start of the new day is counted from |activity_day_start| that
+  // represents the distance from midnight.
+  ActivityStorage(PrefService* pref_service,
+                  const std::string& pref_name,
+                  base::TimeDelta activity_day_start,
+                  bool is_enterprise_reporting);
+  ~ActivityStorage();
+
+  // Adds an activity period. Accepts empty |active_user_email| if it should not
+  // be stored.
+  void AddActivityPeriod(base::Time start,
+                         base::Time end,
+                         base::Time now,
+                         const std::string& active_user_email);
+
+  // Clears stored activity periods outside of storage range defined by
+  // |max_past_activity_interval| and |max_future_activity_interval| from
+  // |base_time|.
+  void PruneActivityPeriods(base::Time base_time,
+                            base::TimeDelta max_past_activity_interval,
+                            base::TimeDelta max_future_activity_interval);
+
+  // Trims the store activity periods to only retain data within the
+  // [|min_day_key|, |max_day_key|). The record for |min_day_key| will be
+  // adjusted by subtracting |min_day_trim_duration|.
+  void TrimActivityPeriods(int64_t min_day_key,
+                           int min_day_trim_duration,
+                           int64_t max_day_key);
+
+  // Updates stored activity period according to users' reporting preferences.
+  // Removes user's email and aggregates the activity data if user's information
+  // should no longer be reported.
+  void FilterActivityPeriodsByUsers(
+      const std::vector<std::string>& reporting_users);
+
+  // Returns the list of stored activity periods. Aggregated data are returned
+  // without email addresses if |omit_emails| is set.
+  std::vector<ActivityPeriod> GetFilteredActivityPeriods(bool omit_emails);
+
+ private:
+  static std::string MakeActivityPeriodPrefKey(int64_t start,
+                                               const std::string& user_email);
+  static bool ParseActivityPeriodPrefKey(const std::string& key,
+                                         int64_t* start_timestamp,
+                                         std::string* user_email);
+  void ProcessActivityPeriods(const base::DictionaryValue& activity_times,
+                              const std::vector<std::string>& reporting_users,
+                              base::DictionaryValue* const filtered_times);
+  void StoreChildScreenTime(base::Time activity_day_start,
+                            base::TimeDelta activity,
+                            base::Time now);
+
+  // Determine the day key (milliseconds since epoch for corresponding
+  // |day_start_| in UTC) for a given |timestamp|.
+  int64_t TimestampToDayKey(base::Time timestamp);
+
+  PrefService* const pref_service_ = nullptr;
+  const std::string pref_name_;
+
+  // New day start time used for aggregating data represented by the distance
+  // from midnight.
+  const base::TimeDelta day_start_;
+
+  // Whether reporting is for enterprise or consumer.
+  bool is_enterprise_reporting_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(ActivityStorage);
+};
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_ACTIVITY_STORAGE_H_
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
index b600316..651f640 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
@@ -14,7 +14,6 @@
 #include <sstream>
 #include <utility>
 
-#include "base/base64.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/feature_list.h"
@@ -22,8 +21,11 @@
 #include "base/files/file_util.h"
 #include "base/format_macros.h"
 #include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/optional.h"
 #include "base/posix/eintr_wrapper.h"
+#include "base/sequenced_task_runner.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
@@ -31,6 +33,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/system/sys_info.h"
 #include "base/task/post_task.h"
+#include "base/task/task_traits.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/values.h"
 #include "base/version.h"
@@ -41,6 +44,8 @@
 #include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/policy/device_local_account.h"
+#include "chrome/browser/chromeos/policy/status_collector/activity_storage.h"
+#include "chrome/browser/chromeos/policy/status_collector/status_collector_state.h"
 #include "chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h"
 #include "chrome/browser/chromeos/policy/user_policy_manager_factory_chromeos.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -77,7 +82,6 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
-#include "components/prefs/scoped_user_pref_update.h"
 #include "components/session_manager/session_manager_types.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
@@ -116,10 +120,6 @@
 const char kHwmonDirectoryPattern[] = "hwmon*";
 const char kCPUTempFilePattern[] = "temp*_input";
 
-// Activity periods are keyed with day and user in format:
-// '<day_timestamp>:<BASE64 encoded user email>'
-constexpr char kActivityKeySeparator = ':';
-
 // How often the child's usage time is stored.
 static constexpr base::TimeDelta kUpdateChildActiveTimeInterval =
     base::TimeDelta::FromSeconds(30);
@@ -354,41 +354,16 @@
 
 namespace policy {
 
-// Helper class for state tracking of async status queries. Creates device and
-// session status blobs in the constructor and sends them to the the status
-// response callback in the destructor.
-//
-// Some methods like |SampleVolumeInfo| queue async queries to collect data. The
-// response callback of these queries, e.g. |OnVolumeInfoReceived|, holds a
-// reference to the instance of this class, so that the destructor will not be
-// invoked and the status response callback will not be fired until the original
-// owner of the instance releases its reference and all async queries finish.
-//
-// Therefore, if you create an instance of this class, make sure to release your
-// reference after quering all async queries (if any), e.g. by using a local
-// |scoped_refptr<GetStatusState>| and letting it go out of scope.
-class GetStatusState : public base::RefCountedThreadSafe<GetStatusState> {
+class DeviceStatusCollectorState : public StatusCollectorState {
  public:
-  explicit GetStatusState(
+  explicit DeviceStatusCollectorState(
       const scoped_refptr<base::SequencedTaskRunner> task_runner,
-      const policy::StatusCollectorCallback& response)
-      : task_runner_(task_runner), response_(response) {}
-
-  inline em::DeviceStatusReportRequest* device_status() {
-    return response_params_.device_status.get();
-  }
-
-  inline em::SessionStatusReportRequest* session_status() {
-    return response_params_.session_status.get();
-  }
-
-  inline void ResetDeviceStatus() { response_params_.device_status.reset(); }
-
-  inline void ResetSessionStatus() { response_params_.session_status.reset(); }
+      const StatusCollectorCallback& response)
+      : StatusCollectorState(task_runner, response) {}
 
   // Queues an async callback to query disk volume information.
-  void SampleVolumeInfo(const policy::DeviceStatusCollector::VolumeInfoFetcher&
-                            volume_info_fetcher) {
+  void SampleVolumeInfo(
+      const DeviceStatusCollector::VolumeInfoFetcher& volume_info_fetcher) {
     // Create list of mounted disk volumes to query status.
     std::vector<storage::MountPoints::MountPointInfo> external_mount_points;
     storage::ExternalMountPoints::GetSystemInstance()->AddMountPointInfosTo(
@@ -408,48 +383,40 @@
     base::PostTaskWithTraitsAndReplyWithResult(
         FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
         base::Bind(volume_info_fetcher, mount_points),
-        base::Bind(&GetStatusState::OnVolumeInfoReceived, this));
+        base::Bind(&DeviceStatusCollectorState::OnVolumeInfoReceived, this));
   }
 
   // Queues an async callback to query CPU temperature information.
   void SampleCPUTempInfo(
-      const policy::DeviceStatusCollector::CPUTempFetcher& cpu_temp_fetcher) {
+      const DeviceStatusCollector::CPUTempFetcher& cpu_temp_fetcher) {
     // Call out to the blocking pool to sample CPU temp.
     base::PostTaskWithTraitsAndReplyWithResult(
         FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
         cpu_temp_fetcher,
-        base::Bind(&GetStatusState::OnCPUTempInfoReceived, this));
+        base::Bind(&DeviceStatusCollectorState::OnCPUTempInfoReceived, this));
   }
 
-  bool FetchAndroidStatus(
-      const policy::DeviceStatusCollector::AndroidStatusFetcher&
-          android_status_fetcher) {
+  bool FetchAndroidStatus(const DeviceStatusCollector::AndroidStatusFetcher&
+                              android_status_fetcher) {
     return android_status_fetcher.Run(
-        base::Bind(&GetStatusState::OnAndroidInfoReceived, this));
+        base::Bind(&DeviceStatusCollectorState::OnAndroidInfoReceived, this));
   }
 
-  void FetchTpmStatus(const policy::DeviceStatusCollector::TpmStatusFetcher&
-                          tpm_status_fetcher) {
+  void FetchTpmStatus(
+      const DeviceStatusCollector::TpmStatusFetcher& tpm_status_fetcher) {
     tpm_status_fetcher.Run(
-        base::BindOnce(&GetStatusState::OnTpmStatusReceived, this));
+        base::BindOnce(&DeviceStatusCollectorState::OnTpmStatusReceived, this));
   }
 
   void FetchProbeData(
-      policy::DeviceStatusCollector::ProbeDataFetcher probe_data_fetcher) {
+      DeviceStatusCollector::ProbeDataFetcher probe_data_fetcher) {
     std::move(probe_data_fetcher)
-        .Run(base::BindOnce(&GetStatusState::OnProbeDataReceived, this));
+        .Run(base::BindOnce(&DeviceStatusCollectorState::OnProbeDataReceived,
+                            this));
   }
 
  private:
-  friend class RefCountedThreadSafe<GetStatusState>;
-
-  // Posts the response on the UI thread. As long as there is an outstanding
-  // async query, the query holds a reference to us, so the destructor is
-  // not called.
-  ~GetStatusState() {
-    task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(response_, base::Passed(&response_params_)));
-  }
+  ~DeviceStatusCollectorState() override = default;
 
   void OnVolumeInfoReceived(const std::vector<em::VolumeInfo>& volume_info) {
     response_params_.device_status->clear_volume_infos();
@@ -569,97 +536,6 @@
       }
     }
   }
-
-  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  policy::StatusCollectorCallback response_;
-  StatusCollectorParams response_params_;
-};
-
-// Handles storing activity time periods needed for reporting. Provides
-// filtering of the user identifying data.
-class DeviceStatusCollector::ActivityStorage {
- public:
-  // Stored activity period.
-  struct ActivityPeriod {
-    // Email can be empty.
-    std::string user_email;
-
-    // Timestamp dating the beginning of the captured activity.
-    int64_t start_timestamp;
-
-    // User's activity in milliseconds.
-    int activity_milliseconds;
-  };
-
-  // Creates activity storage. Activity data will be stored in the given
-  // |pref_service| under |pref_name| preference. Activity data are aggregated
-  // by day. The start of the new day is counted from |activity_day_start| that
-  // represents the distance from midnight.
-  ActivityStorage(PrefService* pref_service,
-                  const std::string& pref_name,
-                  TimeDelta activity_day_start,
-                  bool is_enterprise_reporting);
-  ~ActivityStorage();
-
-  // Adds an activity period. Accepts empty |active_user_email| if it should not
-  // be stored.
-  void AddActivityPeriod(Time start,
-                         Time end,
-                         Time now,
-                         const std::string& active_user_email);
-
-  // Clears stored activity periods outside of storage range defined by
-  // |max_past_activity_interval| and |max_future_activity_interval| from
-  // |base_time|.
-  void PruneActivityPeriods(Time base_time,
-                            TimeDelta max_past_activity_interval,
-                            TimeDelta max_future_activity_interval);
-
-  // Trims the store activity periods to only retain data within the
-  // [|min_day_key|, |max_day_key|). The record for |min_day_key| will be
-  // adjusted by subtracting |min_day_trim_duration|.
-  void TrimActivityPeriods(int64_t min_day_key,
-                           int min_day_trim_duration,
-                           int64_t max_day_key);
-
-  // Updates stored activity period according to users' reporting preferences.
-  // Removes user's email and aggregates the activity data if user's information
-  // should no longer be reported.
-  void FilterActivityPeriodsByUsers(
-      const std::vector<std::string>& reporting_users);
-
-  // Returns the list of stored activity periods. Aggregated data are returned
-  // without email addresses if |omit_emails| is set.
-  std::vector<ActivityPeriod> GetFilteredActivityPeriods(bool omit_emails);
-
- private:
-  static std::string MakeActivityPeriodPrefKey(int64_t start,
-                                               const std::string& user_email);
-  static bool ParseActivityPeriodPrefKey(const std::string& key,
-                                         int64_t* start_timestamp,
-                                         std::string* user_email);
-  void ProcessActivityPeriods(const base::DictionaryValue& activity_times,
-                              const std::vector<std::string>& reporting_users,
-                              base::DictionaryValue* const filtered_times);
-  void StoreChildScreenTime(Time activity_day_start,
-                            TimeDelta activity,
-                            Time now);
-
-  // Determine the day key (milliseconds since epoch for corresponding
-  // |day_start_| in UTC) for a given |timestamp|.
-  int64_t TimestampToDayKey(Time timestamp);
-
-  PrefService* const pref_service_ = nullptr;
-  const std::string pref_name_;
-
-  // New day start time used for aggregating data represented by the distance
-  // from midnight.
-  const TimeDelta day_start_;
-
-  // Whether reporting is for enterprise or consumer.
-  bool is_enterprise_reporting_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(ActivityStorage);
 };
 
 TpmStatusInfo::TpmStatusInfo() = default;
@@ -688,237 +564,6 @@
       boot_lockbox_finalized(boot_lockbox_finalized) {}
 TpmStatusInfo::~TpmStatusInfo() = default;
 
-DeviceStatusCollector::ActivityStorage::ActivityStorage(
-    PrefService* pref_service,
-    const std::string& pref_name,
-    TimeDelta activity_day_start,
-    bool is_enterprise_reporting)
-    : pref_service_(pref_service),
-      pref_name_(pref_name),
-      day_start_(activity_day_start),
-      is_enterprise_reporting_(is_enterprise_reporting) {
-  DCHECK(pref_service_);
-  const PrefService::PrefInitializationStatus pref_service_status =
-      pref_service_->GetInitializationStatus();
-  DCHECK(pref_service_status != PrefService::INITIALIZATION_STATUS_WAITING &&
-         pref_service_status != PrefService::INITIALIZATION_STATUS_ERROR);
-}
-
-DeviceStatusCollector::ActivityStorage::~ActivityStorage() = default;
-
-void DeviceStatusCollector::ActivityStorage::AddActivityPeriod(
-    Time start,
-    Time end,
-    Time now,
-    const std::string& active_user_email) {
-  DCHECK(start <= end);
-
-  // Maintain the list of active periods in a local_state pref.
-  DictionaryPrefUpdate update(pref_service_, pref_name_);
-  base::DictionaryValue* activity_times = update.Get();
-
-  // Assign the period to day buckets in local time.
-  Time day_start = start.LocalMidnight() + day_start_;
-  if (start < day_start)
-    day_start -= TimeDelta::FromDays(1);
-  while (day_start < end) {
-    day_start += TimeDelta::FromDays(1);
-    int64_t activity = (std::min(end, day_start) - start).InMilliseconds();
-    const std::string key =
-        MakeActivityPeriodPrefKey(TimestampToDayKey(start), active_user_email);
-    int previous_activity = 0;
-    activity_times->GetInteger(key, &previous_activity);
-    activity_times->SetInteger(key, previous_activity + activity);
-
-    // If the user is a child, the child screen time pref may need to be
-    // updated.
-    if (user_manager::UserManager::Get()->IsLoggedInAsChildUser() &&
-        !is_enterprise_reporting_) {
-      StoreChildScreenTime(day_start - TimeDelta::FromDays(1),
-                           TimeDelta::FromMilliseconds(activity), now);
-    }
-
-    start = day_start;
-  }
-}
-
-void DeviceStatusCollector::ActivityStorage::PruneActivityPeriods(
-    Time base_time,
-    TimeDelta max_past_activity_interval,
-    TimeDelta max_future_activity_interval) {
-  Time min_time = base_time - max_past_activity_interval;
-  Time max_time = base_time + max_future_activity_interval;
-  TrimActivityPeriods(TimestampToDayKey(min_time), 0,
-                      TimestampToDayKey(max_time));
-}
-
-void DeviceStatusCollector::ActivityStorage::TrimActivityPeriods(
-    int64_t min_day_key,
-    int min_day_trim_duration,
-    int64_t max_day_key) {
-  const base::DictionaryValue* activity_times =
-      pref_service_->GetDictionary(pref_name_);
-
-  std::unique_ptr<base::DictionaryValue> copy(activity_times->DeepCopy());
-  for (base::DictionaryValue::Iterator it(*activity_times); !it.IsAtEnd();
-       it.Advance()) {
-    int64_t timestamp;
-    std::string active_user_email;
-    if (ParseActivityPeriodPrefKey(it.key(), &timestamp, &active_user_email)) {
-      // Remove data that is too old, or too far in the future.
-      if (timestamp >= min_day_key && timestamp < max_day_key) {
-        if (timestamp == min_day_key) {
-          int new_activity_duration = 0;
-          if (it.value().GetAsInteger(&new_activity_duration)) {
-            new_activity_duration =
-                std::max(new_activity_duration - min_day_trim_duration, 0);
-          }
-          copy->SetInteger(it.key(), new_activity_duration);
-        }
-        continue;
-      }
-    }
-    // The entry is out of range or couldn't be parsed. Remove it.
-    copy->Remove(it.key(), NULL);
-  }
-  pref_service_->Set(pref_name_, *copy);
-}
-
-void DeviceStatusCollector::ActivityStorage::FilterActivityPeriodsByUsers(
-    const std::vector<std::string>& reporting_users) {
-  const base::DictionaryValue* stored_activity_periods =
-      pref_service_->GetDictionary(pref_name_);
-  base::DictionaryValue filtered_activity_periods;
-  ProcessActivityPeriods(*stored_activity_periods, reporting_users,
-                         &filtered_activity_periods);
-  pref_service_->Set(pref_name_, filtered_activity_periods);
-}
-
-std::vector<DeviceStatusCollector::ActivityStorage::ActivityPeriod>
-DeviceStatusCollector::ActivityStorage::GetFilteredActivityPeriods(
-    bool omit_emails) {
-  DictionaryPrefUpdate update(pref_service_, pref_name_);
-  base::DictionaryValue* stored_activity_periods = update.Get();
-
-  base::DictionaryValue filtered_activity_periods;
-  if (omit_emails) {
-    std::vector<std::string> empty_user_list;
-    ProcessActivityPeriods(*stored_activity_periods, empty_user_list,
-                           &filtered_activity_periods);
-    stored_activity_periods = &filtered_activity_periods;
-  }
-
-  std::vector<ActivityPeriod> activity_periods;
-  for (base::DictionaryValue::Iterator it(*stored_activity_periods);
-       !it.IsAtEnd(); it.Advance()) {
-    ActivityPeriod activity_period;
-    if (ParseActivityPeriodPrefKey(it.key(), &activity_period.start_timestamp,
-                                   &activity_period.user_email) &&
-        it.value().GetAsInteger(&activity_period.activity_milliseconds)) {
-      activity_periods.push_back(activity_period);
-    }
-  }
-  return activity_periods;
-}
-
-// static
-std::string DeviceStatusCollector::ActivityStorage::MakeActivityPeriodPrefKey(
-    int64_t start,
-    const std::string& user_email) {
-  const std::string day_key = base::NumberToString(start);
-  if (user_email.empty())
-    return day_key;
-
-  std::string encoded_email;
-  base::Base64Encode(user_email, &encoded_email);
-  return day_key + kActivityKeySeparator + encoded_email;
-}
-
-// static
-bool DeviceStatusCollector::ActivityStorage::ParseActivityPeriodPrefKey(
-    const std::string& key,
-    int64_t* start_timestamp,
-    std::string* user_email) {
-  auto separator_pos = key.find(kActivityKeySeparator);
-  if (separator_pos == std::string::npos) {
-    user_email->clear();
-    return base::StringToInt64(key, start_timestamp);
-  }
-  return base::StringToInt64(key.substr(0, separator_pos), start_timestamp) &&
-         base::Base64Decode(key.substr(separator_pos + 1), user_email);
-}
-
-void DeviceStatusCollector::ActivityStorage::ProcessActivityPeriods(
-    const base::DictionaryValue& activity_times,
-    const std::vector<std::string>& reporting_users,
-    base::DictionaryValue* const filtered_times) {
-  std::set<std::string> reporting_users_set(reporting_users.begin(),
-                                            reporting_users.end());
-  const std::string empty;
-  for (const auto& it : activity_times.DictItems()) {
-    DCHECK(it.second.is_int());
-    int64_t timestamp;
-    std::string user_email;
-    if (!ParseActivityPeriodPrefKey(it.first, &timestamp, &user_email))
-      continue;
-    if (!user_email.empty() && reporting_users_set.count(user_email) == 0) {
-      int value = 0;
-      std::string timestamp_str = MakeActivityPeriodPrefKey(timestamp, empty);
-      const base::Value* prev_value = filtered_times->FindKeyOfType(
-          timestamp_str, base::Value::Type::INTEGER);
-      if (prev_value)
-        value = prev_value->GetInt();
-      filtered_times->SetKey(timestamp_str,
-                             base::Value(value + it.second.GetInt()));
-    } else {
-      filtered_times->SetKey(it.first, it.second.Clone());
-    }
-  }
-}
-
-int64_t DeviceStatusCollector::ActivityStorage::TimestampToDayKey(
-    Time timestamp) {
-  Time::Exploded exploded;
-  Time day_start = timestamp.LocalMidnight() + day_start_;
-  if (timestamp < day_start)
-    day_start -= TimeDelta::FromDays(1);
-  day_start.LocalExplode(&exploded);
-  Time out_time;
-  bool conversion_success = Time::FromUTCExploded(exploded, &out_time);
-  DCHECK(conversion_success);
-  return out_time.ToJavaTime();
-}
-
-void DeviceStatusCollector::ActivityStorage::StoreChildScreenTime(
-    Time activity_day_start,
-    TimeDelta activity,
-    Time now) {
-  DCHECK(user_manager::UserManager::Get()->IsLoggedInAsChildUser() &&
-         !is_enterprise_reporting_);
-
-  // Today's start time.
-  Time today_start = now.LocalMidnight() + day_start_;
-  if (today_start > now)
-    today_start -= TimeDelta::FromDays(1);
-
-  // The activity windows always start and end on the reset time of two
-  // consecutive days, so it is not possible to have a window starting after
-  // the current day's reset time.
-  DCHECK(activity_day_start <= today_start);
-
-  TimeDelta previous_activity = TimeDelta::FromMilliseconds(
-      pref_service_->GetInteger(prefs::kChildScreenTimeMilliseconds));
-
-  // If this activity window belongs to the current day, the screen time pref
-  // should be updated.
-  if (activity_day_start == today_start) {
-    pref_service_->SetInteger(prefs::kChildScreenTimeMilliseconds,
-                              (previous_activity + activity).InMilliseconds());
-    pref_service_->SetTime(prefs::kLastChildScreenTimeSaved, now);
-  }
-  pref_service_->CommitPendingWrite();
-}
-
 SampledData::SampledData() = default;
 SampledData::~SampledData() = default;
 
@@ -1511,8 +1156,7 @@
     std::move(callback).Run();
 }
 
-void DeviceStatusCollector::FetchProbeData(
-    policy::DeviceStatusCollector::ProbeDataReceiver callback) {
+void DeviceStatusCollector::FetchProbeData(ProbeDataReceiver callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   runtime_probe::ProbeRequest request;
   if (report_power_status_)
@@ -1533,7 +1177,7 @@
 }
 
 void DeviceStatusCollector::OnProbeDataFetched(
-    policy::DeviceStatusCollector::ProbeDataReceiver callback,
+    ProbeDataReceiver callback,
     base::Optional<runtime_probe::ProbeResult> reply) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   std::move(callback).Run(reply, sampled_data_);
@@ -1817,8 +1461,10 @@
 }
 
 bool DeviceStatusCollector::GetHardwareStatus(
-    em::DeviceStatusReportRequest* status,
-    scoped_refptr<GetStatusState> state) {
+    scoped_refptr<DeviceStatusCollectorState> state) {
+  em::DeviceStatusReportRequest* status =
+      state->response_params().device_status.get();
+
   // Sample disk volume info in a background thread.
   state->SampleVolumeInfo(volume_info_fetcher_);
 
@@ -1954,8 +1600,8 @@
 
   // Some of the data we're collecting is gathered in background threads.
   // This object keeps track of the state of each async request.
-  scoped_refptr<GetStatusState> state(
-      new GetStatusState(task_runner_, response));
+  scoped_refptr<DeviceStatusCollectorState> state(
+      new DeviceStatusCollectorState(task_runner_, response));
 
   // Gather device status (might queue some async queries)
   GetDeviceStatus(state);
@@ -1970,8 +1616,9 @@
 }
 
 void DeviceStatusCollector::GetDeviceStatus(
-    scoped_refptr<GetStatusState> state) {
-  em::DeviceStatusReportRequest* status = state->device_status();
+    scoped_refptr<DeviceStatusCollectorState> state) {
+  em::DeviceStatusReportRequest* status =
+      state->response_params().device_status.get();
   bool anything_reported = false;
 
   if (report_activity_times_)
@@ -1990,7 +1637,7 @@
     anything_reported |= GetUsers(status);
 
   if (report_hardware_status_) {
-    anything_reported |= GetHardwareStatus(status, state);
+    anything_reported |= GetHardwareStatus(state);
     anything_reported |= GetWriteProtectSwitch(status);
   }
 
@@ -2002,7 +1649,7 @@
 
   // Wipe pointer if we didn't actually add any data.
   if (!anything_reported)
-    state->ResetDeviceStatus();
+    state->response_params().device_status.reset();
 }
 
 std::string DeviceStatusCollector::GetDMTokenForProfile(Profile* profile) {
@@ -2021,7 +1668,7 @@
 }
 
 bool DeviceStatusCollector::GetSessionStatusForUser(
-    scoped_refptr<GetStatusState> state,
+    scoped_refptr<DeviceStatusCollectorState> state,
     em::SessionStatusReportRequest* status,
     const user_manager::User* user) {
   Profile* const profile =
@@ -2057,8 +1704,9 @@
 }
 
 void DeviceStatusCollector::GetSessionStatus(
-    scoped_refptr<GetStatusState> state) {
-  em::SessionStatusReportRequest* status = state->session_status();
+    scoped_refptr<DeviceStatusCollectorState> state) {
+  em::SessionStatusReportRequest* status =
+      state->response_params().session_status.get();
   bool anything_reported = false;
 
   user_manager::UserManager* user_manager = user_manager::UserManager::Get();
@@ -2077,7 +1725,7 @@
 
   // Wipe pointer if we didn't actually add any data.
   if (!anything_reported)
-    state->ResetSessionStatus();
+    state->response_params().session_status.reset();
 }
 
 bool DeviceStatusCollector::GetKioskSessionStatus(
@@ -2113,7 +1761,7 @@
 
 bool DeviceStatusCollector::GetAndroidStatus(
     em::SessionStatusReportRequest* status,
-    const scoped_refptr<GetStatusState>& state) {
+    const scoped_refptr<DeviceStatusCollectorState>& state) {
   return state->FetchAndroidStatus(android_status_fetcher_);
 }
 
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector.h b/chrome/browser/chromeos/policy/status_collector/device_status_collector.h
index be309f0..d73eef5 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector.h
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector.h
@@ -63,8 +63,9 @@
 
 namespace policy {
 
+class ActivityStorage;
 struct DeviceLocalAccount;
-class GetStatusState;
+class DeviceStatusCollectorState;
 
 // Holds TPM status info.  Cf. TpmStatusInfo in device_management_backend.proto.
 struct TpmStatusInfo {
@@ -256,8 +257,6 @@
   void UpdateChildUsageTime();
 
  private:
-  class ActivityStorage;
-
   // Callbacks used during sampling data collection, that allows to pass
   // additional data using partial function application.
   using SamplingProbeResultCallback =
@@ -273,16 +272,16 @@
   void OnTpmVersion(
       const chromeos::CryptohomeClient::TpmVersionInfo& tpm_version_info);
 
-  void GetDeviceStatus(scoped_refptr<GetStatusState> state);
-  void GetSessionStatus(scoped_refptr<GetStatusState> state);
+  void GetDeviceStatus(scoped_refptr<DeviceStatusCollectorState> state);
+  void GetSessionStatus(scoped_refptr<DeviceStatusCollectorState> state);
 
   bool GetSessionStatusForUser(
-      scoped_refptr<GetStatusState> state,
+      scoped_refptr<DeviceStatusCollectorState> state,
       enterprise_management::SessionStatusReportRequest* status,
       const user_manager::User* user);
   // Helpers for the various portions of DEVICE STATUS. Return true if they
   // actually report any status. Functions that queue async queries take
-  // a |GetStatusState| instance.
+  // a |DeviceStatusCollectorState| instance.
   bool GetActivityTimes(
       enterprise_management::DeviceStatusReportRequest* status);
   bool GetVersionInfo(enterprise_management::DeviceStatusReportRequest* status);
@@ -292,9 +291,8 @@
   bool GetNetworkInterfaces(
       enterprise_management::DeviceStatusReportRequest* status);
   bool GetUsers(enterprise_management::DeviceStatusReportRequest* status);
-  bool GetHardwareStatus(
-      enterprise_management::DeviceStatusReportRequest* status,
-      scoped_refptr<GetStatusState> state);  // Queues async queries!
+  bool GetHardwareStatus(scoped_refptr<DeviceStatusCollectorState>
+                             state);  // Queues async queries!
   bool GetOsUpdateStatus(
       enterprise_management::DeviceStatusReportRequest* status);
   bool GetRunningKioskApp(
@@ -302,12 +300,13 @@
 
   // Helpers for the various portions of SESSION STATUS. Return true if they
   // actually report any status. Functions that queue async queries take
-  // a |GetStatusState| instance.
+  // a |DeviceStatusCollectorState| instance.
   bool GetKioskSessionStatus(
       enterprise_management::SessionStatusReportRequest* status);
   bool GetAndroidStatus(
       enterprise_management::SessionStatusReportRequest* status,
-      const scoped_refptr<GetStatusState>& state);  // Queues async queries!
+      const scoped_refptr<DeviceStatusCollectorState>&
+          state);  // Queues async queries!
   bool GetCrostiniUsage(
       enterprise_management::SessionStatusReportRequest* status,
       Profile* profile);
@@ -319,8 +318,8 @@
   void ReceiveCPUStatistics(const base::Time& timestamp,
                             const std::string& statistics);
 
-  // Callback for RuntimeProbe that samples probe live data. |callback| will be
-  // called once all sampling is finished.
+  // Callback for RuntimeProbe that samples probe live data. |callback| will
+  // be called once all sampling is finished.
   void SampleProbeData(std::unique_ptr<SampledData> sample,
                        SamplingProbeResultCallback callback,
                        base::Optional<runtime_probe::ProbeResult> result);
@@ -342,14 +341,12 @@
 
   // ProbeDataReceiver interface implementation, fetches data from
   // RuntimeProbe passes it to |callback| via OnProbeDataFetched().
-  void FetchProbeData(
-      policy::DeviceStatusCollector::ProbeDataReceiver callback);
+  void FetchProbeData(ProbeDataReceiver callback);
 
   // Callback for RuntimeProbe that performs final sampling and
   // actually invokes |callback|.
-  void OnProbeDataFetched(
-      policy::DeviceStatusCollector::ProbeDataReceiver callback,
-      base::Optional<runtime_probe::ProbeResult> reply);
+  void OnProbeDataFetched(ProbeDataReceiver callback,
+                          base::Optional<runtime_probe::ProbeResult> reply);
 
   // Callback invoked when reporting users pref is changed.
   void ReportingUsersChanged();
diff --git a/chrome/browser/chromeos/policy/status_collector/status_collector_state.cc b/chrome/browser/chromeos/policy/status_collector/status_collector_state.cc
new file mode 100644
index 0000000..24b3a45
--- /dev/null
+++ b/chrome/browser/chromeos/policy/status_collector/status_collector_state.cc
@@ -0,0 +1,28 @@
+// 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 "chrome/browser/chromeos/policy/status_collector/status_collector_state.h"
+
+#include "base/bind.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+
+namespace policy {
+
+StatusCollectorState::StatusCollectorState(
+    const scoped_refptr<base::SequencedTaskRunner> task_runner,
+    const StatusCollectorCallback& response)
+    : task_runner_(task_runner), response_(response) {}
+
+StatusCollectorParams& StatusCollectorState::response_params() {
+  return response_params_;
+}
+
+// Protected.
+StatusCollectorState::~StatusCollectorState() {
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(response_, base::Passed(&response_params_)));
+}
+
+}  // namespace policy
diff --git a/chrome/browser/chromeos/policy/status_collector/status_collector_state.h b/chrome/browser/chromeos/policy/status_collector/status_collector_state.h
new file mode 100644
index 0000000..87f3c8e
--- /dev/null
+++ b/chrome/browser/chromeos/policy/status_collector/status_collector_state.h
@@ -0,0 +1,54 @@
+// 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 CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_STATUS_COLLECTOR_STATE_H_
+#define CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_STATUS_COLLECTOR_STATE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "chrome/browser/chromeos/policy/status_collector/status_collector.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+// Helper class for state tracking of status queries (sync and async). Creates
+// status blobs in the constructor and sends them to the the status response
+// callback in the destructor.
+//
+// Some methods queue async queries to collect data. The response callback of
+// these queries holds a reference to the instance of this class, so that the
+// destructor will not be invoked and the status response callback will not be
+// fired until the original owner of the instance releases its reference and all
+// async queries finish.
+//
+// Therefore, if you create an instance of this class, make sure to release your
+// reference after querying all async queries (if any), e.g. by using a local
+// |scoped_refptr<StatusCollectorState>| and letting it go out of scope.
+class StatusCollectorState
+    : public base::RefCountedThreadSafe<StatusCollectorState> {
+ public:
+  explicit StatusCollectorState(
+      const scoped_refptr<base::SequencedTaskRunner> task_runner,
+      const StatusCollectorCallback& response);
+
+  // Returns a reference to the internal state of this object.
+  StatusCollectorParams& response_params();
+
+ protected:
+  friend class base::RefCountedThreadSafe<StatusCollectorState>;
+
+  // Posts the response on the UI thread. As long as there is an outstanding
+  // async query, the query holds a reference to us, so the destructor is
+  // not called.
+  virtual ~StatusCollectorState();
+
+  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  StatusCollectorCallback response_;
+  StatusCollectorParams response_params_;
+};
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_STATUS_COLLECTOR_STATE_H_
diff --git a/chrome/browser/chromeos/policy/status_uploader_unittest.cc b/chrome/browser/chromeos/policy/status_uploader_unittest.cc
index a1bcf51..f7a5e33 100644
--- a/chrome/browser/chromeos/policy/status_uploader_unittest.cc
+++ b/chrome/browser/chromeos/policy/status_uploader_unittest.cc
@@ -50,6 +50,8 @@
 constexpr base::TimeDelta kMinImmediateUploadInterval =
     base::TimeDelta::FromSeconds(10);
 
+// Using a DeviceStatusCollector to have a concrete StatusCollector, but the
+// exact type doesn't really matter, as it is being mocked.
 class MockDeviceStatusCollector : public policy::DeviceStatusCollector {
  public:
   explicit MockDeviceStatusCollector(PrefService* local_state)