chromium/chrome/browser/ash/policy/status_collector/child_status_collector_browsertest.cc

// Copyright 2019 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 <stddef.h>
#include <stdint.h>

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_path_override.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "chrome/browser/ash/child_accounts/child_user_service.h"
#include "chrome/browser/ash/child_accounts/child_user_service_factory.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_activity_registry.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_controller.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_limits_policy_builder.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_types.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/ownership/fake_owner_settings_service.h"
#include "chrome/browser/ash/policy/status_collector/child_status_collector.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_unit_test_suite.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
#include "chromeos/ash/components/dbus/update_engine/fake_update_engine_client.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/settings/timezone_settings.h"
#include "chromeos/ash/components/system/fake_statistics_provider.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power_manager/idle.pb.h"
#include "components/account_id/account_id.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_service.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_type.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "mojo/core/embedder/embedder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace policy {

namespace {

namespace em = ::enterprise_management;

using ::base::Time;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Return;

// Time delta representing midnight 00:00.
constexpr base::TimeDelta kMidnight;

// Time delta representing 06:00AM.
constexpr base::TimeDelta kSixAm = base::Hours(6);

// Time delta representing 1 hour time interval.
constexpr base::TimeDelta kHour = base::Hours(1);

constexpr int kIdlePollIntervalSeconds = 30;

constexpr char kStartTime[] = "1 Jan 2020 21:15";

const char kArcStatus[] = R"(
{
   "applications":[
      {
         "packageName":"com.android.providers.telephony",
         "versionName":"6.0.1",
         "permissions":[ "android.permission.INTERNET" ]
      }
   ],
   "userEmail":"[email protected]"
})";
const char kDroidGuardInfo[] = "{\"droid_guard_info\":42}";

const char kFakeDmToken[] = "kFakeDmToken";

class TestingChildStatusCollector : public ChildStatusCollector {
 public:
  TestingChildStatusCollector(
      PrefService* pref_service,
      Profile* profile,
      ash::system::StatisticsProvider* provider,
      const StatusCollector::AndroidStatusFetcher& android_status_fetcher,
      base::TimeDelta activity_day_start)
      : ChildStatusCollector(pref_service,
                             profile,
                             provider,
                             android_status_fetcher,
                             activity_day_start) {}

  // Each time this is called, returns a time that is a fixed increment
  // later than the previous time.
  void UpdateUsageTime() { UpdateChildUsageTime(); }

  std::string GetDMTokenForProfile(Profile* profile) const override {
    return kFakeDmToken;
  }
};

// Overloads |GetActiveMilliseconds| for child status report.
int64_t GetActiveMilliseconds(const em::ChildStatusReportRequest& status) {
  int64_t active_milliseconds = 0;
  for (int i = 0; i < status.screen_time_span_size(); i++) {
    active_milliseconds += status.screen_time_span(i).active_duration_ms();
  }
  return active_milliseconds;
}

void CallAndroidStatusReceiver(
    ChildStatusCollector::AndroidStatusReceiver receiver,
    const std::string& status,
    const std::string& droid_guard_info) {
  std::move(receiver).Run(status, droid_guard_info);
}

bool GetEmptyAndroidStatus(StatusCollector::AndroidStatusReceiver receiver) {
  // Post it to the thread because this call is expected to be asynchronous.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&CallAndroidStatusReceiver, std::move(receiver), "", ""));
  return true;
}

bool GetFakeAndroidStatus(const std::string& status,
                          const std::string& droid_guard_info,
                          StatusCollector::AndroidStatusReceiver receiver) {
  // Post it to the thread because this call is expected to be asynchronous.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&CallAndroidStatusReceiver, std::move(receiver),
                                status, droid_guard_info));
  return true;
}

}  // namespace

