chromium/chrome/browser/ash/child_accounts/usage_time_limit_processor.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ash/child_accounts/usage_time_limit_processor.h"

#include <optional>
#include <string>
#include <utility>

#include "base/check_deref.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/child_accounts/time_limit_override.h"

namespace ash::usage_time_limit {
namespace internal {
namespace {

constexpr char kTimeLimitLastUpdatedAt[] = "last_updated_millis";
constexpr char kTimeWindowLimit[] = "time_window_limit";
constexpr char kTimeUsageLimit[] = "time_usage_limit";
constexpr char kUsageLimitResetAt[] = "reset_at";
constexpr char kUsageLimitUsageQuota[] = "usage_quota_mins";
constexpr char kWindowLimitEntries[] = "entries";
constexpr char kWindowLimitEntryEffectiveDay[] = "effective_day";
constexpr char kWindowLimitEntryEndsAt[] = "ends_at";
constexpr char kWindowLimitEntryStartsAt[] = "starts_at";
constexpr char kWindowLimitEntryTimeHour[] = "hour";
constexpr char kWindowLimitEntryTimeMinute[] = "minute";
constexpr const char* kTimeLimitWeekdays[] = {
    "sunday",   "monday", "tuesday", "wednesday",
    "thursday", "friday", "saturday"};

// Defaults to midnight.
constexpr base::TimeDelta kDefaultUsageLimitResetTime;

// Whether a timestamp is inside a window.
bool ContainsTime(base::Time start, base::Time end, base::Time now) {
  return now >= start && now < end;
}

// Returns true when a < b. When b is null, this returns true.
bool IsBefore(base::Time a, base::Time b) {
  return b.is_null() || a < b;
}

// Implements the modulo operation. E.g. modulo(10, 7) == 3, modulo(-1, 7) == 6.
int Modulo(int dividend, int divisor) {
  int remainder = dividend % divisor;
  if (remainder < 0)
    remainder += divisor;
  return remainder;
}

// Shifts the current weekday, if the value is positive shifts forward and if
// negative backwards.
Weekday WeekdayShift(Weekday current_day, int shift) {
  return static_cast<Weekday>(Modulo(static_cast<int>(current_day) + shift,
                                     static_cast<int>(Weekday::kCount)));
}

// Returns usage limit reset time or default value if |time_usage_limit| is
// invalid.
base::TimeDelta GetUsageLimitResetTime(
    const std::optional<internal::TimeUsageLimit>& time_usage_limit) {
  if (time_usage_limit)
    return time_usage_limit->resets_at;
  return kDefaultUsageLimitResetTime;
}

// Helper class to process the UsageTimeLimit policy.
class UsageTimeLimitProcessor {
 public:
  UsageTimeLimitProcessor(
      std::optional<internal::TimeWindowLimit> time_window_limit,
      std::optional<internal::TimeUsageLimit> time_usage_limit,
      std::optional<TimeLimitOverride> time_limit_override,
      std::optional<TimeLimitOverride> local_time_limit_override,
      const base::TimeDelta& used_time,
      const base::Time& usage_timestamp,
      const base::Time& current_time,
      const icu::TimeZone* const time_zone,
      const std::optional<State>& previous_state);

  ~UsageTimeLimitProcessor() = default;

  // Current user's session state.
  State GetState();

  // Expected time when the user's usage quota should be reset.
  base::Time GetExpectedResetTime();

  // Difference between today user's usage quota and usage time.
  std::optional<base::TimeDelta> GetRemainingTimeUsage();

 private:
  // Get the active time window limit.
  std::optional<internal::TimeWindowLimitEntry> GetActiveTimeWindowLimit();

  // Get the active time usage limit.
  std::optional<internal::TimeUsageLimitEntry> GetActiveTimeUsageLimit();

  // Get the enabled time usage limit.
  std::optional<internal::TimeUsageLimitEntry> GetEnabledTimeUsageLimit();

  // Returns the duration of all the consuctive time window limit starting at
  // the given weekday.
  base::TimeDelta GetConsecutiveTimeWindowLimitDuration(
      internal::Weekday weekday);

  // Whether there's an override whose duration has finished.
  bool IsOverrideDurationFinished();

  // Whether the device should be locked by an override.
  bool ShouldBeLockedByOverride();

  // Whether there is a valid override. It includes lock, unlock or unlock with
  // duration override.
  bool HasActiveOverride();

  // Whether there's an active override with duration.
  bool HasActiveOverrideWithDuration();

  // Whether the user's session should be locked.
  bool IsLocked();

  // Which policy is currently active.
  PolicyType GetActivePolicy();

  // Gets the time when the active time limit will end.
  base::Time GetActiveTimeLimitEndTime();

  // Gets the next time when usage time limit will reset.
  base::Time GetNextUsageLimitResetTime();

  // Gets the time when the current override will end. If there's no override,
  // returns base::Time().
  base::Time GetCurrentOverrideEndTime();

  // Gets the time when the lock override will end.
  base::Time GetLockOverrideEndTime();

  // Next time when the user session will be unlocked.
  base::Time GetNextUnlockTime();

  // Expected time when the state will change.
  base::Time GetNextStateChangeTime(PolicyType* out_next_active);

  // Whether the time window limit defined in the given weekday is overridden.
  bool IsWindowLimitOverridden(internal::Weekday weekday);

  // Whether the time usage limit defined in the given weekday is overridden.
  bool IsUsageLimitOverridden(internal::Weekday weekday);

  // Whether the current override was canceled by the window limit update in the
  // given weekday.
  bool WasOverrideCanceledByWindowLimit(internal::Weekday weekday);

  // Whether the current override was canceled by the usage time limit update in
  // the given weekday.
  bool WasOverrideCanceledByUsageTimeLimit(internal::Weekday weekday);

  // When the lock override should reset.
  base::TimeDelta LockOverrideResetTime();

  // When the usage limit should reset the usage quota.
  base::TimeDelta UsageLimitResetTime();

  // Checks if the time window limit entry for the current weekday is active.
  bool IsTodayTimeWindowLimitActive();

  // Local midnight.
  base::Time LocalMidnight(base::Time time);

  // Get the current weekday.
  Weekday GetCurrentWeekday();

  // Get the time zone offset. Used to convert GMT time to local time.
  base::TimeDelta GetTimeZoneOffset(base::Time time);

  // Converts the policy time, which is a delta from midnight, to a timestamp.
  // Since this is done based on the current time, a shift in days param is
  // available.
  base::Time ConvertPolicyTime(base::TimeDelta policy_time, int shift_in_days);

