chromium/chromeos/ash/components/report/utils/pref_utils.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 "chromeos/ash/components/report/utils/pref_utils.h"

#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "chromeos/ash/components/dbus/private_computing/private_computing_service.pb.h"
#include "chromeos/ash/components/report/prefs/fresnel_pref_names.h"
#include "chromeos/ash/components/report/utils/time_utils.h"
#include "components/prefs/pref_service.h"

namespace ash::report::utils {

namespace {

using private_computing::ActiveStatus;
using private_computing::ChurnObservationStatus;
using private_computing::GetStatusResponse;
using private_computing::PrivateComputingUseCase;
using private_computing::SaveStatusRequest;

// UMA histogram names for preserved file read records.
const char kHistogramsPreservedFileRead[] =
    "Ash.Report.PreservedFileReadAndParsed";

// |ts| must be defined and not unix epoch time.
void WriteLocalStateTimestampIfValid(PrefService* local_state,
                                     const std::string& pref_name,
                                     base::Time ts) {
  if (ts != base::Time::UnixEpoch() || ts != base::Time()) {
    local_state->SetTime(pref_name, ts);
  }
}

void WriteObservationLastPingTimestampIfValid(PrefService* local_state,
                                              const std::string& pref_name,
                                              base::Time ts) {
  if (ts == base::Time::UnixEpoch() || ts == base::Time()) {
    LOG(ERROR) << "Observation timestamp is not valid. ts = " << ts;
    return;
  }

  if (local_state->GetBoolean(
          prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus0)) {
    local_state->SetTime(pref_name, ts);
  } else if (local_state->GetBoolean(
                 prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus1)) {
    local_state->SetTime(pref_name, utils::GetPreviousMonth(ts).value());
  } else if (local_state->GetBoolean(
                 prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus2)) {
    local_state->SetTime(
        pref_name,
        utils::GetPreviousMonth(utils::GetPreviousMonth(ts).value()).value());
  }
}

}  // namespace

void RestoreLocalStateWithPreservedFile(PrefService* local_state,
                                        GetStatusResponse response) {
  bool read_success = true;
  for (ActiveStatus active_status : response.active_status()) {
    base::Time last_ping_ts;
    // Parse and validate the ping date before attempting to restore value.
    if (active_status.has_last_ping_date()) {
      bool success = base::Time::FromUTCString(
          active_status.last_ping_date().c_str(), &last_ping_ts);
      if (!success) {
        read_success = false;
        LOG(ERROR) << "Fail to convert last ping date to ts for use case = "
                   << PrivateComputingUseCase_Name(active_status.use_case());
        continue;
      }
    }

    switch (active_status.use_case()) {
      case PrivateComputingUseCase::CROS_FRESNEL_DAILY:
        WriteLocalStateTimestampIfValid(
            local_state, prefs::kDeviceActiveLastKnown1DayActivePingTimestamp,
            last_ping_ts);
        break;
      case PrivateComputingUseCase::CROS_FRESNEL_28DAY_ACTIVE:
        WriteLocalStateTimestampIfValid(
            local_state, prefs::kDeviceActiveLastKnown28DayActivePingTimestamp,
            last_ping_ts);
        break;
      case PrivateComputingUseCase::CROS_FRESNEL_CHURN_MONTHLY_COHORT:
        WriteLocalStateTimestampIfValid(
            local_state, prefs::kDeviceActiveChurnCohortMonthlyPingTimestamp,
            last_ping_ts);

        if (active_status.has_churn_active_status() &&
            active_status.churn_active_status() != 0) {
          local_state->SetInteger(
              prefs::kDeviceActiveLastKnownChurnActiveStatus,
              active_status.churn_active_status());
        }
        break;
      case PrivateComputingUseCase::CROS_FRESNEL_CHURN_MONTHLY_OBSERVATION:
        if (active_status.has_period_status()) {
          local_state->SetBoolean(
              prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus0,
              active_status.period_status().is_active_current_period_minus_0());
          local_state->SetBoolean(
              prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus1,
              active_status.period_status().is_active_current_period_minus_1());
          local_state->SetBoolean(
              prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus2,
              active_status.period_status().is_active_current_period_minus_2());

          WriteObservationLastPingTimestampIfValid(
              local_state,
              prefs::kDeviceActiveChurnObservationMonthlyPingTimestamp,
              local_state->GetTime(
                  prefs::kDeviceActiveChurnCohortMonthlyPingTimestamp));
        }
        break;
      default:
        read_success = false;
        LOG(ERROR) << "Restore local state failed - unknown use case.";
        continue;
    }
  }

  base::UmaHistogramBoolean(kHistogramsPreservedFileRead, read_success);
}

SaveStatusRequest CreatePreservedFileContents(PrefService* local_state) {
  base::Time one_day_ts = local_state->GetTime(
      prefs::kDeviceActiveLastKnown1DayActivePingTimestamp);
  base::Time twenty_eight_day_ts = local_state->GetTime(
      prefs::kDeviceActiveLastKnown28DayActivePingTimestamp);
  base::Time cohort_ts =
      local_state->GetTime(prefs::kDeviceActiveChurnCohortMonthlyPingTimestamp);
  base::Time observation_ts = local_state->GetTime(
      prefs::kDeviceActiveChurnObservationMonthlyPingTimestamp);
  int churn_active_status =
      local_state->GetInteger(prefs::kDeviceActiveLastKnownChurnActiveStatus);
  bool period_0 = local_state->GetBoolean(
      prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus0);
  bool period_1 = local_state->GetBoolean(
      prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus1);
  bool period_2 = local_state->GetBoolean(
      prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus2);

  SaveStatusRequest save_request;

  // Store 1-day-active data.
  if (one_day_ts != base::Time() && one_day_ts != base::Time::UnixEpoch()) {
    ActiveStatus one_day_status;
    one_day_status.set_use_case(PrivateComputingUseCase::CROS_FRESNEL_DAILY);
    one_day_status.set_last_ping_date(
        utils::FormatTimestampToMidnightGMTString(one_day_ts));

    *save_request.add_active_status() = one_day_status;
  }

  // Store 28-day-active data.
  if (twenty_eight_day_ts != base::Time() &&
      twenty_eight_day_ts != base::Time::UnixEpoch()) {
    ActiveStatus twenty_eight_day_status;
    twenty_eight_day_status.set_use_case(
        PrivateComputingUseCase::CROS_FRESNEL_28DAY_ACTIVE);
    twenty_eight_day_status.set_last_ping_date(
        utils::FormatTimestampToMidnightGMTString(twenty_eight_day_ts));

    *save_request.add_active_status() = twenty_eight_day_status;
  }

  // Store Churn data.
  if (cohort_ts != base::Time() && cohort_ts != base::Time::UnixEpoch()) {
    ActiveStatus cohort_status;
    cohort_status.set_use_case(
        PrivateComputingUseCase::CROS_FRESNEL_CHURN_MONTHLY_COHORT);
    cohort_status.set_last_ping_date(
        utils::FormatTimestampToMidnightGMTString(cohort_ts));
    cohort_status.set_churn_active_status(churn_active_status);
    *save_request.add_active_status() = cohort_status;

    // Store Monthly Observation data.
    //
    // Observation active status will only be saved to preserved file,
    // if it is aligned with when Cohort use case last pinged.
    if (utils::IsSameYearAndMonth(observation_ts, cohort_ts)) {
      ActiveStatus observation_status;
      observation_status.set_use_case(
          PrivateComputingUseCase::CROS_FRESNEL_CHURN_MONTHLY_OBSERVATION);
      ChurnObservationStatus* period_status =
          observation_status.mutable_period_status();
      period_status->set_is_active_current_period_minus_0(period_0);
      period_status->set_is_active_current_period_minus_1(period_1);
      period_status->set_is_active_current_period_minus_2(period_2);
      *save_request.add_active_status() = observation_status;
    }
  }

  return save_request;
}

}  // namespace ash::report::utils