// Though it is a unit test, this test is linked with browser_tests so that it
// runs in a separate process. The intention is to avoid overriding the timezone
// environment variable for other tests.
class ChildStatusCollectorTest : public testing::Test {
 public:
  ChildStatusCollectorTest()
      : user_manager_enabler_(std::make_unique<ash::FakeChromeUserManager>()),
        user_data_dir_override_(chrome::DIR_USER_DATA) {
    scoped_stub_install_attributes_.Get()->SetCloudManaged("managed.com",
                                                           "device_id");

    // Ensure mojo is started, otherwise browser context keyed services that
    // rely on mojo will explode.
    mojo::core::Init();

    // Although this is really a unit test which runs in the browser_tests
    // binary, it doesn't get the unit setup which normally happens in the unit
    // test binary.
    ChromeUnitTestSuite::InitializeProviders();
    ChromeUnitTestSuite::InitializeResourceBundle();

    content::SetContentClient(&content_client_);
    content::SetBrowserClientForTesting(&browser_content_client_);

    // Run this test with a well-known timezone so that Time::LocalMidnight()
    // returns the same values on all machines.
    std::unique_ptr<base::Environment> env(base::Environment::Create());
    env->SetVar("TZ", "UTC");

    TestingBrowserProcess::GetGlobal()->SetLocalState(&local_state_);

    // Use FakeUpdateEngineClient.
    ash::UpdateEngineClient::InitializeFakeForTest();
    ash::CiceroneClient::InitializeFake();
    ash::ConciergeClient::InitializeFake();
    ash::SeneschalClient::InitializeFake();
    chromeos::PowerManagerClient::InitializeFake();
    ash::LoginState::Initialize();

    AddChildUser(AccountId::FromUserEmail("[email protected]"));
  }

  ~ChildStatusCollectorTest() override {
    ash::LoginState::Shutdown();
    chromeos::PowerManagerClient::Shutdown();
    ash::SeneschalClient::Shutdown();
    // |testing_profile_| must be destructed while ConciergeClient is alive.
    testing_profile_.reset();
    ash::ConciergeClient::Shutdown();
    ash::CiceroneClient::Shutdown();
    ash::UpdateEngineClient::Shutdown();
    TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);

