chromium/chrome/browser/ash/child_accounts/time_limits/app_activity_registry_unittest.cc

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

#include "chrome/browser/ash/child_accounts/time_limits/app_activity_registry.h"

#include <map>
#include <memory>
#include <optional>
#include <vector>

#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_service_wrapper.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_limit_utils.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_limits_allowlist_policy_test_utils.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_limits_allowlist_policy_wrapper.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_notification_delegate.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_types.h"
#include "chrome/browser/ash/child_accounts/time_limits/persisted_app_info.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "extensions/common/constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/window_types.h"
#include "ui/aura/window.h"

namespace ash {
namespace app_time {

namespace {

const AppId kApp1(apps::AppType::kArc, "1");
const AppId kApp2(apps::AppType::kWeb, "3");
const AppId kGoogleSlidesApp(apps::AppType::kChromeApp,
                             extension_misc::kGoogleSlidesAppId);

class AppTimeNotificationDelegateMock : public AppTimeNotificationDelegate {
 public:
  AppTimeNotificationDelegateMock() = default;
  AppTimeNotificationDelegateMock(const AppTimeNotificationDelegateMock&) =
      delete;
  AppTimeNotificationDelegateMock& operator=(
      const AppTimeNotificationDelegateMock&) = delete;

  ~AppTimeNotificationDelegateMock() override = default;

  MOCK_METHOD3(ShowAppTimeLimitNotification,
               void(const AppId&,
                    const std::optional<base::TimeDelta>&,
                    AppNotification));
};

class AppStateObserverMock : public AppActivityRegistry::AppStateObserver {
 public:
  AppStateObserverMock() = default;
  AppStateObserverMock(const AppStateObserverMock&) = delete;
  AppStateObserverMock& operator=(const AppStateObserverMock&) = delete;

  ~AppStateObserverMock() override = default;

  MOCK_METHOD3(OnAppLimitReached, void(const AppId&, base::TimeDelta, bool));
  MOCK_METHOD1(OnAppLimitRemoved, void(const AppId&));
  MOCK_METHOD1(OnAppInstalled, void(const AppId&));
};

}  // namespace

class AppActivityRegistryTest : public ChromeViewsTestBase {
 protected:
  AppActivityRegistryTest() = default;
  AppActivityRegistryTest(const AppActivityRegistryTest&) = delete;
  AppActivityRegistryTest& operator=(const AppActivityRegistryTest&) = delete;
  ~AppActivityRegistryTest() override = default;

  // ChromeViewsTestBase:
  void SetUp() override;

  void InstallApps();

  base::UnguessableToken CreateInstanceIdForApp(const AppId& app_id);
  base::UnguessableToken GetInstanceIdForApp(const AppId& app_id);

  void SetAppLimit(const AppId& app_id,
                   const std::optional<AppLimit>& app_limit);

  void ReInitializeRegistry();

  AppActivityRegistry& registry() {
    EXPECT_TRUE(registry_.get());
    return *registry_;
  }
  AppActivityRegistry::TestApi& registry_test() {
    EXPECT_TRUE(registry_test_.get());
    return *registry_test_;
  }

  AppTimeNotificationDelegateMock& notification_delegate_mock() {
    return notification_delegate_mock_;
  }
  PrefService* prefs() { return profile_.GetPrefs(); }

  void CreateAppActivityForApp(const AppId& app_id,
                               base::TimeDelta activity_length);

 private:
  TestingProfile profile_;
  AppTimeNotificationDelegateMock notification_delegate_mock_;
  AppServiceWrapper wrapper_{&profile_};
  std::unique_ptr<AppActivityRegistry> registry_;
  std::unique_ptr<AppActivityRegistry::TestApi> registry_test_;

