chromium/ash/system/input_device_settings/settings_updated_metrics_info.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/input_device_settings/settings_updated_metrics_info.h"

#include "base/containers/fixed_flat_map.h"
#include "base/json/values_util.h"
#include "base/time/time.h"
#include "base/values.h"

namespace ash {

namespace {

using TimePeriod = SettingsUpdatedMetricsInfo::TimePeriod;
using Category = SettingsUpdatedMetricsInfo::Category;

constexpr char kLocalFirstConnectionKey[] = "initial_connection_time";
constexpr char kCategoryKey[] = "category";

constexpr auto kTimePeriodToKey =
    base::MakeFixedFlatMap<TimePeriod, const char*>({
        {TimePeriod::kOneHour, "one_hour"},
        {TimePeriod::kThreeHours, "three_hours"},
        {TimePeriod::kOneDay, "one_day"},
        {TimePeriod::kThreeDays, "three_days"},
        {TimePeriod::kOneWeek, "one_week"},
    });

constexpr auto kTimePeriodToTimeDelta =
    base::MakeFixedFlatMap<TimePeriod, base::TimeDelta>({
        {TimePeriod::kOneHour, base::Hours(1)},
        {TimePeriod::kThreeHours, base::Hours(3)},
        {TimePeriod::kOneDay, base::Days(1)},
        {TimePeriod::kThreeDays, base::Days(3)},
        {TimePeriod::kOneWeek, base::Days(7)},
    });

bool IsValidCategory(int category_int) {
  return category_int >= static_cast<int>(Category::kMinValue) &&
         category_int <= static_cast<int>(Category::kMaxValue);
}

}  // namespace

SettingsUpdatedMetricsInfo::SettingsUpdatedMetricsInfo(
    Category category,
    base::Time initial_connection_time)
    : category_(category),
      initial_connection_time_(initial_connection_time),
      time_period_counts_({0}) {}
SettingsUpdatedMetricsInfo::~SettingsUpdatedMetricsInfo() = default;
SettingsUpdatedMetricsInfo::SettingsUpdatedMetricsInfo(
    const SettingsUpdatedMetricsInfo&) = default;

// static
std::optional<SettingsUpdatedMetricsInfo> SettingsUpdatedMetricsInfo::FromDict(
    const base::Value::Dict& dict) {
  auto category_int = dict.FindInt(kCategoryKey);
  if (!category_int || !IsValidCategory(*category_int)) {
    return std::nullopt;
  }

  auto initial_connection_time_ =
      base::ValueToTime(dict.Find(kLocalFirstConnectionKey));
  if (!initial_connection_time_) {
    return std::nullopt;
  }

  SettingsUpdatedMetricsInfo metric_info(static_cast<Category>(*category_int),
                                         *initial_connection_time_);
  for (const auto& [time_period, key] : kTimePeriodToKey) {
    metric_info.time_period_counts_[static_cast<int>(time_period)] =
        dict.FindInt(key).value_or(0);
  }

  return metric_info;
}

std::optional<TimePeriod> SettingsUpdatedMetricsInfo::RecordSettingsUpdate(
    base::Time update_time) {
  CHECK(update_time >= initial_connection_time_);
  const base::TimeDelta time_since_initial_connection =
      update_time - initial_connection_time_;

  for (const auto& [time_period, time_delta] : kTimePeriodToTimeDelta) {
    if (time_since_initial_connection < time_delta) {
      time_period_counts_[static_cast<size_t>(time_period)] += 1;
      return time_period;
    }
  }

  return std::nullopt;
}

base::Value::Dict SettingsUpdatedMetricsInfo::ToDict() const {
  base::Value::Dict dict;
  dict.Set(kLocalFirstConnectionKey,
           base::TimeToValue(initial_connection_time_));
  dict.Set(kCategoryKey, static_cast<int>(category_));

  for (size_t i = 0; i < time_period_counts_.size(); ++i) {
    if (time_period_counts_[i] > 0) {
      TimePeriod time_period = static_cast<TimePeriod>(i);
      DCHECK(kTimePeriodToKey.contains(time_period));
      dict.Set(kTimePeriodToKey.at(time_period), time_period_counts_[i]);
    }
  }

  return dict;
}

int SettingsUpdatedMetricsInfo::GetCount(TimePeriod time_period) const {
  return time_period_counts_[static_cast<int>(time_period)];
}

}  // namespace ash