    // Finish pending tasks.
    content::RunAllTasksUntilIdle();
  }

  ChildStatusCollectorTest(const ChildStatusCollectorTest&) = delete;
  ChildStatusCollectorTest& operator=(const ChildStatusCollectorTest&) = delete;

  void SetUp() override {
    RestartStatusCollector(base::BindRepeating(&GetEmptyAndroidStatus));

    // Disable network reporting since it requires additional setup.
    scoped_testing_cros_settings_.device_settings()->SetBoolean(
        ash::kReportDeviceNetworkConfiguration, false);
    scoped_testing_cros_settings_.device_settings()->SetBoolean(
        ash::kReportDeviceNetworkStatus, false);

    // Mock clock in task environment is set to Unix Epoch, advance it to avoid
    // using times from before Unix Epoch in some tests.
    Time start_time;
    EXPECT_TRUE(Time::FromString(kStartTime, &start_time));
    FastForwardTo(start_time);
  }

  void TearDown() override { status_collector_.reset(); }

 protected:
  // States tracked to calculate a child's active time.
  enum class DeviceStateTransitions {
    kEnterIdleState,
    kLeaveIdleState,
    kEnterSleep,
    kLeaveSleep,
    kEnterSessionActive,
    kLeaveSessionActive,
    kPeriodicCheckTriggered
  };

  ash::FakeChromeUserManager* GetFakeUserManager() {
    return static_cast<ash::FakeChromeUserManager*>(
        user_manager::UserManager::Get());
  }

  void SimulateStateChanges(DeviceStateTransitions* states, int len) {
    for (int i = 0; i < len; i++) {
      switch (states[i]) {
        case DeviceStateTransitions::kEnterIdleState: {
          power_manager::ScreenIdleState state;
          state.set_off(true);
          chromeos::FakePowerManagerClient::Get()->SendScreenIdleStateChanged(
              state);
        } break;
        case DeviceStateTransitions::kLeaveIdleState: {
          power_manager::ScreenIdleState state;
          state.set_off(false);
          chromeos::FakePowerManagerClient::Get()->SendScreenIdleStateChanged(
              state);
        } break;
        case DeviceStateTransitions::kEnterSleep:
          chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
              power_manager::SuspendImminent_Reason_LID_CLOSED);
          break;
        case DeviceStateTransitions::kLeaveSleep:
          chromeos::FakePowerManagerClient::Get()->SendSuspendDone(
              base::Seconds(kIdlePollIntervalSeconds));
          break;
        case DeviceStateTransitions::kEnterSessionActive:
          session_manager::SessionManager::Get()->SetSessionState(
              session_manager::SessionState::ACTIVE);
          break;
        case DeviceStateTransitions::kLeaveSessionActive:
          session_manager::SessionManager::Get()->SetSessionState(
              session_manager::SessionState::LOCKED);
          break;
        case DeviceStateTransitions::kPeriodicCheckTriggered:
          break;
      }
      task_environment_.AdvanceClock(base::Seconds(kIdlePollIntervalSeconds));
      status_collector_->UpdateUsageTime();
    }
  }

  // If `should_run_tasks` is true, then use FastForwardBy() to run tasks.
  // Otherwise use AdvanceClock() to skip running tasks.
  void SimulateAppActivity(const ash::app_time::AppId& app_id,
                           base::TimeDelta duration,
                           bool should_run_tasks = true) {
    ash::ChildUserService::TestApi child_user_service =
        ash::ChildUserService::TestApi(
            ash::ChildUserServiceFactory::GetForBrowserContext(
                testing_profile_.get()));
    EXPECT_TRUE(child_user_service.app_time_controller());

    ash::app_time::AppActivityRegistry* app_registry =
        ash::app_time::AppTimeController::TestApi(
            child_user_service.app_time_controller())
            .app_registry();
    app_registry->OnAppInstalled(app_id);

    // Window instance is irrelevant for tests here.
    auto instance_id = base::UnguessableToken::Create();
    app_registry->OnAppActive(app_id, instance_id, Time::Now());
    if (should_run_tasks) {
      task_environment_.FastForwardBy(duration);
    } else {
      task_environment_.AdvanceClock(duration);
    }
    app_registry->OnAppInactive(app_id, instance_id, Time::Now());
  }

  virtual void RestartStatusCollector(
      const StatusCollector::AndroidStatusFetcher& android_status_fetcher,
      const base::TimeDelta activity_day_start = kMidnight) {
    status_collector_ = std::make_unique<TestingChildStatusCollector>(
        pref_service(), testing_profile(), &fake_statistics_provider_,
        android_status_fetcher, activity_day_start);
  }

  void GetStatus() {
    run_loop_ = std::make_unique<base::RunLoop>();
    status_collector_->GetStatusAsync(base::BindRepeating(
        &ChildStatusCollectorTest::OnStatusReceived, base::Unretained(this)));
    run_loop_->Run();
    run_loop_.reset();
  }

  void OnStatusReceived(StatusCollectorParams callback_params) {
    if (callback_params.child_status)
      child_status_ = *callback_params.child_status;
    EXPECT_TRUE(run_loop_);
    run_loop_->Quit();
  }

  void AddUserWithTypeAndAffiliation(const AccountId& account_id,
                                     user_manager::UserType user_type,
                                     bool is_affiliated) {
    // Build a profile with profile name=account e-mail because our testing
    // version of GetDMTokenForProfile returns the profile name.
    TestingProfile::Builder profile_builder;
    profile_builder.SetProfileName(account_id.GetUserEmail());
    testing_profile_ = profile_builder.Build();

    auto* user_manager = GetFakeUserManager();
    auto* user = user_manager->AddUserWithAffiliationAndTypeAndProfile(
        account_id, is_affiliated, user_type, testing_profile_.get());
    user_manager->UserLoggedIn(user->GetAccountId(), user->username_hash(),
                               /*browser_restart=*/false, /*is_child=*/false);
  }

  void AddChildUser(const AccountId& account_id) {
    AddUserWithTypeAndAffiliation(account_id, user_manager::UserType::kChild,
                                  false);
    GetFakeUserManager()->set_current_user_child(true);
  }

  // Convenience method.
  static int64_t ActivePeriodMilliseconds() {
    return kIdlePollIntervalSeconds * base::Time::kMillisecondsPerSecond;
  }

  void ExpectChildScreenTimeMilliseconds(int64_t duration) {
    pref_service()->CommitPendingWrite(
        base::OnceClosure(),
        base::BindOnce(
            [](int64_t duration, PrefService* profile_pref_service_) {
              EXPECT_EQ(duration, profile_pref_service_->GetInteger(
                                      prefs::kChildScreenTimeMilliseconds));
            },
            duration, pref_service()));
  }

  void ExpectLastChildScreenTimeReset(Time time) {
    pref_service()->CommitPendingWrite(
        base::OnceClosure(),
        base::BindOnce(
            [](Time time, PrefService* profile_pref_service_) {
              EXPECT_EQ(time, profile_pref_service_->GetTime(
                                  prefs::kLastChildScreenTimeReset));
            },
            time, pref_service()));
  }

  void FastForwardTo(Time time) {
    base::TimeDelta forward_by = time - Time::Now();
    EXPECT_LT(base::TimeDelta(), forward_by);
    task_environment_.AdvanceClock(forward_by);
  }

  Profile* testing_profile() { return testing_profile_.get(); }
  PrefService* pref_service() { return testing_profile_->GetPrefs(); }

  // Since this is a unit test running in browser_tests we must do additional
  // unit test setup and make a TestingBrowserProcess. Must be first member.
  TestingBrowserProcessInitializer initializer_;

  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  ChromeContentClient content_client_;
  ChromeContentBrowserClient browser_content_client_;
  ash::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
  ash::ScopedStubInstallAttributes scoped_stub_install_attributes_;
  ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
  ash::FakeOwnerSettingsService owner_settings_service_{
      scoped_testing_cros_settings_.device_settings(), nullptr};
  // local_state_ should be destructed after TestingProfile.
  TestingPrefServiceSimple local_state_;
  std::unique_ptr<TestingProfile> testing_profile_;
  user_manager::ScopedUserManager user_manager_enabler_;
  em::ChildStatusReportRequest child_status_;
  std::unique_ptr<TestingChildStatusCollector> status_collector_;
  base::ScopedPathOverride user_data_dir_override_;
  std::unique_ptr<base::RunLoop> run_loop_;

  // This property is required to instantiate the session manager, a singleton
  // which is used by the device status collector.
  session_manager::SessionManager session_manager_;
};