  std::map<AppId, std::vector<base::UnguessableToken>> instance_ids_;
};

void AppActivityRegistryTest::SetUp() {
  ChromeViewsTestBase::SetUp();
  ReInitializeRegistry();
  InstallApps();
}

void AppActivityRegistryTest::InstallApps() {
  registry().OnAppInstalled(GetChromeAppId());
  registry().OnAppInstalled(kApp1);
  registry().OnAppInstalled(kApp2);
  registry().OnAppAvailable(GetChromeAppId());
  registry().OnAppAvailable(kApp1);
  registry().OnAppAvailable(kApp2);
}

base::UnguessableToken AppActivityRegistryTest::CreateInstanceIdForApp(
    const AppId& app_id) {
  base::UnguessableToken instance_id(base::UnguessableToken::Create());
  instance_ids_[app_id].push_back(instance_id);
  return instance_id;
}

base::UnguessableToken AppActivityRegistryTest::GetInstanceIdForApp(
    const AppId& app_id) {
  const std::vector<base::UnguessableToken>& app_windows =
      instance_ids_.at(app_id);
  EXPECT_GE(app_windows.size(), 0u);
  return app_windows[app_windows.size() - 1];
}

void AppActivityRegistryTest::SetAppLimit(
    const AppId& app_id,
    const std::optional<AppLimit>& app_limit) {
  registry().SetAppLimit(app_id, app_limit);
  task_environment()->RunUntilIdle();
}

void AppActivityRegistryTest::ReInitializeRegistry() {
  registry_ = std::make_unique<AppActivityRegistry>(
      &wrapper_, &notification_delegate_mock_, prefs());

  registry_test_ =
      std::make_unique<AppActivityRegistry::TestApi>(registry_.get());
}

void AppActivityRegistryTest::CreateAppActivityForApp(
    const AppId& app_id,
    base::TimeDelta activity_length) {
  auto app_instance_id = CreateInstanceIdForApp(app_id);
  registry().OnAppActive(app_id, app_instance_id, base::Time::Now());
  task_environment()->FastForwardBy(activity_length);
  registry().OnAppInactive(app_id, app_instance_id, base::Time::Now());
}

TEST_F(AppActivityRegistryTest, RunningActiveTimeCheck) {
  auto app1_instance_id = CreateInstanceIdForApp(kApp1);

  base::Time app1_start_time = base::Time::Now();
  base::TimeDelta active_time = base::Minutes(5);
  registry().OnAppActive(kApp1, app1_instance_id, app1_start_time);
  task_environment()->FastForwardBy(active_time / 2);
  EXPECT_EQ(active_time / 2, registry().GetActiveTime(kApp1));
  EXPECT_TRUE(registry().IsAppActive(kApp1));

  task_environment()->FastForwardBy(active_time / 2);
  base::Time app1_end_time = base::Time::Now();
  registry().OnAppInactive(kApp1, app1_instance_id, app1_end_time);
  EXPECT_EQ(active_time, registry().GetActiveTime(kApp1));
  EXPECT_FALSE(registry().IsAppActive(kApp1));
}

TEST_F(AppActivityRegistryTest, MultipleWindowSameApp) {
  auto app2_instance_id1 = CreateInstanceIdForApp(kApp2);
  auto app2_instance_id2 = CreateInstanceIdForApp(kApp2);

  base::TimeDelta app2_active_time = base::Minutes(5);

  registry().OnAppActive(kApp2, app2_instance_id1, base::Time::Now());
  task_environment()->FastForwardBy(app2_active_time / 2);

  registry().OnAppActive(kApp2, app2_instance_id2, base::Time::Now());
  registry().OnAppInactive(kApp2, app2_instance_id1, base::Time::Now());
  registry().OnAppInactive(kApp2, app2_instance_id1, base::Time::Now());
  EXPECT_TRUE(registry().IsAppActive(kApp2));

  task_environment()->FastForwardBy(app2_active_time / 2);

  // Repeated calls to OnAppInactive shouldn't affect the time calculation.
  registry().OnAppInactive(kApp2, app2_instance_id1, base::Time::Now());

  // Mark the application inactive.
  registry().OnAppInactive(kApp2, app2_instance_id2, base::Time::Now());

  // There was no interruption in active times. Therefore, the app should
  // be active for the whole 5 minutes.
  EXPECT_EQ(app2_active_time, registry().GetActiveTime(kApp2));

  base::TimeDelta app2_inactive_time = base::Minutes(1);

  registry().OnAppActive(kApp2, app2_instance_id1, base::Time::Now());
  task_environment()->FastForwardBy(app2_active_time / 2);

  registry().OnAppInactive(kApp2, app2_instance_id1, base::Time::Now());
  task_environment()->FastForwardBy(app2_inactive_time);
  EXPECT_FALSE(registry().IsAppActive(kApp2));

  registry().OnAppActive(kApp2, app2_instance_id2, base::Time::Now());
  task_environment()->FastForwardBy(app2_active_time / 2);

  registry().OnAppInactive(kApp2, app2_instance_id1, base::Time::Now());
  EXPECT_TRUE(registry().IsAppActive(kApp2));

  registry().OnAppInactive(kApp2, app2_instance_id2, base::Time::Now());
  EXPECT_FALSE(registry().IsAppActive(kApp2));

  EXPECT_EQ(app2_active_time * 2, registry().GetActiveTime(kApp2));
}

TEST_F(AppActivityRegistryTest, AppTimeLimitReachedActiveApp) {
  base::Time start = base::Time::Now();

  // Set the time limit for kApp1 to be 10 minutes.
  const AppLimit limit(AppRestriction::kTimeLimit, base::Minutes(10), start);
  SetAppLimit(kApp1, limit);

  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kAvailable);

  auto app1_instance_id = CreateInstanceIdForApp(kApp1);

  registry().OnAppActive(kApp1, app1_instance_id, start);

  // Expect 5 minute left notification.
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, testing::_,
                                           AppNotification::kFiveMinutes))
      .Times(1);
  task_environment()->FastForwardBy(base::Minutes(5));
  EXPECT_EQ(base::Minutes(5), registry().GetActiveTime(kApp1));
  EXPECT_TRUE(registry().IsAppActive(kApp1));

  // Expect One minute left notification.
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, testing::_,
                                           AppNotification::kOneMinute))
      .Times(1);
  task_environment()->FastForwardBy(base::Minutes(4));
  EXPECT_EQ(base::Minutes(9), registry().GetActiveTime(kApp1));

  // Expect time limit reached notification.
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, testing::_,
                                           AppNotification::kTimeLimitReached))
      .Times(1);
  task_environment()->FastForwardBy(base::Minutes(1));
  EXPECT_EQ(base::Minutes(10), registry().GetActiveTime(kApp1));

  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kLimitReached);
}

TEST_F(AppActivityRegistryTest, SkippedFiveMinuteNotification) {
  // The application is inactive when the time limit is reached.
  base::Time start = base::Time::Now();

  // Set the time limit for kApp1 to be 25 minutes.
  const AppLimit limit(AppRestriction::kTimeLimit, base::Minutes(25), start);
  SetAppLimit(kApp1, limit);

  auto app1_instance_id = CreateInstanceIdForApp(kApp1);
  base::TimeDelta active_time = base::Minutes(10);
  registry().OnAppActive(kApp1, app1_instance_id, start);

  task_environment()->FastForwardBy(active_time);

  const AppLimit new_limit(AppRestriction::kTimeLimit, base::Minutes(14),
                           start + active_time);
  SetAppLimit(kApp1, new_limit);

  // Notice that the 5 minute notification is jumped.
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, testing::_,
                                           AppNotification::kOneMinute))
      .Times(1);
  task_environment()->FastForwardBy(base::Minutes(3));
}