  // The policy time window limit object.
  std::optional<internal::TimeWindowLimit> time_window_limit_;

  // The policy time usage limit object.
  std::optional<internal::TimeUsageLimit> time_usage_limit_;

  // The policy override object.
  std::optional<TimeLimitOverride> time_limit_override_;

  // The local override object.
  std::optional<TimeLimitOverride> local_time_limit_override_;

  // How long the user has used the device.
  const base::TimeDelta used_time_;

  // When the used_time_ data was collected.
  const base::Time usage_timestamp_;

  // The current time, not necessarily equal to usage_timestamp_.
  const base::Time current_time_;

  // Unowned. The device's timezone.
  const raw_ptr<const icu::TimeZone> time_zone_;

  // Current weekday, extracted from current time.
  internal::Weekday current_weekday_;

  // The previous state calculated by this class.
  const raw_ref<const std::optional<State>> previous_state_;

  // The active time window limit. If this is set, it means that the user
  // session should be locked, in other words, there is a time window limit set
  // for the current day, the current time is inside that window and no unlock
  // override is preventing it to be locked.
  std::optional<internal::TimeWindowLimitEntry> active_time_window_limit_;

  // The active time usage limit. If this is set, it means that the user session
  // should be locked, in other words, there is a time usage limit set for the
  // current day, the user has used all their usage quota and no unlock override
  // is preventing it to be locked.
  std::optional<internal::TimeUsageLimitEntry> active_time_usage_limit_;

  // If this is set, it means that there is a time usage limit set for today,
  // but it is not necessarily active. It could be inactive either because the
  // user haven't used all their quota or because there is an unlock override
  // active.
  std::optional<internal::TimeUsageLimitEntry> enabled_time_usage_limit_;

  // Whether there is a window limit overridden.
  bool overridden_window_limit_ = false;