TEST_F(ChildStatusCollectorTest, ReportingBootMode) {
  fake_statistics_provider_.SetMachineStatistic(
      ash::system::kDevSwitchBootKey, ash::system::kDevSwitchBootValueVerified);

  GetStatus();

  EXPECT_TRUE(child_status_.has_boot_mode());
  EXPECT_EQ("Verified", child_status_.boot_mode());
}

TEST_F(ChildStatusCollectorTest, ReportingArcStatus) {
  RestartStatusCollector(
      base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo));
  testing_profile_->GetPrefs()->SetBoolean(prefs::kReportArcStatusEnabled,
                                           true);

  GetStatus();

  EXPECT_EQ(kArcStatus, child_status_.android_status().status_payload());
  EXPECT_EQ(kDroidGuardInfo, child_status_.android_status().droid_guard_info());
  EXPECT_EQ(kFakeDmToken, child_status_.user_dm_token());
}

TEST_F(ChildStatusCollectorTest, ReportingPartialVersionInfo) {
  GetStatus();

  EXPECT_TRUE(child_status_.has_os_version());
}

TEST_F(ChildStatusCollectorTest, TimeZoneReporting) {
  const std::string timezone = base::UTF16ToUTF8(
      ash::system::TimezoneSettings::GetInstance()->GetCurrentTimezoneID());

  GetStatus();

  EXPECT_TRUE(child_status_.has_time_zone());
  EXPECT_EQ(timezone, child_status_.time_zone());
}