TEST_F(AppActivityRegistryTest, SkippedAllNotifications) {
  // The application is inactive when the time limit is reached.
  base::Time start = base::Time::Now();

  // Set the time limit for kApp1 to be 25 minutes.
  const AppLimit limit(AppRestriction::kTimeLimit, base::Minutes(25), start);
  SetAppLimit(kApp1, limit);

  auto app1_instance_id = CreateInstanceIdForApp(kApp1);
  base::TimeDelta active_time = base::Minutes(10);
  registry().OnAppActive(kApp1, app1_instance_id, start);

  task_environment()->FastForwardBy(active_time);

  // Notice that the 5 minute and 1 minute notifications are jumped.
  const AppLimit new_limit(AppRestriction::kTimeLimit, base::Minutes(5),
                           start + active_time);
  SetAppLimit(kApp1, new_limit);

  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kLimitReached);
}

TEST_F(AppActivityRegistryTest, BlockedAppSetAvailable) {
  base::Time start = base::Time::Now();

  const base::TimeDelta kTenMinutes = base::Minutes(10);
  const AppLimit limit(AppRestriction::kTimeLimit, kTenMinutes, start);
  SetAppLimit(kApp1, limit);

  auto app1_instance_id = CreateInstanceIdForApp(kApp1);
  registry().OnAppActive(kApp1, app1_instance_id, start);

  // There are going to be a bunch of mock notification calls for kFiveMinutes,
  // kOneMinute, and kTimeLimitReached. They have already been tested in the
  // other tests. Let's igonre them.
  task_environment()->FastForwardBy(kTenMinutes);

  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kLimitReached);

  const AppLimit new_limit(AppRestriction::kTimeLimit, base::Minutes(20),
                           start + kTenMinutes);
  SetAppLimit(kApp1, new_limit);
  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kAvailable);
}

TEST_F(AppActivityRegistryTest, ResetTimeReached) {
  base::Time start = base::Time::Now();
  const base::TimeDelta kTenMinutes = base::Minutes(10);

  const AppLimit limit1(AppRestriction::kTimeLimit, kTenMinutes, start);
  const AppLimit limit2(AppRestriction::kTimeLimit, base::Minutes(20), start);
  const std::map<AppId, AppLimit> limits{{kApp1, limit1},
                                         {GetChromeAppId(), limit2}};
  registry().UpdateAppLimits(limits);

  auto app1_instance_id = CreateInstanceIdForApp(kApp1);
  auto app2_instance_id = CreateInstanceIdForApp(kApp2);
  registry().OnAppActive(kApp1, app1_instance_id, start);
  registry().OnAppActive(kApp2, app2_instance_id, start);

  task_environment()->FastForwardBy(kTenMinutes);

  // App 1's time limit has been reached.
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp1));
  EXPECT_EQ(kTenMinutes, registry().GetActiveTime(kApp1));

  // App 2 is still active.
  EXPECT_FALSE(registry().IsAppTimeLimitReached(kApp2));
  EXPECT_EQ(kTenMinutes, registry().GetActiveTime(kApp2));

  // Reset time has been reached.
  registry().OnResetTimeReached(start + kTenMinutes);
  EXPECT_FALSE(registry().IsAppTimeLimitReached(kApp1));
  EXPECT_EQ(base::Seconds(0), registry().GetActiveTime(kApp1));
  EXPECT_FALSE(registry().IsAppTimeLimitReached(kApp2));
  EXPECT_EQ(base::Seconds(0), registry().GetActiveTime(kApp2));

  // Now make sure that the timers have been scheduled appropriately.
  registry().OnAppActive(kApp1, app1_instance_id, start);

  task_environment()->FastForwardBy(kTenMinutes);

  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp1));
  EXPECT_EQ(kTenMinutes, registry().GetActiveTime(kApp1));

  // App 2 is still active.
  EXPECT_FALSE(registry().IsAppTimeLimitReached(kApp2));
  EXPECT_EQ(kTenMinutes, registry().GetActiveTime(kApp2));

  // Now let's make sure
  task_environment()->FastForwardBy(kTenMinutes);
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp2));
  EXPECT_EQ(*limit2.daily_limit(), registry().GetActiveTime(kApp2));
}

