chromium/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_theme_provider_impl.cc

// Copyright 2022 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/system_web_apps/apps/personalization_app/personalization_app_theme_provider_impl.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/geolocation_access_level.h"
#include "ash/public/cpp/schedule_enums.h"
#include "ash/shell.h"
#include "ash/style/color_palette_controller.h"
#include "ash/style/color_util.h"
#include "ash/style/mojom/color_scheme.mojom-shared.h"
#include "ash/system/privacy_hub/privacy_hub_controller.h"
#include "ash/system/scheduled_feature/scheduled_feature.h"
#include "base/i18n/time_formatting.h"
#include "chrome/browser/ash/privacy_hub/privacy_hub_util.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_metrics.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/geolocation/simple_geolocation_provider.h"
#include "chromeos/ash/components/settings/timezone_settings.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/prefs/pref_service.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"

namespace ash::personalization_app {

// This array represents the order, number, and types of color schemes
// represented by the color scheme buttons in the app.
const std::array<ash::style::mojom::ColorScheme, 4> kColorSchemeButtons{
    ash::style::mojom::ColorScheme::kTonalSpot,
    ash::style::mojom::ColorScheme::kNeutral,
    ash::style::mojom::ColorScheme::kVibrant,
    ash::style::mojom::ColorScheme::kExpressive,
};

PersonalizationAppThemeProviderImpl::PersonalizationAppThemeProviderImpl(
    content::WebUI* web_ui)
    : profile_(Profile::FromWebUI(web_ui)) {
  pref_change_registrar_.Init(profile_->GetPrefs());
  color_palette_controller_ = Shell::Get()->color_palette_controller();
}

PersonalizationAppThemeProviderImpl::~PersonalizationAppThemeProviderImpl() =
    default;

void PersonalizationAppThemeProviderImpl::BindInterface(
    mojo::PendingReceiver<ash::personalization_app::mojom::ThemeProvider>
        receiver) {
  theme_receiver_.reset();
  theme_receiver_.Bind(std::move(receiver));
}

void PersonalizationAppThemeProviderImpl::SetThemeObserver(
    mojo::PendingRemote<ash::personalization_app::mojom::ThemeObserver>
        observer) {
  // May already be bound if user refreshes page.
  theme_observer_remote_.reset();
  theme_observer_remote_.Bind(std::move(observer));
  if (!color_mode_observer_.IsObserving()) {
    color_mode_observer_.Observe(ash::DarkLightModeControllerImpl::Get());
  }
  // Call it once to get the current color mode.
  OnColorModeChanged(
      ash::DarkLightModeControllerImpl::Get()->IsDarkModeEnabled());

  // Listen to |ash::prefs::kDarkModeScheduleType| changes.
  if (!pref_change_registrar_.IsObserved(ash::prefs::kDarkModeScheduleType)) {
    pref_change_registrar_.Add(
        ash::prefs::kDarkModeScheduleType,
        base::BindRepeating(&PersonalizationAppThemeProviderImpl::
                                NotifyColorModeAutoScheduleChanged,
                            base::Unretained(this)));
  }
  // Call once to get the initial status.
  NotifyColorModeAutoScheduleChanged();

  // Listen to `ash::prefs::kUserGeolocationAccesslevel` changes.
  if (!pref_change_registrar_.IsObserved(
          ash::prefs::kUserGeolocationAccessLevel)) {
    pref_change_registrar_.Add(
        ash::prefs::kUserGeolocationAccessLevel,
        base::BindRepeating(&PersonalizationAppThemeProviderImpl::
                                NotifyGeolocationPermissionChanged,
                            base::Unretained(this)));
  }
  // Call once to get the initial status.
  NotifyGeolocationPermissionChanged();

  system::TimezoneSettings* tz_settings =
      ash::system::TimezoneSettings::GetInstance();
  CHECK(tz_settings);
  // This provides the initial timezone.
  TimezoneChanged(tz_settings->GetTimezone());
  if (!timezone_settings_observer_.IsObserving()) {
    // Listen to timezone changes to update the daylight time.
    timezone_settings_observer_.Observe(tz_settings);
  }

  OnStaticColorChanged();
  OnColorSchemeChanged();
  if (!pref_change_registrar_.IsObserved(
          ash::prefs::kDynamicColorColorScheme)) {
    pref_change_registrar_.Add(
        ash::prefs::kDynamicColorColorScheme,
        base::BindRepeating(
            &PersonalizationAppThemeProviderImpl::OnColorSchemeChanged,
            base::Unretained(this)));
  }
  if (!pref_change_registrar_.IsObserved(ash::prefs::kDynamicColorSeedColor)) {
    pref_change_registrar_.Add(
        ash::prefs::kDynamicColorSeedColor,
        base::BindRepeating(
            &PersonalizationAppThemeProviderImpl::OnStaticColorChanged,
            base::Unretained(this)));
  }
  ui::ColorProviderSourceObserver::Observe(
      ash::ColorUtil::GetColorProviderSourceForWindow(
          ash::Shell::GetPrimaryRootWindow()));
}

void PersonalizationAppThemeProviderImpl::SetColorModePref(
    bool dark_mode_enabled) {
  auto* dark_light_mode_controller = ash::DarkLightModeControllerImpl::Get();
  if (dark_light_mode_controller->IsDarkModeEnabled() != dark_mode_enabled) {
    LogPersonalizationTheme(dark_mode_enabled ? ColorMode::kDark
                                              : ColorMode::kLight);
    dark_light_mode_controller->ToggleColorMode();
  }
}

void PersonalizationAppThemeProviderImpl::SetColorModeAutoScheduleEnabled(
    bool enabled) {
  PrefService* pref_service = profile_->GetPrefs();
  DCHECK(pref_service);
  if (enabled) {
    LogPersonalizationTheme(ColorMode::kAuto);
  }
  const ScheduleType schedule_type =
      enabled ? ScheduleType::kSunsetToSunrise : ScheduleType::kNone;
  pref_service->SetInteger(ash::prefs::kDarkModeScheduleType,
                           static_cast<int>(schedule_type));
}

void PersonalizationAppThemeProviderImpl::IsDarkModeEnabled(
    IsDarkModeEnabledCallback callback) {
  auto* dark_light_mode_controller = ash::DarkLightModeControllerImpl::Get();
  std::move(callback).Run(dark_light_mode_controller->IsDarkModeEnabled());
}

void PersonalizationAppThemeProviderImpl::IsColorModeAutoScheduleEnabled(
    IsColorModeAutoScheduleEnabledCallback callback) {
  std::move(callback).Run(IsColorModeAutoScheduleEnabled());
}

void PersonalizationAppThemeProviderImpl::IsGeolocationEnabledForSystemServices(
    IsGeolocationEnabledForSystemServicesCallback callback) {
  std::move(callback).Run(IsGeolocationEnabledForSystemServices());
}

void PersonalizationAppThemeProviderImpl::EnableGeolocationForSystemServices() {
  PrefService* pref_service = profile_->GetPrefs();
  pref_service->SetInteger(
      prefs::kUserGeolocationAccessLevel,
      static_cast<int>(GeolocationAccessLevel::kOnlyAllowedForSystem));
}

void PersonalizationAppThemeProviderImpl::OnColorModeChanged(
    bool dark_mode_enabled) {
  DCHECK(theme_observer_remote_.is_bound());
  theme_observer_remote_->OnColorModeChanged(dark_mode_enabled);
}

void PersonalizationAppThemeProviderImpl::OnColorSchemeChanged() {
  DCHECK(theme_observer_remote_.is_bound());
  theme_observer_remote_->OnColorSchemeChanged(
      color_palette_controller_->GetColorScheme(GetAccountId(profile_)));
}

void PersonalizationAppThemeProviderImpl::OnStaticColorChanged() {
  DCHECK(theme_observer_remote_.is_bound());
  theme_observer_remote_->OnStaticColorChanged(
      color_palette_controller_->GetStaticColor(GetAccountId(profile_)));
}

void PersonalizationAppThemeProviderImpl::OnSampleColorSchemesChanged(
    const std::vector<ash::SampleColorScheme>& sampleColorSchemes) {
  DCHECK(theme_observer_remote_.is_bound());
  theme_observer_remote_->OnSampleColorSchemesChanged(sampleColorSchemes);
}

bool PersonalizationAppThemeProviderImpl::IsColorModeAutoScheduleEnabled() {
  PrefService* pref_service = profile_->GetPrefs();
  DCHECK(pref_service);
  const auto schedule_type = static_cast<ScheduleType>(
      pref_service->GetInteger(ash::prefs::kDarkModeScheduleType));
  return schedule_type == ScheduleType::kSunsetToSunrise;
}

void PersonalizationAppThemeProviderImpl::NotifyColorModeAutoScheduleChanged() {
  DCHECK(theme_observer_remote_.is_bound());
  theme_observer_remote_->OnColorModeAutoScheduleChanged(
      IsColorModeAutoScheduleEnabled());
}

bool PersonalizationAppThemeProviderImpl::
    IsGeolocationEnabledForSystemServices() {
  PrefService* pref_service = profile_->GetPrefs();
  CHECK(pref_service);
  const auto access_level = static_cast<GeolocationAccessLevel>(
      pref_service->GetInteger(prefs::kUserGeolocationAccessLevel));

  switch (access_level) {
    case ash::GeolocationAccessLevel::kAllowed:
    case ash::GeolocationAccessLevel::kOnlyAllowedForSystem:
      return true;
    case ash::GeolocationAccessLevel::kDisallowed:
      return false;
  }
}

void PersonalizationAppThemeProviderImpl::NotifyGeolocationPermissionChanged() {
  CHECK(theme_observer_remote_.is_bound());
  theme_observer_remote_->OnGeolocationPermissionForSystemServicesChanged(
      IsGeolocationEnabledForSystemServices());
}

void PersonalizationAppThemeProviderImpl::GetColorScheme(
    GetColorSchemeCallback callback) {
  std::move(callback).Run(
      color_palette_controller_->GetColorScheme(GetAccountId(profile_)));
}

void PersonalizationAppThemeProviderImpl::SetColorScheme(
    ash::style::mojom::ColorScheme color_scheme) {
  color_palette_controller_->SetColorScheme(
      color_scheme, GetAccountId(profile_), base::DoNothing());
}

void PersonalizationAppThemeProviderImpl::GetStaticColor(
    GetStaticColorCallback callback) {
  std::move(callback).Run(
      color_palette_controller_->GetStaticColor(GetAccountId(profile_)));
}

void PersonalizationAppThemeProviderImpl::SetStaticColor(SkColor static_color) {
  AccountId account_id = GetAccountId(profile_);
  color_palette_controller_->SetStaticColor(static_color, account_id,
                                            base::DoNothing());
}

void PersonalizationAppThemeProviderImpl::GenerateSampleColorSchemes(
    GenerateSampleColorSchemesCallback callback) {
  color_palette_controller_->GenerateSampleColorSchemes(kColorSchemeButtons,
                                                        std::move(callback));
}

void PersonalizationAppThemeProviderImpl::OnColorProviderChanged() {
  GenerateSampleColorSchemes(base::BindOnce(
      &PersonalizationAppThemeProviderImpl::OnSampleColorSchemesChanged,
      weak_factory_.GetWeakPtr()));
}

void PersonalizationAppThemeProviderImpl::TimezoneChanged(
    const icu::TimeZone& timezone) {
  CHECK(theme_observer_remote_.is_bound());
  auto [sunrise_time, sunset_time] =
      ash::privacy_hub_util::SunriseSunsetSchedule();
  theme_observer_remote_->OnDaylightTimeChanged(
      base::TimeFormatTimeOfDay(sunrise_time),
      base::TimeFormatTimeOfDay(sunset_time));
}
}  // namespace ash::personalization_app