TEST_F(ChildStatusCollectorTest, ReportingActivityTimesSessionTransistions) {
  DeviceStateTransitions test_states[] = {
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,  // Check while inactive
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive};
  SimulateStateChanges(test_states,
                       sizeof(test_states) / sizeof(DeviceStateTransitions));

  GetStatus();

  ASSERT_EQ(1, child_status_.screen_time_span_size());
  EXPECT_EQ(5 * ActivePeriodMilliseconds(),
            GetActiveMilliseconds(child_status_));
  ExpectChildScreenTimeMilliseconds(5 * ActivePeriodMilliseconds());
}

TEST_F(ChildStatusCollectorTest, ReportingActivityTimesSleepTransitions) {
  DeviceStateTransitions test_states[] = {
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kEnterSleep,
      DeviceStateTransitions::kPeriodicCheckTriggered,  // Check while inactive
      DeviceStateTransitions::kLeaveSleep,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive,
      DeviceStateTransitions::kEnterSleep,
      DeviceStateTransitions::kLeaveSleep,
      DeviceStateTransitions::kPeriodicCheckTriggered};
  SimulateStateChanges(test_states,
                       sizeof(test_states) / sizeof(DeviceStateTransitions));

  GetStatus();

  ASSERT_EQ(1, child_status_.screen_time_span_size());
  EXPECT_EQ(4 * ActivePeriodMilliseconds(),
            GetActiveMilliseconds(child_status_));
  ExpectChildScreenTimeMilliseconds(4 * ActivePeriodMilliseconds());
}

TEST_F(ChildStatusCollectorTest, ReportingActivityTimesIdleTransitions) {
  DeviceStateTransitions test_states[] = {
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kEnterIdleState,
      DeviceStateTransitions::kPeriodicCheckTriggered,  // Check while inactive
      DeviceStateTransitions::kPeriodicCheckTriggered,  // Check while inactive
      DeviceStateTransitions::kLeaveIdleState,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive};
  SimulateStateChanges(test_states,
                       sizeof(test_states) / sizeof(DeviceStateTransitions));

  GetStatus();

  ASSERT_EQ(1, child_status_.screen_time_span_size());
  EXPECT_EQ(5 * ActivePeriodMilliseconds(),
            GetActiveMilliseconds(child_status_));
  ExpectChildScreenTimeMilliseconds(5 * ActivePeriodMilliseconds());
}

TEST_F(ChildStatusCollectorTest, ActivityKeptInPref) {
  EXPECT_THAT(pref_service()->GetDict(prefs::kUserActivityTimes), IsEmpty());
  task_environment_.AdvanceClock(kHour);

  DeviceStateTransitions test_states[] = {
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive,
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,  // Check while inactive
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kLeaveSessionActive};
  SimulateStateChanges(test_states,
                       sizeof(test_states) / sizeof(DeviceStateTransitions));
  EXPECT_THAT(pref_service()->GetDict(prefs::kUserActivityTimes),
              Not(IsEmpty()));

  // Process the list a second time after restarting the collector. It should be
  // able to count the active periods found by the original collector, because
  // the results are stored in a pref.
  RestartStatusCollector(base::BindRepeating(&GetEmptyAndroidStatus));
  // Avoid resetting to test accumulating screen time.
  pref_service()->SetTime(prefs::kLastChildScreenTimeReset, Time::Now());
  SimulateStateChanges(test_states,
                       sizeof(test_states) / sizeof(DeviceStateTransitions));

  GetStatus();

  ExpectChildScreenTimeMilliseconds(12 * ActivePeriodMilliseconds());
  EXPECT_EQ(12 * ActivePeriodMilliseconds(),
            GetActiveMilliseconds(child_status_));
}