TEST_F(AppActivityRegistryTest, SharedTimeLimitForChromeAndWebApps) {
  base::Time start = base::Time::Now();
  const base::TimeDelta kOneHour = base::Hours(1);
  const base::TimeDelta kHalfHour = base::Minutes(30);

  const AppId kChromeAppId = GetChromeAppId();

  const AppLimit limit1(AppRestriction::kTimeLimit, kOneHour, start);
  const std::map<AppId, AppLimit> limits{{kChromeAppId, limit1}};

  registry().UpdateAppLimits(limits);

  auto app2_instance_id = CreateInstanceIdForApp(kApp2);

  // Make chrome active for 30 minutes.
  registry().OnChromeAppActivityChanged(ChromeAppActivityState::kActive, start);
  task_environment()->FastForwardBy(kHalfHour);
  registry().OnChromeAppActivityChanged(ChromeAppActivityState::kInactive,
                                        start + kHalfHour);

  // Expect that the active running time for app2 has been updated.
  EXPECT_EQ(kHalfHour, registry().GetActiveTime(kChromeAppId));
  EXPECT_EQ(kHalfHour, registry().GetActiveTime(kApp2));

  // Make |kApp2| active for 30 minutes. Expect that it reaches its time limit.
  registry().OnAppActive(kApp2, app2_instance_id, start + kHalfHour);
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp2, testing::_,
                                           AppNotification::kFiveMinutes))
      .Times(1);
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp2, testing::_,
                                           AppNotification::kOneMinute))
      .Times(1);
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp2, testing::_,
                                           AppNotification::kTimeLimitReached))
      .Times(1);

  task_environment()->FastForwardBy(kHalfHour);

  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp2));
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kChromeAppId));
}

TEST_F(AppActivityRegistryTest, LimitChangedForActiveApp) {
  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kAvailable);

  auto app1_instance_id = CreateInstanceIdForApp(kApp1);
  base::Time start = base::Time::Now();
  registry().OnAppActive(kApp1, app1_instance_id, start);

  EXPECT_TRUE(registry().IsAppActive(kApp1));
  EXPECT_EQ(base::Minutes(0), registry().GetActiveTime(kApp1));
  EXPECT_EQ(std::nullopt, registry_test().GetAppLimit(kApp1));
  EXPECT_EQ(std::nullopt, registry().GetTimeLimit(kApp1));
  EXPECT_EQ(std::nullopt, registry_test().GetTimeLeft(kApp1));

  task_environment()->FastForwardBy(base::Minutes(5));

  // Limit set for active app.
  const AppLimit limit1(AppRestriction::kTimeLimit, base::Minutes(11),
                        base::Time::Now());
  SetAppLimit(kApp1, limit1);

  EXPECT_TRUE(registry().IsAppActive(kApp1));
  EXPECT_EQ(base::Minutes(5), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(11), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(6), registry_test().GetTimeLeft(kApp1));

  task_environment()->FastForwardBy(base::Minutes(5));

  EXPECT_TRUE(registry().IsAppActive(kApp1));
  EXPECT_EQ(base::Minutes(10), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(11), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(1), registry_test().GetTimeLeft(kApp1));

  // Increase the limit.
  const AppLimit limit_increase(AppRestriction::kTimeLimit, base::Minutes(20),
                                base::Time::Now());
  SetAppLimit(kApp1, limit_increase);
  EXPECT_TRUE(registry().IsAppActive(kApp1));
  EXPECT_EQ(base::Minutes(10), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(20), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(10), registry_test().GetTimeLeft(kApp1));

  // Decrease the limit.
  const AppLimit limit_decrease(AppRestriction::kTimeLimit, base::Minutes(5),
                                base::Time::Now());
  SetAppLimit(kApp1, limit_decrease);
  EXPECT_FALSE(registry().IsAppActive(kApp1));
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp1));
  EXPECT_EQ(base::Minutes(10), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(5), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(0), registry_test().GetTimeLeft(kApp1));
}

TEST_F(AppActivityRegistryTest, LimitChangesForInactiveApp) {
  // Set initial limit.
  const AppLimit limit(AppRestriction::kTimeLimit, base::Minutes(5),
                       base::Time::Now());
  SetAppLimit(kApp1, limit);

  // Use available limit - app should become paused.
  auto app1_instance_id = CreateInstanceIdForApp(kApp1);
  registry().OnAppActive(kApp1, app1_instance_id, base::Time::Now());
  task_environment()->FastForwardBy(base::Minutes(5));

  EXPECT_FALSE(registry().IsAppActive(kApp1));
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp1));
  EXPECT_EQ(base::Minutes(5), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(5), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(0), registry_test().GetTimeLeft(kApp1));

  // Decrease limit - app should remain paused.
  const AppLimit decreased_limit(AppRestriction::kTimeLimit, base::Minutes(3),
                                 base::Time::Now());
  SetAppLimit(kApp1, decreased_limit);

  EXPECT_FALSE(registry().IsAppActive(kApp1));
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp1));
  EXPECT_EQ(base::Minutes(5), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(3), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(0), registry_test().GetTimeLeft(kApp1));

  // Increase limit - app should become available, but inactive.
  const AppLimit increased_limit(AppRestriction::kTimeLimit, base::Minutes(10),
                                 base::Time::Now());
  SetAppLimit(kApp1, increased_limit);

  EXPECT_FALSE(registry().IsAppActive(kApp1));
  EXPECT_TRUE(registry().IsAppAvailable(kApp1));
  EXPECT_EQ(base::Minutes(5), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(10), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(5), registry_test().GetTimeLeft(kApp1));

  // Decrease limit above used time - app should stay available.
  const AppLimit limit_above_used(AppRestriction::kTimeLimit, base::Minutes(8),
                                  base::Time::Now());
  SetAppLimit(kApp1, limit_above_used);

  EXPECT_FALSE(registry().IsAppActive(kApp1));
  EXPECT_TRUE(registry().IsAppAvailable(kApp1));
  EXPECT_EQ(base::Minutes(5), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(8), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(3), registry_test().GetTimeLeft(kApp1));

  // Decrease limit below below time - app should become unavailabe.
  const AppLimit limit_below_used(AppRestriction::kTimeLimit, base::Minutes(4),
                                  base::Time::Now());
  SetAppLimit(kApp1, limit_below_used);

  EXPECT_FALSE(registry().IsAppActive(kApp1));
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp1));
  EXPECT_EQ(base::Minutes(5), registry().GetActiveTime(kApp1));
  EXPECT_EQ(base::Minutes(4), *registry().GetTimeLimit(kApp1));
  EXPECT_EQ(base::Minutes(0), *registry_test().GetTimeLeft(kApp1));
}