  // Whether there is a usage limit overridden.
  bool overridden_usage_limit_ = false;
};

UsageTimeLimitProcessor::UsageTimeLimitProcessor(
    std::optional<internal::TimeWindowLimit> time_window_limit,
    std::optional<internal::TimeUsageLimit> time_usage_limit,
    std::optional<TimeLimitOverride> time_limit_override,
    std::optional<TimeLimitOverride> local_time_limit_override,
    const base::TimeDelta& used_time,
    const base::Time& usage_timestamp,
    const base::Time& current_time,
    const icu::TimeZone* const time_zone,
    const std::optional<State>& previous_state)
    : time_window_limit_(std::move(time_window_limit)),
      time_usage_limit_(std::move(time_usage_limit)),
      used_time_(used_time),
      usage_timestamp_(usage_timestamp),
      current_time_(current_time),
      time_zone_(time_zone),
      current_weekday_(GetCurrentWeekday()),
      previous_state_(previous_state),
      enabled_time_usage_limit_(GetEnabledTimeUsageLimit()) {
  // Use local override if it is newer than policy override, otherwise ignore
  // local override.
  // Note: |time_limit_override_| needs to be set before calculating
  // |active_time_window_limit_| and |active_time_usage_limit_|.
  bool should_use_local_override = local_time_limit_override.has_value() &&
                                   (!time_limit_override.has_value() ||
                                    local_time_limit_override->created_at() >
                                        time_limit_override->created_at());
  time_limit_override_ = should_use_local_override
                             ? std::move(local_time_limit_override)
                             : std::move(time_limit_override);

  // This will also set overridden_window_limit_ to true if applicable.
  // TODO: refactor GetActiveTimeWindowLimit to stop updating the state on a
  // getter method.
  active_time_window_limit_ = GetActiveTimeWindowLimit();
  // This will also sets overridden_usage_limit_ to true if applicable.
  // TODO: refactor GetActiveTimeUsageLimit to stop updating the state on a
  // getter method.
  active_time_usage_limit_ = GetActiveTimeUsageLimit();
}

base::Time UsageTimeLimitProcessor::GetExpectedResetTime() {
  base::TimeDelta delta_from_midnight =
      current_time_ - LocalMidnight(current_time_);
  int shift_in_days = 1;
  if (delta_from_midnight < UsageLimitResetTime())
    shift_in_days = 0;
  return ConvertPolicyTime(UsageLimitResetTime(), shift_in_days);
}

std::optional<base::TimeDelta>
UsageTimeLimitProcessor::GetRemainingTimeUsage() {
  if (!enabled_time_usage_limit_)
    return std::nullopt;
  return std::max(enabled_time_usage_limit_->usage_quota - used_time_,
                  base::Minutes(0));
}

State UsageTimeLimitProcessor::GetState() {
  State state;
  state.is_locked = IsLocked();
  state.active_policy = GetActivePolicy();

  // Time usage limit is enabled if there is an entry for the current day and it
  // is not overridden.
  std::optional<base::TimeDelta> remaining_usage = GetRemainingTimeUsage();
  if (remaining_usage) {
    state.is_time_usage_limit_enabled = true;
    state.remaining_usage = remaining_usage.value();
  }

  const base::TimeDelta delta_zero = base::Minutes(0);
  bool current_state_above_usage_limit =
      state.is_time_usage_limit_enabled && state.remaining_usage <= delta_zero;
  bool previous_state_below_usage_limit =
      previous_state_->has_value() &&
      (*previous_state_)->is_time_usage_limit_enabled &&
      (*previous_state_)->remaining_usage > delta_zero;
  bool previous_state_no_usage_limit =
      previous_state_->has_value() &&
      !(*previous_state_)->is_time_usage_limit_enabled;
  bool previous_state_above_usage_limit =
      previous_state_->has_value() &&
      (*previous_state_)->is_time_usage_limit_enabled &&
      (*previous_state_)->remaining_usage <= delta_zero;
  if ((previous_state_below_usage_limit || previous_state_no_usage_limit ||
       !previous_state_->has_value()) &&
      current_state_above_usage_limit) {
    // Time usage limit just started being enforced.
    state.time_usage_limit_started = usage_timestamp_;
  } else if (previous_state_above_usage_limit) {
    // Time usage limit was already enforced.
    state.time_usage_limit_started =
        (*previous_state_)->time_usage_limit_started;
  }

  state.next_state_change_time =
      GetNextStateChangeTime(&state.next_state_active_policy);

  state.next_unlock_time = GetNextUnlockTime();

  return state;
}

base::TimeDelta UsageTimeLimitProcessor::GetConsecutiveTimeWindowLimitDuration(
    internal::Weekday weekday) {
  base::TimeDelta duration = base::Minutes(0);
  std::optional<internal::TimeWindowLimitEntry> current_day_entry =
      time_window_limit_->entries[weekday];

  if (!time_window_limit_ || !current_day_entry)
    return duration;

  // Iterate throught entries as long as they are consecutive, or overlap.
  base::TimeDelta last_entry_end = current_day_entry->starts_at;
  for (int i = 0; i < static_cast<int>(internal::Weekday::kCount); i++) {
    std::optional<internal::TimeWindowLimitEntry> window_limit_entry =
        time_window_limit_->entries[internal::WeekdayShift(weekday, i)];

    // It is not consecutive.
    if (!window_limit_entry || window_limit_entry->starts_at > last_entry_end)
      break;

    if (window_limit_entry->IsOvernight()) {
      duration += base::TimeDelta(base::Hours(24) - last_entry_end) +
                  base::TimeDelta(window_limit_entry->ends_at);
    } else {
      duration += std::max(window_limit_entry->ends_at - last_entry_end,
                           base::Minutes(0));
      // This entry is not overnight, so the next one cannot be a consecutive
      // window.
      break;
    }
    last_entry_end = window_limit_entry->ends_at;
  }

  return duration;
}

bool UsageTimeLimitProcessor::IsWindowLimitOverridden(
    internal::Weekday weekday) {
  if (!time_window_limit_ || !time_limit_override_ ||
      time_limit_override_->IsLock()) {
    return false;
  }

  // If there's an override with duration, the window limit is overridden only
  // if the override is active and duration is not over, since it works
  // as a lock override after duration.
  if (time_limit_override_->duration())
    return HasActiveOverrideWithDuration() && !IsOverrideDurationFinished();

  if (WasOverrideCanceledByWindowLimit(weekday))
    return false;

  std::optional<internal::TimeWindowLimitEntry> window_limit_entry =
      time_window_limit_->entries[weekday];

  int days_behind = 0;
  for (int i = 0; i < static_cast<int>(internal::Weekday::kCount); i++) {
    if (internal::WeekdayShift(weekday, i) == current_weekday_) {
      days_behind = i;
      break;
    }
  }

  base::Time window_limit_start =
      ConvertPolicyTime(window_limit_entry->starts_at, -days_behind);
  base::Time window_limit_end =
      window_limit_start + GetConsecutiveTimeWindowLimitDuration(weekday);

  return ContainsTime(window_limit_start, window_limit_end,
                      time_limit_override_->created_at());
}

bool UsageTimeLimitProcessor::IsUsageLimitOverridden(
    internal::Weekday weekday) {
  if (!time_limit_override_ || time_limit_override_->IsLock()) {
    return false;
  }

  if (!time_usage_limit_ || !previous_state_->has_value()) {
    return false;
  }

  // If there's an override with duration, the usage limit is overridden only
  // if the override is active and duration is not over, since it works
  // as a lock override after duration.
  if (time_limit_override_->duration())
    return HasActiveOverrideWithDuration() && !IsOverrideDurationFinished();

  if (WasOverrideCanceledByUsageTimeLimit(weekday))
    return false;

  base::Time last_reset_time = ConvertPolicyTime(LockOverrideResetTime(), 0);
  bool usage_limit_enforced_previously =
      (*previous_state_)->is_time_usage_limit_enabled &&
      (*previous_state_)->remaining_usage <= base::Minutes(0);
  bool override_created_after_usage_limit_start =
      !(*previous_state_)->time_usage_limit_started.is_null() &&
      time_limit_override_->created_at() >
          (*previous_state_)->time_usage_limit_started &&
      time_limit_override_->created_at() >= last_reset_time;
  return usage_limit_enforced_previously &&
         override_created_after_usage_limit_start;
}

bool UsageTimeLimitProcessor::WasOverrideCanceledByUsageTimeLimit(
    internal::Weekday weekday) {
  if (!time_usage_limit_ || !time_limit_override_)
    return false;

  std::optional<internal::TimeUsageLimitEntry> usage_limit_entry =
      time_usage_limit_->entries[weekday];

  // If the time usage limit has been updated since the override, the
  // override is cancelled.
  return usage_limit_entry &&
         usage_limit_entry->last_updated > time_limit_override_->created_at();
}

bool UsageTimeLimitProcessor::WasOverrideCanceledByWindowLimit(
    internal::Weekday weekday) {
  if (!time_window_limit_ || !time_limit_override_)
    return false;

  std::optional<TimeWindowLimitEntry> window_limit =
      time_window_limit_->entries[weekday];

  // If the window limit has been updated since the override, the
  // override is cancelled.
  if (window_limit &&
      window_limit->last_updated > time_limit_override_->created_at())
    return true;

  return false;
}

bool UsageTimeLimitProcessor::HasActiveOverrideWithDuration() {
  if (!time_limit_override_ || time_limit_override_->IsLock() ||
      !time_limit_override_->duration()) {
    return false;
  }

  internal::Weekday current_usage_limit_day =
      current_time_ > ConvertPolicyTime(UsageLimitResetTime(), 0)
          ? current_weekday_
          : internal::WeekdayShift(current_weekday_, -1);

  if (current_time_ >= GetCurrentOverrideEndTime() ||
      WasOverrideCanceledByUsageTimeLimit(current_usage_limit_day))
    return false;

  if (!time_window_limit_)
    return true;

  internal::Weekday previous_weekday =
      internal::WeekdayShift(current_weekday_, -1);
  std::optional<internal::TimeWindowLimitEntry> previous_day_entry =
      time_window_limit_->entries[previous_weekday];

  // Active time window limit that started on the previous day.
  if (previous_day_entry && previous_day_entry->IsOvernight() &&
      WasOverrideCanceledByWindowLimit(previous_weekday)) {
    return false;
  }

  return !WasOverrideCanceledByWindowLimit(current_weekday_);
}

std::optional<internal::TimeWindowLimitEntry>
UsageTimeLimitProcessor::GetActiveTimeWindowLimit() {
  if (!time_window_limit_)
    return std::nullopt;

  internal::Weekday previous_weekday =
      internal::WeekdayShift(current_weekday_, -1);
  std::optional<internal::TimeWindowLimitEntry> previous_day_entry =
      time_window_limit_->entries[previous_weekday];

  // Active time window limit that started on the previous day.
  std::optional<internal::TimeWindowLimitEntry> previous_day_active_entry;
  if (previous_day_entry && previous_day_entry->IsOvernight()) {
    base::Time limit_start =
        ConvertPolicyTime(previous_day_entry->starts_at, -1);
    base::Time limit_end = ConvertPolicyTime(previous_day_entry->ends_at, 0);

    if (ContainsTime(limit_start, limit_end, current_time_)) {
      if (IsWindowLimitOverridden(previous_weekday)) {
        overridden_window_limit_ = true;
      } else {
        previous_day_active_entry = previous_day_entry;
      }
    }
  }

  std::optional<internal::TimeWindowLimitEntry> current_day_entry =
      time_window_limit_->entries[current_weekday_];

  // Active time window limit that started today.
  std::optional<internal::TimeWindowLimitEntry> current_day_active_entry;
  if (current_day_entry) {
    base::Time limit_start = ConvertPolicyTime(current_day_entry->starts_at, 0);
    base::Time limit_end = ConvertPolicyTime(
        current_day_entry->ends_at, current_day_entry->IsOvernight() ? 1 : 0);

    if (ContainsTime(limit_start, limit_end, current_time_)) {
      if (IsWindowLimitOverridden(current_weekday_)) {
        overridden_window_limit_ = true;
      } else {
        current_day_active_entry = current_day_entry;
      }
    }
  }

  if (current_day_active_entry && previous_day_active_entry) {
    // If two windows overlap and are active now we must return the one that
    // ends later.
    if (current_day_active_entry->IsOvernight() ||
        current_day_active_entry->ends_at >
            previous_day_active_entry->ends_at) {
      return current_day_active_entry;
    }
    return previous_day_active_entry;
  }

  if (current_day_active_entry)
    return current_day_active_entry;

  return previous_day_active_entry;
}

std::optional<internal::TimeUsageLimitEntry>
UsageTimeLimitProcessor::GetEnabledTimeUsageLimit() {
  if (!time_usage_limit_)
    return std::nullopt;

  internal::Weekday current_usage_limit_day =
      current_time_ >= ConvertPolicyTime(UsageLimitResetTime(), 0)
          ? current_weekday_
          : internal::WeekdayShift(current_weekday_, -1);
  return time_usage_limit_->entries[current_usage_limit_day];
}

std::optional<internal::TimeUsageLimitEntry>
UsageTimeLimitProcessor::GetActiveTimeUsageLimit() {
  if (!time_usage_limit_)
    return std::nullopt;

  internal::Weekday current_usage_limit_day =
      current_time_ > ConvertPolicyTime(UsageLimitResetTime(), 0)
          ? current_weekday_
          : internal::WeekdayShift(current_weekday_, -1);

  std::optional<internal::TimeUsageLimitEntry> current_usage_limit =
      GetEnabledTimeUsageLimit();

  if (IsUsageLimitOverridden(current_usage_limit_day)) {
    overridden_usage_limit_ = true;
    return std::nullopt;
  }

  if (current_usage_limit && used_time_ >= current_usage_limit->usage_quota)
    return current_usage_limit;

  return std::nullopt;
}

bool UsageTimeLimitProcessor::IsOverrideDurationFinished() {
  if (!time_limit_override_ || time_limit_override_->IsLock() ||
      !time_limit_override_->duration())
    return false;

  base::Time lock_time = time_limit_override_->created_at() +
                         time_limit_override_->duration().value();
  if (ContainsTime(time_limit_override_->created_at(), lock_time,
                   current_time_))
    return false;

  return true;
}

bool UsageTimeLimitProcessor::ShouldBeLockedByOverride() {
  return (HasActiveOverride() && time_limit_override_->IsLock()) ||
         (HasActiveOverrideWithDuration() && IsOverrideDurationFinished());
}

bool UsageTimeLimitProcessor::HasActiveOverride() {
  if (!time_limit_override_ || active_time_window_limit_ ||
      active_time_usage_limit_) {
    return false;
  }

  if (overridden_window_limit_ || overridden_usage_limit_)
    return true;

  if (time_limit_override_->duration())
    return HasActiveOverrideWithDuration();

  base::Time last_reset_time = ConvertPolicyTime(LockOverrideResetTime(), 0);
  if (current_time_ < last_reset_time)
    last_reset_time -= base::Days(1);

  bool override_cancelled_by_window_limit = false;
  if (time_window_limit_) {
    // Check if yesterdays or todays window limit ended after override was
    // created.
    for (int i = -1; i <= 0; i++) {
      internal::Weekday weekday = WeekdayShift(current_weekday_, i);
      std::optional<TimeWindowLimitEntry> window_limit =
          time_window_limit_->entries[weekday];
      if (window_limit) {
        base::Time window_limit_start =
            ConvertPolicyTime(window_limit->starts_at, i);
        base::Time window_limit_end =
            window_limit_start + GetConsecutiveTimeWindowLimitDuration(weekday);
        if (current_time_ >= window_limit_end &&
            window_limit_end > time_limit_override_->created_at()) {
          override_cancelled_by_window_limit = true;
          break;
        }
      }
    }
  }

  bool has_valid_lock_override =
      time_limit_override_->created_at() > last_reset_time &&
      !override_cancelled_by_window_limit;

  if (!has_valid_lock_override)
    return false;

  // Check if the usage time was increased before the override creation, which
  // invalidates it.
  if (previous_state_->has_value() &&
      (*previous_state_)->is_time_usage_limit_enabled &&
      (*previous_state_)->remaining_usage <= base::Minutes(0)) {
    if (enabled_time_usage_limit_ &&
        time_limit_override_->created_at() <
            enabled_time_usage_limit_->last_updated) {
      return false;
    }
  }

  return true;
}

bool UsageTimeLimitProcessor::IsLocked() {
  return active_time_usage_limit_ || active_time_window_limit_ ||
         ShouldBeLockedByOverride();
}

PolicyType UsageTimeLimitProcessor::GetActivePolicy() {
  // If there's an active override with duration, the active policy is always
  // override.
  if (HasActiveOverrideWithDuration())
    return PolicyType::kOverride;

  if (active_time_window_limit_)
    return PolicyType::kFixedLimit;

  if (active_time_usage_limit_)
    return PolicyType::kUsageLimit;

  if (HasActiveOverride())
    return PolicyType::kOverride;

  return PolicyType::kNoPolicy;
}

base::Time UsageTimeLimitProcessor::GetActiveTimeLimitEndTime() {
  if (!active_time_window_limit_)
    return base::Time();

  base::TimeDelta window_limit_duration =
      IsTodayTimeWindowLimitActive()
          ? GetConsecutiveTimeWindowLimitDuration(current_weekday_)
          : GetConsecutiveTimeWindowLimitDuration(
                internal::WeekdayShift(current_weekday_, -1));
  return ConvertPolicyTime(active_time_window_limit_->starts_at,
                           IsTodayTimeWindowLimitActive() ? 0 : -1) +
         window_limit_duration;
}

base::Time UsageTimeLimitProcessor::GetNextUsageLimitResetTime() {
  bool has_reset_today =
      (current_time_ - LocalMidnight(current_time_)) >= UsageLimitResetTime();
  return ConvertPolicyTime(UsageLimitResetTime(), has_reset_today ? 1 : 0);
}

base::Time UsageTimeLimitProcessor::GetCurrentOverrideEndTime() {
  if (!time_limit_override_)
    return base::Time();

  base::Time reset_time = LocalMidnight(time_limit_override_->created_at()) +
                          LockOverrideResetTime();

  if (IsBefore(reset_time, time_limit_override_->created_at()))
    reset_time = reset_time + base::Days(1);

  return reset_time;
}

base::Time UsageTimeLimitProcessor::GetLockOverrideEndTime() {
  if (!ShouldBeLockedByOverride()) {
    return base::Time();
  }
  return GetCurrentOverrideEndTime();
}

base::Time UsageTimeLimitProcessor::GetNextUnlockTime() {
  if (!IsLocked())
    return base::Time();

  base::Time unlock_time;

  // When the current active time window limit ends.
  if (active_time_window_limit_)
    unlock_time = std::max(unlock_time, GetActiveTimeLimitEndTime());

  // When the usage quota resets.
  if (active_time_usage_limit_) {
    base::Time next_usage_limit_reset = GetNextUsageLimitResetTime();
    unlock_time = std::max(unlock_time, next_usage_limit_reset);
    // The usage limit could reset when a window limit is active, we must check
    // that, and if this is the case calculate the end of the window limit.
    if (time_window_limit_) {
      // Check if yesterdays, todays or tomorrows window limit will be active
      // when the reset happens.
      for (int i = -1; i <= 1; i++) {
        std::optional<TimeWindowLimitEntry> window_limit =
            time_window_limit_->entries[WeekdayShift(current_weekday_, i)];
        if (window_limit) {
          TimeWindowLimitBoundaries limits = window_limit->GetLimits(
              LocalMidnight(current_time_) + base::Days(i));
          // Ignores time window limit if it is overridden.
          if (overridden_window_limit_ &&
              ContainsTime(limits.starts, limits.ends, current_time_)) {
            continue;
          }
          if (ContainsTime(limits.starts, limits.ends, next_usage_limit_reset))
            unlock_time = std::max(unlock_time, limits.ends);
        }
      }
    }
  }

  // When a lock override will become inactive.
  if (ShouldBeLockedByOverride()) {
    // The lock override ends either on the next reset time or when a bedtime
    // ends.
    base::Time lock_override_ends;
    if (time_window_limit_ && !IsOverrideDurationFinished()) {
      // Search a time window limit that could start before the lock override
      // ends and retrieve its end time. It could be yesterday's, today's or
      // tomorrow's time window limit.
      for (int i = -1; i <= 1; i++) {
        internal::Weekday weekday = WeekdayShift(current_weekday_, i);
        std::optional<TimeWindowLimitEntry> window_limit =
            time_window_limit_->entries[weekday];
        if (window_limit) {
          base::Time window_limit_start =
              ConvertPolicyTime(window_limit->starts_at, i);

          // Window limit starts after the end of override.
          if (window_limit_start > GetLockOverrideEndTime())
            continue;

          base::Time window_limit_end =
              window_limit_start +
              GetConsecutiveTimeWindowLimitDuration(weekday);

          if (window_limit_end > time_limit_override_->created_at() &&
              IsBefore(window_limit_end, lock_override_ends)) {
            lock_override_ends = window_limit_end;
          }
        }
      }
    }
    // Set override end to default reset time when:
    // 1. No window limit starts before the reset time;
    // 2. Window limit starts and ends before reset time and there is an unlock
    // with duration active. Unlock with duration must lock device at least
    // until the reset time.
    if (lock_override_ends.is_null() || IsOverrideDurationFinished()) {
      lock_override_ends =
          std::max(lock_override_ends, GetLockOverrideEndTime());
    }
    unlock_time = std::max(unlock_time, lock_override_ends);
  }

  return unlock_time;
}

base::Time UsageTimeLimitProcessor::GetNextStateChangeTime(
    PolicyType* out_next_active) {
  base::Time next_change;
  *out_next_active = PolicyType::kNoPolicy;

  base::Time active_time_window_limit_ends = GetActiveTimeLimitEndTime();
  base::Time next_usage_quota_reset = GetNextUsageLimitResetTime();

  // Check when next time window limit starts.
  if (time_window_limit_ && !active_time_window_limit_) {
    internal::Weekday start_day = internal::WeekdayShift(current_weekday_, 1);
    base::TimeDelta delta_from_midnight =
        current_time_ - LocalMidnight(current_time_);
    bool todays_time_limit_not_started =
        time_window_limit_->entries[current_weekday_] &&
        time_window_limit_->entries[current_weekday_]->starts_at >
            delta_from_midnight;
    // If today's time limit has not started yet, start search today.
    if (todays_time_limit_not_started)
      start_day = current_weekday_;

    // Search a time window limit in the next following days.
    for (int i = 0; i < static_cast<int>(internal::Weekday::kCount); i++) {
      std::optional<internal::TimeWindowLimitEntry> entry =
          time_window_limit_.value()
              .entries[internal::WeekdayShift(start_day, i)];
      if (entry) {
        int shift = start_day == current_weekday_ ? 0 : 1;
        base::Time start_time = ConvertPolicyTime(entry->starts_at, i + shift);
        if (IsBefore(start_time, next_change)) {
          next_change = start_time;
          *out_next_active = PolicyType::kFixedLimit;
        }
        break;
      }
    }
  }

  // Minimum time when the current time usage quota could end. Not calculated
  // when time usage limit has already finished.
  if (time_usage_limit_ && !active_time_usage_limit_ &&
      !overridden_usage_limit_ && !active_time_window_limit_) {
    // If there is an active time usage, we just look when it would lock the
    // session if the user don't stop using it.
    if (enabled_time_usage_limit_) {
      base::Time quota_ends =
          current_time_ + (enabled_time_usage_limit_->usage_quota - used_time_);
      if (IsBefore(quota_ends, next_change)) {
        next_change = quota_ends;
        *out_next_active = PolicyType::kUsageLimit;
      }
    }
  }

  // Look for the next time usage, and calculate the minimum time when it could
  // end.
  if (time_usage_limit_) {
    for (int i = 1; i < static_cast<int>(internal::Weekday::kCount); i++) {
      std::optional<internal::TimeUsageLimitEntry> usage_limit_entry =
          time_usage_limit_
              ->entries[internal::WeekdayShift(current_weekday_, i)];
      if (usage_limit_entry) {
        base::Time quota_ends = ConvertPolicyTime(UsageLimitResetTime(), i) +
                                usage_limit_entry->usage_quota;
        if (IsBefore(quota_ends, next_change)) {
          next_change = quota_ends;
          *out_next_active = PolicyType::kUsageLimit;
        }
        break;
      }
    }
  }

  // When the current active time window limit ends.
  if (active_time_window_limit_) {
    if (IsBefore(active_time_window_limit_ends, next_change)) {
      next_change = active_time_window_limit_ends;
      if (active_time_usage_limit_ &&
          used_time_ >= active_time_usage_limit_->usage_quota &&
          active_time_window_limit_ends < next_usage_quota_reset) {
        *out_next_active = PolicyType::kUsageLimit;
      } else {
        *out_next_active = PolicyType::kNoPolicy;
      }
    }
  }

  // When the usage quota resets. Only calculated if there is an enforced time
  // usage limit, and when it ends no other policy would be active.
  if (active_time_usage_limit_ &&
      (!active_time_window_limit_ ||
       active_time_window_limit_->ends_at < UsageLimitResetTime())) {
    if (IsBefore(next_usage_quota_reset, next_change)) {
      next_change = next_usage_quota_reset;
      *out_next_active = PolicyType::kNoPolicy;
    }
  }

  // When a lock override will become inactive. Lock overrides are disabled at
  // the same time as time usage limit resets.
  if (HasActiveOverride() && time_limit_override_->IsLock()) {
    base::Time lock_end = GetLockOverrideEndTime();

    if (IsBefore(lock_end, next_change)) {
      next_change = lock_end;
      if (active_time_window_limit_ &&
          active_time_window_limit_ends > next_usage_quota_reset) {
        *out_next_active = PolicyType::kFixedLimit;
      } else {
        *out_next_active = PolicyType::kNoPolicy;
      }
    }
  }

  // When an override with duration will change the state. It will change either
  // when the duration is over (then the next state will work as a lock
  // override) or at the same time as time usage limit resets.
  if (HasActiveOverrideWithDuration()) {
    base::Time lock_time = time_limit_override_->created_at() +
                           time_limit_override_->duration().value();
    if (!IsOverrideDurationFinished()) {
      next_change = lock_time;
      *out_next_active = PolicyType::kOverride;
    } else {
      next_change = GetLockOverrideEndTime();
      *out_next_active = PolicyType::kNoPolicy;

      if (time_window_limit_) {
        // Check yesterdays, todays or tomorrows window limit, since these can
        // end after the next change time or it can starts at the next change
        // time, if it happens, the next active policy should be fixed limit.
        for (int i = -1; i <= 1; i++) {
          internal::Weekday weekday = WeekdayShift(current_weekday_, i);
          std::optional<TimeWindowLimitEntry> window_limit =
              time_window_limit_->entries[weekday];
          if (window_limit) {
            base::Time window_start =
                ConvertPolicyTime(window_limit->starts_at, i);
            base::Time window_end =
                window_start + GetConsecutiveTimeWindowLimitDuration(weekday);

            if (ContainsTime(window_start, window_end, next_change) ||
                next_change == window_start)
              *out_next_active = PolicyType::kFixedLimit;
          }
        }
      }
    }
  }
  return next_change;
}

bool UsageTimeLimitProcessor::IsTodayTimeWindowLimitActive() {
  if (!time_window_limit_)
    return false;

  std::optional<internal::TimeWindowLimitEntry> yesterday_window_limit =
      time_window_limit_.value()
          .entries[internal::WeekdayShift(current_weekday_, -1)];
  base::TimeDelta delta_from_midnight =
      current_time_ - LocalMidnight(current_time_);

  if ((active_time_window_limit_ || overridden_window_limit_) &&
      (!yesterday_window_limit || !yesterday_window_limit->IsOvernight() ||
       yesterday_window_limit->ends_at < delta_from_midnight)) {
    return true;
  }
  return false;
}

base::TimeDelta UsageTimeLimitProcessor::UsageLimitResetTime() {
  return GetUsageLimitResetTime(time_usage_limit_);
}

base::TimeDelta UsageTimeLimitProcessor::LockOverrideResetTime() {
  // The default behavior is to stop enforcing the lock override at the same
  // time as the time usage limit resets.
  return UsageLimitResetTime();
}

base::Time UsageTimeLimitProcessor::ConvertPolicyTime(
    base::TimeDelta policy_time,
    int shift_in_days) {
  return LocalMidnight(current_time_) + base::Days(shift_in_days) + policy_time;
}

base::Time UsageTimeLimitProcessor::LocalMidnight(base::Time time) {
  base::TimeDelta time_zone_offset = GetTimeZoneOffset(time);
  return (time + time_zone_offset).UTCMidnight() - time_zone_offset;
}

Weekday UsageTimeLimitProcessor::GetCurrentWeekday() {
  base::TimeDelta time_zone_offset = GetTimeZoneOffset(current_time_);
  base::TimeDelta midnight_delta = current_time_ - current_time_.UTCMidnight();
  // Shift in days due to the timezone.
  int time_zone_shift = 0;
  if (midnight_delta + time_zone_offset < base::Hours(0)) {
    time_zone_shift = -1;
  } else if (midnight_delta + time_zone_offset >= base::Hours(24)) {
    time_zone_shift = 1;
  }

  base::Time::Exploded exploded;
  current_time_.UTCExplode(&exploded);
  return WeekdayShift(static_cast<Weekday>(exploded.day_of_week),
                      time_zone_shift);
}

base::TimeDelta UsageTimeLimitProcessor::GetTimeZoneOffset(base::Time time) {
  int32_t raw_offset, dst_offset;
  UErrorCode status = U_ZERO_ERROR;
  time_zone_->getOffset(
      time.InSecondsFSinceUnixEpoch() * base::Time::kMillisecondsPerSecond,
      true /* local */, raw_offset, dst_offset, status);
  base::TimeDelta time_zone_offset =
      base::Milliseconds(raw_offset + dst_offset);
  if (U_FAILURE(status)) {
    LOG(ERROR) << "Failed to get time zone offset, error code: " << status;
    // The fallback case is to get the raw timezone offset ignoring the daylight
    // saving time.
    time_zone_offset = base::Milliseconds(time_zone_->getRawOffset());
  }
  return time_zone_offset;
}

// Transforms the time dictionary sent on the UsageTimeLimit policy to a
// TimeDelta, that represents the distance from midnight.
base::TimeDelta DictToTimeDelta(const base::Value::Dict& policy_time) {
  int hour = policy_time.FindInt(kWindowLimitEntryTimeHour).value();
  int minute = policy_time.FindInt(kWindowLimitEntryTimeMinute).value();
  return base::Minutes(hour * 60 + minute);
}

// Transforms weekday strings into the Weekday enum.
Weekday GetWeekday(std::string weekday) {
  base::ranges::transform(weekday, weekday.begin(), ::tolower);
  for (int i = 0; i < static_cast<int>(Weekday::kCount); i++) {
    if (weekday == kTimeLimitWeekdays[i]) {
      return static_cast<Weekday>(i);
    }
  }

  LOG(ERROR) << "Unexpected weekday " << weekday;
  return Weekday::kSunday;
}

}  // namespace

TimeWindowLimitEntry::TimeWindowLimitEntry() = default;

bool TimeWindowLimitEntry::operator==(const TimeWindowLimitEntry& rhs) const {
  return starts_at == rhs.starts_at && ends_at == rhs.ends_at &&
         last_updated == rhs.last_updated;
}

bool TimeWindowLimitEntry::IsOvernight() const {
  return ends_at < starts_at;
}

TimeWindowLimitBoundaries TimeWindowLimitEntry::GetLimits(
    base::Time start_day_midnight) {
  TimeWindowLimitBoundaries limit;
  limit.starts = start_day_midnight + starts_at;
  limit.ends = start_day_midnight + base::Days(IsOvernight() ? 1 : 0) + ends_at;
  return limit;
}

TimeWindowLimit::TimeWindowLimit(const base::Value& window_limit_val) {
  const base::Value::Dict& window_limit_dict = window_limit_val.GetDict();
  if (!window_limit_dict.contains(kWindowLimitEntries)) {
    return;
  }

  for (const base::Value& entry_val :
       CHECK_DEREF(window_limit_dict.FindList(kWindowLimitEntries))) {
    const base::Value::Dict& entry_dict = entry_val.GetDict();
    const std::string* effective_day =
        entry_dict.FindString(kWindowLimitEntryEffectiveDay);
    const base::Value::Dict* starts_at =
        entry_dict.FindDict(kWindowLimitEntryStartsAt);
    const base::Value::Dict* ends_at =
        entry_dict.FindDict(kWindowLimitEntryEndsAt);
    const std::string* last_updated_value =
        entry_dict.FindString(kTimeLimitLastUpdatedAt);

    if (!effective_day || !starts_at || !ends_at || !last_updated_value) {
      // Missing information, so this entry will be ignored.
      continue;
    }

    int64_t last_updated;
    if (!base::StringToInt64(*last_updated_value, &last_updated)) {
      // Cannot process entry without a valid last updated.
      continue;
    }

    TimeWindowLimitEntry entry;
    entry.starts_at = DictToTimeDelta(*starts_at);
    entry.ends_at = DictToTimeDelta(*ends_at);
    entry.last_updated =
        base::Time::UnixEpoch() + base::Milliseconds(last_updated);

    Weekday weekday = GetWeekday(*effective_day);
    // We only support one time_limit_window per day. If more than one is sent
    // we only use the latest updated.
    if (!entries[weekday] ||
        entries[weekday]->last_updated < entry.last_updated) {
      entries[weekday] = std::move(entry);
    }
  }
}

TimeWindowLimit::~TimeWindowLimit() = default;

TimeWindowLimit::TimeWindowLimit(TimeWindowLimit&&) = default;

TimeWindowLimit& TimeWindowLimit::operator=(TimeWindowLimit&&) = default;

bool TimeWindowLimit::operator==(const TimeWindowLimit& rhs) const {
  return entries == rhs.entries;
}

TimeUsageLimitEntry::TimeUsageLimitEntry() = default;

bool TimeUsageLimitEntry::operator==(const TimeUsageLimitEntry& rhs) const {
  return usage_quota == rhs.usage_quota && last_updated == rhs.last_updated;
}

TimeUsageLimit::TimeUsageLimit(const base::Value::Dict& usage_limit_dict)
    // Default reset time is midnight.
    : resets_at(base::Minutes(0)) {
  const base::Value::Dict* resets_at_value =
      usage_limit_dict.FindDict(kUsageLimitResetAt);
  if (resets_at_value) {
    resets_at = DictToTimeDelta(*resets_at_value);
  }

  for (const std::string& weekday_key : kTimeLimitWeekdays) {
    const base::Value::Dict* entry_dict =
        usage_limit_dict.FindDict(weekday_key);
    if (!entry_dict) {
      continue;
    }

    const std::optional<int> usage_quota =
        entry_dict->FindInt(kUsageLimitUsageQuota);
    const std::string* last_updated_value =
        entry_dict->FindString(kTimeLimitLastUpdatedAt);

    int64_t last_updated;
    if (!base::StringToInt64(CHECK_DEREF(last_updated_value), &last_updated)) {
      // Cannot process entry without a valid last updated.
      continue;
    }

    Weekday weekday = GetWeekday(weekday_key);
    TimeUsageLimitEntry entry;
    entry.usage_quota = base::Minutes(usage_quota.value());
    entry.last_updated =
        base::Time::UnixEpoch() + base::Milliseconds(last_updated);
    entries[weekday] = std::move(entry);
  }
}

TimeUsageLimit::~TimeUsageLimit() = default;

bool TimeUsageLimit::operator==(const TimeUsageLimit& rhs) const {
  return entries == rhs.entries && resets_at == rhs.resets_at;
}

TimeUsageLimit::TimeUsageLimit(TimeUsageLimit&&) = default;

TimeUsageLimit& TimeUsageLimit::operator=(TimeUsageLimit&&) = default;

}  // namespace internal

std::optional<internal::TimeWindowLimit> TimeWindowLimitFromPolicy(
    const base::Value::Dict& time_limit) {
  const base::Value* time_window_limit_value =
      time_limit.Find(internal::kTimeWindowLimit);
  if (!time_window_limit_value)
    return std::nullopt;
  return internal::TimeWindowLimit(*time_window_limit_value);
}

std::optional<internal::TimeUsageLimit> TimeUsageLimitFromPolicy(
    const base::Value::Dict& time_limit) {
  const base::Value* time_usage_limit_value =
      time_limit.Find(internal::kTimeUsageLimit);
  if (!time_usage_limit_value)
    return std::nullopt;
  return internal::TimeUsageLimit(time_usage_limit_value->GetDict());
}

std::optional<TimeLimitOverride> OverrideFromPolicy(
    const base::Value::Dict& time_limit) {
  const base::Value::List* override_value =
      time_limit.FindList(TimeLimitOverride::kOverridesDictKey);
  return TimeLimitOverride::MostRecentFromList(override_value);
}

State GetState(const base::Value::Dict& time_limit,
               const base::Value::Dict* local_override,
               const base::TimeDelta& used_time,
               const base::Time& usage_timestamp,
               const base::Time& current_time,
               const icu::TimeZone* const time_zone,
               const std::optional<State>& previous_state) {
  std::optional<internal::TimeWindowLimit> time_window_limit =
      TimeWindowLimitFromPolicy(time_limit);
  std::optional<internal::TimeUsageLimit> time_usage_limit =
      TimeUsageLimitFromPolicy(time_limit);
  std::optional<TimeLimitOverride> time_limit_override =
      OverrideFromPolicy(time_limit);
  std::optional<TimeLimitOverride> local_time_limit_override =
      TimeLimitOverride::FromDictionary(local_override);
  // TODO(agawronska): Pass |usage_timestamp| instead of second |current_time|.
  return internal::UsageTimeLimitProcessor(
             std::move(time_window_limit), std::move(time_usage_limit),
             std::move(time_limit_override),
             std::move(local_time_limit_override), used_time, current_time,
             current_time, time_zone, previous_state)
      .GetState();
}

base::Time GetExpectedResetTime(const base::Value::Dict& time_limit,
                                const base::Value::Dict* local_override,
                                const base::Time current_time,
                                const icu::TimeZone* const time_zone) {
  std::optional<internal::TimeWindowLimit> time_window_limit =
      TimeWindowLimitFromPolicy(time_limit);
  std::optional<internal::TimeUsageLimit> time_usage_limit =
      TimeUsageLimitFromPolicy(time_limit);
  std::optional<TimeLimitOverride> time_limit_override =
      OverrideFromPolicy(time_limit);
  std::optional<TimeLimitOverride> local_time_limit_override =
      TimeLimitOverride::FromDictionary(local_override);
  return internal::UsageTimeLimitProcessor(
             std::move(time_window_limit), std::move(time_usage_limit),
             std::move(time_limit_override),
             std::move(local_time_limit_override), base::Minutes(0),
             base::Time(), current_time, time_zone, std::nullopt)
      .GetExpectedResetTime();
}

std::optional<base::TimeDelta> GetRemainingTimeUsage(
    const base::Value::Dict& time_limit,
    const base::Value::Dict* local_override,
    const base::Time current_time,
    const base::TimeDelta& used_time,
    const icu::TimeZone* const time_zone) {
  std::optional<internal::TimeWindowLimit> time_window_limit =
      TimeWindowLimitFromPolicy(time_limit);
  std::optional<internal::TimeUsageLimit> time_usage_limit =
      TimeUsageLimitFromPolicy(time_limit);
  std::optional<TimeLimitOverride> time_limit_override =
      OverrideFromPolicy(time_limit);
  std::optional<TimeLimitOverride> local_time_limit_override =
      TimeLimitOverride::FromDictionary(local_override);
  return internal::UsageTimeLimitProcessor(
             std::move(time_window_limit), std::move(time_usage_limit),
             std::move(time_limit_override),
             std::move(local_time_limit_override), used_time, base::Time(),
             current_time, time_zone, std::nullopt)
      .GetRemainingTimeUsage();
}

base::TimeDelta GetTimeUsageLimitResetTime(
    const base::Value::Dict& time_limit) {
  return internal::GetUsageLimitResetTime(TimeUsageLimitFromPolicy(time_limit));
}

std::set<PolicyType> UpdatedPolicyTypes(const base::Value::Dict& old_policy,
                                        const base::Value::Dict& new_policy) {
  std::set<PolicyType> updated_policies;
  if (TimeUsageLimitFromPolicy(old_policy) !=
      TimeUsageLimitFromPolicy(new_policy)) {
    updated_policies.insert(PolicyType::kUsageLimit);
  }
  if (TimeWindowLimitFromPolicy(old_policy) !=
      TimeWindowLimitFromPolicy(new_policy)) {
    updated_policies.insert(PolicyType::kFixedLimit);
  }

  std::optional<TimeLimitOverride> old_override =
      OverrideFromPolicy(old_policy);
  std::optional<TimeLimitOverride> new_override =
      OverrideFromPolicy(new_policy);
  // Override changes are added only when the new override has a duration.
  if (old_override != new_override && new_override &&
      new_override->duration()) {
    updated_policies.insert(PolicyType::kOverride);
  }
  return updated_policies;
}

std::set<PolicyType> GetEnabledTimeLimitPolicies(
    const base::Value::Dict& time_limit_prefs) {
  std::set<PolicyType> enabled_policies;

  std::optional<internal::TimeWindowLimit> time_window_limit =
      TimeWindowLimitFromPolicy(time_limit_prefs);
  if (time_window_limit && !time_window_limit->entries.empty()) {
    enabled_policies.insert(PolicyType::kFixedLimit);
  }

  std::optional<internal::TimeUsageLimit> time_usage_limit =
      TimeUsageLimitFromPolicy(time_limit_prefs);
  if (time_usage_limit && !time_usage_limit->entries.empty()) {
    enabled_policies.insert(PolicyType::kUsageLimit);
  }

  std::optional<TimeLimitOverride> time_limit_override =
      OverrideFromPolicy(time_limit_prefs);
  base::Time now = base::Time::Now();
  // Ignores the override time limit that is not created within 1 day.
  if (time_limit_override && now > time_limit_override->created_at() &&
      now - time_limit_override->created_at() < base::Days(1)) {
    enabled_policies.insert(PolicyType::kOverride);
  }

  return enabled_policies;
}

}  // namespace ash::usage_time_limit