TEST_F(ChildStatusCollectorTest, ActivityNotWrittenToLocalState) {
  DeviceStateTransitions test_states[] = {
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,  // Check while inactive
      DeviceStateTransitions::kPeriodicCheckTriggered,  // Check while inactive
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kLeaveSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,  // Check while inactive
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive};
  SimulateStateChanges(test_states,
                       sizeof(test_states) / sizeof(DeviceStateTransitions));
  GetStatus();

  EXPECT_EQ(1, child_status_.screen_time_span_size());
  EXPECT_EQ(5 * ActivePeriodMilliseconds(),
            GetActiveMilliseconds(child_status_));
  ExpectChildScreenTimeMilliseconds(5 * ActivePeriodMilliseconds());
  // Nothing should be written to local state, because it is only used for
  // enterprise reporting.
}

TEST_F(ChildStatusCollectorTest, BeforeDayStart) {
  RestartStatusCollector(base::BindRepeating(&GetEmptyAndroidStatus), kSixAm);
  // TaskEnvironment can't go backwards in time, so fast forward to 04:00 AM on
  // the next day.
  Time initial_time =
      Time::Now().LocalMidnight() + base::Days(1) + base::Hours(4);
  FastForwardTo(initial_time);
  EXPECT_THAT(pref_service()->GetDict(prefs::kUserActivityTimes), IsEmpty());

  DeviceStateTransitions test_states[] = {
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kLeaveSessionActive,
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kPeriodicCheckTriggered,
      DeviceStateTransitions::kLeaveSessionActive};
  SimulateStateChanges(test_states,
                       sizeof(test_states) / sizeof(DeviceStateTransitions));
  GetStatus();
  // 4 is the number of states yielding an active period with duration of
  // ActivePeriodMilliseconds
  EXPECT_EQ(4 * ActivePeriodMilliseconds(),
            GetActiveMilliseconds(child_status_));
  ExpectChildScreenTimeMilliseconds(4 * ActivePeriodMilliseconds());
  ExpectLastChildScreenTimeReset(initial_time);
}

TEST_F(ChildStatusCollectorTest, ActivityCrossingMidnight) {
  DeviceStateTransitions test_states[] = {
      DeviceStateTransitions::kEnterSessionActive,
      DeviceStateTransitions::kLeaveSessionActive};

  // Set the baseline time to 15 seconds before midnight, so the activity is
  // split between two days.
  // TaskEnvironment can't go backwards in time, so fast forward to 11:45:45 PM
  // on the next day.
  Time start_time =
      Time::Now().LocalMidnight() + base::Days(1) - base::Seconds(15);
  FastForwardTo(start_time);
  SimulateStateChanges(test_states,
                       sizeof(test_states) / sizeof(DeviceStateTransitions));
  GetStatus();

  ASSERT_EQ(2, child_status_.screen_time_span_size());

  em::ScreenTimeSpan timespan0 = child_status_.screen_time_span(0);
  em::ScreenTimeSpan timespan1 = child_status_.screen_time_span(1);
  EXPECT_EQ(ActivePeriodMilliseconds() - 15000, timespan0.active_duration_ms());
  EXPECT_EQ(15000, timespan1.active_duration_ms());

  em::TimePeriod timespan0period = timespan0.time_period();
  em::TimePeriod timespan1period = timespan1.time_period();

  EXPECT_EQ(timespan0period.end_timestamp(), timespan1period.start_timestamp());

  // Ensure that the start and end times for the period are a day apart.
  EXPECT_EQ(timespan0period.end_timestamp() - timespan0period.start_timestamp(),
            base::Time::kMillisecondsPerDay);
  EXPECT_EQ(timespan1period.end_timestamp() - timespan1period.start_timestamp(),
            base::Time::kMillisecondsPerDay);
  ExpectChildScreenTimeMilliseconds(0.5 * ActivePeriodMilliseconds());
}