TEST_F(AppActivityRegistryTest, RemoveLimitsFromAllowlistedApps) {
  // Set initial limit.
  const AppLimit limit(AppRestriction::kTimeLimit, base::Minutes(5),
                       base::Time::Now());
  SetAppLimit(kApp1, limit);
  SetAppLimit(kApp2, limit);

  AppTimeLimitsAllowlistPolicyBuilder builder;
  builder.SetUp();
  builder.AppendToAllowlistAppList(kApp1);

  AppTimeLimitsAllowlistPolicyWrapper wrapper(&builder.dict());
  registry().OnTimeLimitAllowlistChanged(wrapper);

  EXPECT_FALSE(registry_test().GetAppLimit(kApp1));
  EXPECT_EQ(limit.daily_limit(), *registry().GetTimeLimit(kApp2));
  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kAlwaysAvailable);
}

TEST_F(AppActivityRegistryTest, AllowlistedAppsNoLimits) {
  AppTimeLimitsAllowlistPolicyBuilder builder;
  builder.SetUp();
  builder.AppendToAllowlistAppList(kApp1);
  AppTimeLimitsAllowlistPolicyWrapper wrapper(&builder.dict());
  registry().OnTimeLimitAllowlistChanged(wrapper);

  // Set initial limit.
  const AppLimit limit(AppRestriction::kTimeLimit, base::Minutes(5),
                       base::Time::Now());
  SetAppLimit(kApp1, limit);
  SetAppLimit(kApp2, limit);

  EXPECT_FALSE(registry_test().GetAppLimit(kApp1));
  EXPECT_EQ(limit.daily_limit(), *registry().GetTimeLimit(kApp2));
  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kAlwaysAvailable);
}

TEST_F(AppActivityRegistryTest, RestoredApplicationInformation) {
  auto app1_instance_id = CreateInstanceIdForApp(kApp1);
  base::TimeDelta active_timedelta = base::Minutes(30);

  const AppLimit limit(AppRestriction::kTimeLimit, active_timedelta,
                       base::Time::Now());
  SetAppLimit(kApp1, limit);

  base::Time app1_start_time_1 = base::Time::Now();
  registry().OnAppActive(kApp1, app1_instance_id, app1_start_time_1);
  task_environment()->FastForwardBy(active_timedelta / 2);

  // Save app activity.
  registry_test().SaveAppActivity();

  base::Time app1_inactive_time_1 = base::Time::Now();
  registry().OnAppInactive(kApp1, app1_instance_id, app1_inactive_time_1);

  // App1 is inactive for 5 minutes.
  task_environment()->FastForwardBy(base::Minutes(5));

  base::Time app1_start_time_2 = base::Time::Now();
  registry().OnAppActive(kApp1, app1_instance_id, app1_start_time_2);
  task_environment()->FastForwardBy(active_timedelta / 2);

  // Time limit is reached. App becomes inactive.
  EXPECT_FALSE(registry().IsAppActive(kApp1));
  base::Time app1_inactive_time_2 = base::Time::Now();

  // Save app activity.
  registry_test().SaveAppActivity();

  // Now let's recreate AppActivityRegistry. Its state should be restored.
  ReInitializeRegistry();

  EXPECT_TRUE(registry().IsAppInstalled(kApp1));
  EXPECT_TRUE(registry().IsAppInstalled(kApp2));
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp1));
  EXPECT_TRUE(registry().IsAppAvailable(kApp2));
  EXPECT_EQ(registry().GetActiveTime(kApp1), active_timedelta);

  // Now let's test that the app activity are stored appropriately.
  const base::Value::List& list =
      prefs()->GetList(prefs::kPerAppTimeLimitsAppActivities);

  const std::vector<PersistedAppInfo> app_infos =
      PersistedAppInfo::PersistedAppInfosFromList(
          list,
          /* include_app_activity_array */ true);

  // 3 applications. kApp1, kApp2 and Chrome browser.
  EXPECT_TRUE(app_infos.size() == 3);
  std::vector<AppActivity::ActiveTime> app1_times = {{
      AppActivity::ActiveTime(app1_start_time_1, app1_inactive_time_1),
      AppActivity::ActiveTime(app1_start_time_2, app1_inactive_time_2),
  }};

  for (const auto& app_info : app_infos) {
    if (app_info.app_id() == kApp1) {
      const std::vector<AppActivity::ActiveTime> active_time =
          app_info.active_times();
      EXPECT_EQ(app1_times.size(), active_time.size());
      for (size_t i = 0; i < app1_times.size(); i++) {
        EXPECT_EQ(app1_times[i], active_time[i]);
      }

    } else {
      EXPECT_TRUE(app_info.active_times().size() == 0);
    }
  }
}

TEST_F(AppActivityRegistryTest, RemoveUninstalledApplications) {
  CreateAppActivityForApp(kApp1, base::Hours(1));
  CreateAppActivityForApp(kApp2, base::Hours(1));

  // App1 has been uninstalled.
  registry().OnAppUninstalled(kApp1);
  task_environment()->FastForwardBy(base::Minutes(10));

  // Removes kApp1 and cleans up ActiveTimes list in user pref.
  registry().OnSuccessfullyReported(base::Time::Now());

  // Now let's test that the app activity are stored appropriately.
  const base::Value::List& list =
      prefs()->GetList(prefs::kPerAppTimeLimitsAppActivities);

  const std::vector<PersistedAppInfo> app_infos =
      PersistedAppInfo::PersistedAppInfosFromList(
          list,
          /* include_app_activity_array */ true);

  EXPECT_EQ(app_infos.size(), 3u);
  for (const auto& entry : app_infos)
    EXPECT_EQ(entry.active_times().size(), 0u);

  // kApp1 will still be present since it still has activity.
  registry().OnResetTimeReached(base::Time::Now());
  registry().SaveAppActivity();
  registry().OnSuccessfullyReported(base::Time::Now());

  const base::Value::List& new_list =
      prefs()->GetList(prefs::kPerAppTimeLimitsAppActivities);

  const std::vector<PersistedAppInfo> final_app_infos =
      PersistedAppInfo::PersistedAppInfosFromList(
          new_list,
          /* include_app_activity_array */ false);

  // Two apps left. They are Chrome, and kApp2.
  EXPECT_EQ(final_app_infos.size(), 2u);
  for (const auto& entry : final_app_infos) {
    EXPECT_NE(entry.app_id(), kApp1);
  }
}

TEST_F(AppActivityRegistryTest, RemoveOldEntries) {
  base::Time start_time = base::Time::Now();

  CreateAppActivityForApp(kApp1, base::Hours(1));
  CreateAppActivityForApp(kApp2, base::Hours(1));

  prefs()->SetInt64(prefs::kPerAppTimeLimitsLastSuccessfulReportTime,
                    start_time.ToDeltaSinceWindowsEpoch().InMicroseconds());

  task_environment()->AdvanceClock(base::Days(30));
  task_environment()->RunUntilIdle();

  // Now let's recreate AppActivityRegistry. Its state should be restored.
  ReInitializeRegistry();

  // Now let's test that the app activity are stored appropriately.
  const base::Value::List& list =
      prefs()->GetList(prefs::kPerAppTimeLimitsAppActivities);

  const std::vector<PersistedAppInfo> app_infos =
      PersistedAppInfo::PersistedAppInfosFromList(
          list,
          /* include_app_activity_array */ true);

  // The app activities have been cleared.
  for (const auto& app_info : app_infos) {
    const std::vector<AppActivity::ActiveTime>& active_time =
        app_info.active_times();
    EXPECT_EQ(active_time.size(), 0u);
  }
}

TEST_F(AppActivityRegistryTest, ActiveWebAppBlocked) {
  // Create activity for web app.
  CreateAppActivityForApp(kApp2, base::Hours(1));

  // Set Chrome as active.
  registry().OnChromeAppActivityChanged(ChromeAppActivityState::kActive,
                                        base::Time::Now());

  // Update the time limits for Chrome.
  AppLimit chrome_limit(AppRestriction::kTimeLimit, base::Minutes(30),
                        base::Time::Now());

  std::map<AppId, AppLimit> app_limits = {{GetChromeAppId(), chrome_limit}};
  registry().UpdateAppLimits(app_limits);

  // Web time limit should be reached.
  EXPECT_EQ(registry().GetAppState(kApp2), AppState::kLimitReached);

  EXPECT_EQ(registry().GetAppState(GetChromeAppId()), AppState::kLimitReached);
}

TEST_F(AppActivityRegistryTest, OverrideLimitReachedState) {
  AppStateObserverMock state_observer_mock;
  registry().AddAppStateObserver(&state_observer_mock);
  const base::TimeDelta limit = base::Minutes(30);

  std::map<AppId, AppLimit> app_limits = {
      {kApp1, AppLimit(AppRestriction::kTimeLimit, limit, base::Time::Now())},
      {GetChromeAppId(),
       AppLimit(AppRestriction::kTimeLimit, limit, base::Time::Now())}};

  registry().UpdateAppLimits(app_limits);

  // Save app activity and reinitialize.
  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp1, base::Minutes(30),
                                                     /* was_active */ true))
      .Times(1);
  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp2, base::Minutes(30),
                                                     /* was_active */ true))
      .Times(1);
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(GetChromeAppId(), base::Minutes(30),
                                /* was_active */ false))
      .Times(1);

  // App limits will be reached.
  CreateAppActivityForApp(kApp1, 2 * limit);
  CreateAppActivityForApp(kApp2, 2 * limit);

  // Save app activity and reinitialize.
  registry().SaveAppActivity();
  ReInitializeRegistry();
  registry().AddAppStateObserver(&state_observer_mock);
  registry().UpdateAppLimits(app_limits);

  // When OnAppInstalled is called for AppActivityRegistry, it will notify its
  // app state observers that the app time limit has been reached.
  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp1, base::Minutes(30),
                                                     /* was_active */ false))
      .Times(1);
  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp2, base::Minutes(30),
                                                     /* was_active */ false))
      .Times(1);
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(GetChromeAppId(), base::Minutes(30),
                                /* was_active */ false))
      .Times(1);
  InstallApps();

  EXPECT_EQ(registry().GetAppState(kApp1), AppState::kLimitReached);
  EXPECT_EQ(registry().GetAppState(kApp2), AppState::kLimitReached);
  EXPECT_EQ(registry().GetAppState(GetChromeAppId()), AppState::kLimitReached);

  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp1, base::Minutes(30),
                                                     /* was_active */ true))
      .Times(1);
  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp2, base::Minutes(30),
                                                     /* was_active */ true))
      .Times(1);

  registry().OnAppActive(kApp1, GetInstanceIdForApp(kApp1), base::Time::Now());
  registry().OnAppActive(kApp2, GetInstanceIdForApp(kApp2), base::Time::Now());
}