TEST_F(ChildStatusCollectorTest, ClockChanged) {
  DeviceStateTransitions test_states[1] = {
      DeviceStateTransitions::kEnterSessionActive};
  Time initial_time;
  // Test daylight savings time (spring forward).
  ASSERT_TRUE(Time::FromString("30 Mar 2020 1:00AM PST", &initial_time));
  FastForwardTo(initial_time);
  SimulateStateChanges(test_states, 1);

  // Simulate a real DST clock change.
  task_environment_.AdvanceClock(kHour);
  test_states[0] = DeviceStateTransitions::kLeaveSessionActive;
  SimulateStateChanges(test_states, 1);

  GetStatus();

  ASSERT_EQ(1, child_status_.screen_time_span_size());
  ExpectChildScreenTimeMilliseconds(2 * ActivePeriodMilliseconds());
}

TEST_F(ChildStatusCollectorTest, ReportingAppActivity) {
  // Nothing reported yet.
  GetStatus();
  EXPECT_EQ(0, child_status_.app_activity_size());
  status_collector_->OnSubmittedSuccessfully();

  // Report activity for two different apps.
  const ash::app_time::AppId app1(apps::AppType::kWeb, "app1");
  const ash::app_time::AppId app2(apps::AppType::kChromeApp, "app2");
  const Time start_time = Time::Now();
  const base::TimeDelta app1_interval = base::Minutes(1);
  const base::TimeDelta app2_interval = base::Minutes(2);
  SimulateAppActivity(app1, app1_interval);
  SimulateAppActivity(app2, app2_interval);
  SimulateAppActivity(app1, app1_interval);
  SimulateAppActivity(app2, app2_interval);
  SimulateAppActivity(app1, app1_interval);

  GetStatus();
  EXPECT_EQ(2, child_status_.app_activity_size());

  for (const auto& app_activity : child_status_.app_activity()) {
    if (app_activity.app_info().app_id() == app1.app_id()) {
      EXPECT_EQ(em::App::WEB, app_activity.app_info().app_type());
      EXPECT_EQ(0, app_activity.app_info().additional_app_id_size());
      EXPECT_EQ(em::AppActivity::DEFAULT, app_activity.app_state());
      EXPECT_EQ(3, app_activity.active_time_periods_size());
      Time start = start_time;
      for (const auto& active_period : app_activity.active_time_periods()) {
        EXPECT_EQ(start.InMillisecondsSinceUnixEpoch(),
                  active_period.start_timestamp());
        const Time end = start + app1_interval;
        EXPECT_EQ(end.InMillisecondsSinceUnixEpoch(),
                  active_period.end_timestamp());
        start = end + app2_interval;
      }
      continue;
    }
    if (app_activity.app_info().app_id() == app2.app_id()) {
      EXPECT_EQ(em::App::EXTENSION, app_activity.app_info().app_type());
      EXPECT_EQ(0, app_activity.app_info().additional_app_id_size());
      EXPECT_EQ(em::AppActivity::DEFAULT, app_activity.app_state());
      EXPECT_EQ(2, app_activity.active_time_periods_size());
      Time start = start_time + app1_interval;
      for (const auto& active_period : app_activity.active_time_periods()) {
        EXPECT_EQ(start.InMillisecondsSinceUnixEpoch(),
                  active_period.start_timestamp());
        const Time end = start + app2_interval;
        EXPECT_EQ(end.InMillisecondsSinceUnixEpoch(),
                  active_period.end_timestamp());
        start = end + app1_interval;
      }
      continue;
    }
  }

  // After successful report submission 'old' data should be cleared.
  status_collector_->OnSubmittedSuccessfully();
  GetStatus();
  EXPECT_EQ(0, child_status_.app_activity_size());
}

TEST_F(ChildStatusCollectorTest, ReportingAppActivityNoReport) {
  // Nothing reported yet.
  GetStatus();
  EXPECT_EQ(0, child_status_.app_activity_size());
  status_collector_->OnSubmittedSuccessfully();

  const ash::app_time::AppId app1(apps::AppType::kWeb, "app1");
  const ash::app_time::AppId app2(apps::AppType::kChromeApp, "app2");
  const base::TimeDelta app1_interval = base::Minutes(1);
  const base::TimeDelta app2_interval = base::Minutes(2);

  SimulateAppActivity(app1, app1_interval);
  SimulateAppActivity(app2, app2_interval);
  SimulateAppActivity(app1, app1_interval);
  SimulateAppActivity(app2, app2_interval);
  SimulateAppActivity(app1, app1_interval);

  {
    ash::app_time::AppTimeLimitsPolicyBuilder builder;
    builder.SetAppActivityReportingEnabled(/* enabled */ false);
    testing_profile()->GetPrefs()->SetDict(prefs::kPerAppTimeLimitsPolicy,
                                           builder.value().Clone());
  }

  SimulateAppActivity(app1, app1_interval);
  SimulateAppActivity(app2, app2_interval);
  SimulateAppActivity(app1, app1_interval);
  SimulateAppActivity(app2, app2_interval);
  SimulateAppActivity(app1, app1_interval);

  GetStatus();
  EXPECT_EQ(0, child_status_.app_activity_size());
}

TEST_F(ChildStatusCollectorTest, ReportingAppActivityMetrics) {
  base::HistogramTester histogram_tester;

  // Nothing reported yet.
  GetStatus();
  EXPECT_EQ(0, child_status_.app_activity_size());
  status_collector_->OnSubmittedSuccessfully();

  histogram_tester.ExpectTotalCount(
      ChildStatusCollector::GetReportSizeHistogramNameForTest(),
      /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      ChildStatusCollector::GetTimeSinceLastReportHistogramNameForTest(),
      /*expected_count=*/0);

  // Report activity for two different apps.
  const ash::app_time::AppId app1(apps::AppType::kWeb, "app1");
  const ash::app_time::AppId app2(apps::AppType::kChromeApp, "app2");
  const base::TimeDelta app1_interval = base::Seconds(1);
  const base::TimeDelta app2_interval = base::Seconds(2);
  SimulateAppActivity(app1, app1_interval);
  SimulateAppActivity(app2, app2_interval);
  SimulateAppActivity(app1, app1_interval);
  SimulateAppActivity(app2, app2_interval);
  SimulateAppActivity(app1, app1_interval);

  GetStatus();
  status_collector_->OnSubmittedSuccessfully();
  EXPECT_EQ(2, child_status_.app_activity_size());

  // The amount of data is less than one KB, so that rounds down to zero.
  histogram_tester.ExpectUniqueSample(
      ChildStatusCollector::GetReportSizeHistogramNameForTest(), /*sample=*/0,
      /*expected_count=*/1);
  // There was no previous report, so the time elapsed since the last report is
  // not applicable.
  histogram_tester.ExpectTotalCount(
      ChildStatusCollector::GetTimeSinceLastReportHistogramNameForTest(),
      /*expected_count=*/0);

  // Generate a much larger report. The report size needs to exceed 1000KB to
  // reach the next bucket size in the histogram. Also fast forwards by 2000
  // minutes.
  for (int i = 0; i < 40000; i++) {
    SimulateAppActivity(app1, app1_interval, /*should_run_tasks=*/false);
    SimulateAppActivity(app2, app2_interval, /*should_run_tasks=*/false);
  }

  GetStatus();
  status_collector_->OnSubmittedSuccessfully();
  EXPECT_EQ(2, child_status_.app_activity_size());

  histogram_tester.ExpectBucketCount(
      ChildStatusCollector::GetReportSizeHistogramNameForTest(),
      /*sample=*/1250 /*KB*/,
      /*expected_count=*/1);
  histogram_tester.ExpectTotalCount(
      ChildStatusCollector::GetReportSizeHistogramNameForTest(),
      /*expected_count=*/2);
  // 2000 minutes (33 hours) have elapsed since the last report.
  histogram_tester.ExpectUniqueSample(
      ChildStatusCollector::GetTimeSinceLastReportHistogramNameForTest(),
      /*sample=*/2000, /*expected_count=*/1);
}

}  // namespace policy