TEST_F(AppActivityRegistryTest, AvoidReduntantNotifications) {
  const base::TimeDelta delta = base::Minutes(5);
  AppLimit chrome_limit(AppRestriction::kTimeLimit, base::Minutes(30),
                        base::Time::Now());
  AppLimit app1_limit(AppRestriction::kTimeLimit, base::Minutes(5),
                      base::Time::Now() + delta);
  std::map<AppId, AppLimit> app_limits = {{GetChromeAppId(), chrome_limit},
                                          {kApp1, app1_limit}};
  EXPECT_CALL(
      notification_delegate_mock(),
      ShowAppTimeLimitNotification(GetChromeAppId(), chrome_limit.daily_limit(),
                                   AppNotification::kTimeLimitChanged))
      .Times(1);

  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, app1_limit.daily_limit(),
                                           AppNotification::kTimeLimitChanged))
      .Times(1);

  registry().UpdateAppLimits(app_limits);
  registry().SaveAppActivity();

  // Reinitialized the registry. We don't expect redundant time limit updatese
  // will result in notifications.
  ReInitializeRegistry();
  registry().OnAppInstalled(GetChromeAppId());
  registry().OnAppInstalled(kApp1);
  registry().OnAppInstalled(kApp2);

  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(GetChromeAppId(), testing::_,
                                           AppNotification::kTimeLimitChanged))
      .Times(0);
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, testing::_,
                                           AppNotification::kTimeLimitChanged))
      .Times(0);

  registry().UpdateAppLimits(app_limits);

  // Update the limit for Chrome.
  AppLimit new_chrome_limit(AppRestriction::kTimeLimit, base::Minutes(15),
                            base::Time::Now() + 2 * delta);
  app_limits.at(GetChromeAppId()) = new_chrome_limit;

  // Expect that there will be a notification for Chrome but not for kApp1.
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(GetChromeAppId(),
                                           new_chrome_limit.daily_limit(),
                                           AppNotification::kTimeLimitChanged))
      .Times(1);
  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, testing::_,
                                           AppNotification::kTimeLimitChanged))
      .Times(0);
  registry().UpdateAppLimits(app_limits);
}

TEST_F(AppActivityRegistryTest, NoNotification) {
  AppLimit app1_limit(AppRestriction::kTimeLimit, base::Minutes(30),
                      base::Time::Now());
  std::map<AppId, AppLimit> app_limits = {{kApp1, app1_limit}};

  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, app1_limit.daily_limit(),
                                           AppNotification::kTimeLimitChanged))
      .Times(0);
  registry().SaveAppActivity();
  ReInitializeRegistry();
  registry().UpdateAppLimits(app_limits);
}

TEST_F(AppActivityRegistryTest, NotificationAfterAppInstall) {
  AppLimit app1_limit(AppRestriction::kTimeLimit, base::Minutes(30),
                      base::Time::Now());
  std::map<AppId, AppLimit> app_limits = {{kApp1, app1_limit}};

  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, app1_limit.daily_limit(),
                                           AppNotification::kTimeLimitChanged))
      .Times(1);

  registry().SaveAppActivity();
  ReInitializeRegistry();
  registry().UpdateAppLimits(app_limits);
  registry().OnAppInstalled(kApp1);
}

TEST_F(AppActivityRegistryTest, AvoidRedundantCallsToPauseApp) {
  AppStateObserverMock state_observer_mock;
  registry().AddAppStateObserver(&state_observer_mock);

  const base::TimeDelta kOneHour = base::Hours(1);
  registry().SetAppLimit(
      kApp1, AppLimit(AppRestriction::kTimeLimit, kOneHour, base::Time::Now()));

  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp1, base::Hours(1),
                                                     /* was_active */ true))
      .Times(1);
  CreateAppActivityForApp(kApp1, kOneHour);
  EXPECT_TRUE(registry().IsAppTimeLimitReached(kApp1));

  auto app1_instance_id = GetInstanceIdForApp(kApp1);
  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp1, base::Hours(1),
                                                     /* was_active */ true))
      .Times(0);
  registry().OnAppActive(kApp1, app1_instance_id, base::Time::Now());

  auto new_app1_instance_id = CreateInstanceIdForApp(kApp1);
  EXPECT_CALL(state_observer_mock, OnAppLimitReached(kApp1, base::Hours(1),
                                                     /* was_active */ true))
      .Times(1);
  registry().OnAppActive(kApp1, new_app1_instance_id, base::Time::Now());

  registry().OnAppDestroyed(kApp1, new_app1_instance_id, base::Time::Now());
}

TEST_F(AppActivityRegistryTest, AppReinstallations) {
  AppStateObserverMock state_observer_mock;
  registry().AddAppStateObserver(&state_observer_mock);

  AppLimit app1_limit(AppRestriction::kTimeLimit, base::Hours(1),
                      base::Time::Now());

  SetAppLimit(kApp1, app1_limit);

  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(kApp1, app1_limit.daily_limit().value(),
                                /* was_active */ true))
      .Times(1);

  // Application will reach its time limits.
  CreateAppActivityForApp(kApp1, base::Hours(2));
  registry().OnAppUninstalled(kApp1);
  registry().SaveAppActivity();

  // Now let's reinstantiate the registry.
  ReInitializeRegistry();
  registry().AddAppStateObserver(&state_observer_mock);

  EXPECT_CALL(state_observer_mock, OnAppInstalled(kApp1)).Times(1);

  // The child user reinstalls the application.
  registry().OnAppInstalled(kApp1);
  registry().OnAppAvailable(kApp1);

  // Let's set the time limit.
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(kApp1, app1_limit.daily_limit().value(),
                                /* was_active */ false))
      .Times(1);
  registry().SetAppLimit(kApp1, app1_limit);

  // Reinstalled within the same session.
  registry().OnAppUninstalled(kApp1);
  EXPECT_CALL(state_observer_mock, OnAppInstalled(kApp1)).Times(1);
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(kApp1, app1_limit.daily_limit().value(),
                                /* was_active */ false))
      .Times(1);

  registry().OnAppAvailable(kApp1);
}

TEST_F(AppActivityRegistryTest, LimitSetAfterActivity) {
  AppStateObserverMock state_observer_mock;
  registry().AddAppStateObserver(&state_observer_mock);

  const AppId kApp3(apps::AppType::kWeb, "l");
  registry().OnAppInstalled(kApp3);
  registry().OnAppAvailable(kApp3);

  CreateAppActivityForApp(kApp3, base::Hours(1));

  registry().OnAppActive(kApp3, CreateInstanceIdForApp(kApp3),
                         base::Time::Now());

  const AppLimit web_limit(AppRestriction::kTimeLimit, base::Minutes(20),
                           base::Time::Now());
  EXPECT_CALL(
      state_observer_mock,
      OnAppLimitReached(GetChromeAppId(), web_limit.daily_limit().value(),
                        /* was_active */ false))
      .Times(1);
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(kApp2, web_limit.daily_limit().value(),
                                /* was_active */ false))
      .Times(1);
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(kApp3, web_limit.daily_limit().value(),
                                /* was_active */ true))
      .Times(1);
  const std::map<AppId, AppLimit> limits{{GetChromeAppId(), web_limit}};
  registry().UpdateAppLimits(limits);
}

TEST_F(AppActivityRegistryTest, WebAppInstalled) {
  AppStateObserverMock state_observer_mock;
  registry().AddAppStateObserver(&state_observer_mock);
  const AppLimit web_limit(AppRestriction::kTimeLimit, base::Hours(2),
                           base::Time::Now());
  const std::map<AppId, AppLimit> limits{{GetChromeAppId(), web_limit}};
  registry().UpdateAppLimits(limits);

  registry().OnAppActive(kApp2, CreateInstanceIdForApp(kApp2),
                         base::Time::Now());
  task_environment()->FastForwardBy(base::Hours(1));

  // Now a new application is installed.
  const AppId kApp3(apps::AppType::kWeb, "l");

  registry().OnAppInstalled(kApp3);
  registry().OnAppAvailable(kApp3);

  EXPECT_CALL(
      state_observer_mock,
      OnAppLimitReached(GetChromeAppId(), web_limit.daily_limit().value(),
                        /* was_active */ false))
      .Times(1);
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(kApp2, web_limit.daily_limit().value(),
                                /* was_active */ true))
      .Times(1);
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(kApp3, web_limit.daily_limit().value(),
                                /* was_active */ false))
      .Times(1);
  task_environment()->FastForwardBy(base::Hours(1));
}

TEST_F(AppActivityRegistryTest, AppBlocked) {
  const AppLimit app1_limit(AppRestriction::kBlocked, std::nullopt,
                            base::Time::Now());
  const std::map<AppId, AppLimit> limits{{kApp1, app1_limit}};

  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, testing::_,
                                           AppNotification::kBlocked))
      .Times(1);
  registry().UpdateAppLimits(limits);

  EXPECT_CALL(notification_delegate_mock(),
              ShowAppTimeLimitNotification(kApp1, testing::_,
                                           AppNotification::kAvailable))
      .Times(1);

  registry().UpdateAppLimits(std::map<AppId, AppLimit>());
}

TEST_F(AppActivityRegistryTest, GoogleSlidesPaused) {
  registry().OnAppInstalled(kGoogleSlidesApp);
  registry().OnAppAvailable(kGoogleSlidesApp);
  AppStateObserverMock state_observer_mock;
  registry().AddAppStateObserver(&state_observer_mock);
  const AppLimit web_limit(AppRestriction::kTimeLimit, base::Hours(2),
                           base::Time::Now());
  const std::map<AppId, AppLimit> limits{{GetChromeAppId(), web_limit}};
  registry().UpdateAppLimits(limits);
  EXPECT_EQ(registry().GetTimeLimit(kGoogleSlidesApp), web_limit.daily_limit());

  EXPECT_CALL(
      state_observer_mock,
      OnAppLimitReached(GetChromeAppId(), web_limit.daily_limit().value(),
                        /* was_active */ false))
      .Times(1);
  EXPECT_CALL(state_observer_mock,
              OnAppLimitReached(kApp2, web_limit.daily_limit().value(),
                                /* was_active */ false))
      .Times(1);
  EXPECT_CALL(
      state_observer_mock,
      OnAppLimitReached(kGoogleSlidesApp, web_limit.daily_limit().value(),
                        /* was_active */ true))
      .Times(1);

  CreateAppActivityForApp(kApp2, base::Hours(1));
  CreateAppActivityForApp(kGoogleSlidesApp, base::Hours(1));
}

}  // namespace app_time
}  // namespace ash