dark_light_mode_controller_impl.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/style/dark_light_mode_controller_impl.h"
  5. #include "ash/constants/ash_constants.h"
  6. #include "ash/constants/ash_features.h"
  7. #include "ash/constants/ash_pref_names.h"
  8. #include "ash/login/login_screen_controller.h"
  9. #include "ash/public/cpp/schedule_enums.h"
  10. #include "ash/public/cpp/style/color_mode_observer.h"
  11. #include "ash/shell.h"
  12. #include "ash/style/dark_light_mode_nudge_controller.h"
  13. #include "ash/wallpaper/wallpaper_controller_impl.h"
  14. #include "components/account_id/account_id.h"
  15. #include "components/prefs/pref_change_registrar.h"
  16. #include "components/prefs/pref_registry_simple.h"
  17. #include "components/prefs/pref_service.h"
  18. #include "components/user_manager/known_user.h"
  19. #include "ui/chromeos/styles/cros_styles.h"
  20. #include "ui/gfx/color_analysis.h"
  21. #include "ui/gfx/color_utils.h"
  22. namespace ash {
  23. namespace {
  24. DarkLightModeControllerImpl* g_instance = nullptr;
  25. // An array of OOBE screens which currently support dark theme.
  26. // In the future additional screens will be added. Eventually all screens
  27. // will support it and this array will not be needed anymore.
  28. constexpr OobeDialogState kStatesSupportingDarkTheme[] = {
  29. OobeDialogState::MARKETING_OPT_IN, OobeDialogState::THEME_SELECTION};
  30. // Gets the themed background color. The color will be set to `user_color` of
  31. // ColorProvider::Key and then can be assigned to Shield and Base layers for
  32. // dark/light mode feature. The algorithm to calculate the color and scope to
  33. // apply the color will change on Material Next project.
  34. SkColor GetThemedBackgroundColor(bool use_dark_color) {
  35. const SkColor default_color =
  36. use_dark_color ? gfx::kGoogleGrey900 : SK_ColorWHITE;
  37. if (!Shell::HasInstance())
  38. return default_color;
  39. WallpaperControllerImpl* wallpaper_controller =
  40. Shell::Get()->wallpaper_controller();
  41. if (!wallpaper_controller)
  42. return default_color;
  43. color_utils::LumaRange luma_range = use_dark_color
  44. ? color_utils::LumaRange::DARK
  45. : color_utils::LumaRange::LIGHT;
  46. SkColor muted_color =
  47. wallpaper_controller->GetProminentColor(color_utils::ColorProfile(
  48. luma_range, color_utils::SaturationRange::MUTED));
  49. if (muted_color == kInvalidWallpaperColor)
  50. return default_color;
  51. return color_utils::GetResultingPaintColor(
  52. SkColorSetA(use_dark_color ? SK_ColorBLACK : SK_ColorWHITE,
  53. SK_AlphaOPAQUE * 0.5f),
  54. muted_color);
  55. }
  56. // Refresh colors of the system on the current color mode. Not only the SysUI,
  57. // but also all the other components like WebUI. And since
  58. // DarkLightModeController is kind of NativeTheme of ChromeOS. This will trigger
  59. // View::OnThemeChanged to live update the colors. The colors live update can
  60. // happen when color mode changes or wallpaper changes. It is needed when
  61. // wallpaper changes as the background color is calculated from current
  62. // wallpaper.
  63. void RefreshColorsOnColorMode(bool is_dark_mode_enabled) {
  64. const SkColor themed_color = GetThemedBackgroundColor(is_dark_mode_enabled);
  65. auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
  66. native_theme->set_use_dark_colors(is_dark_mode_enabled);
  67. native_theme->set_user_color(themed_color);
  68. native_theme->NotifyOnNativeThemeUpdated();
  69. auto* native_theme_web = ui::NativeTheme::GetInstanceForWeb();
  70. native_theme_web->set_preferred_color_scheme(
  71. is_dark_mode_enabled ? ui::NativeTheme::PreferredColorScheme::kDark
  72. : ui::NativeTheme::PreferredColorScheme::kLight);
  73. native_theme_web->set_user_color(themed_color);
  74. native_theme_web->NotifyOnNativeThemeUpdated();
  75. }
  76. } // namespace
  77. DarkLightModeControllerImpl::DarkLightModeControllerImpl()
  78. : ScheduledFeature(prefs::kDarkModeEnabled,
  79. prefs::kDarkModeScheduleType,
  80. std::string(),
  81. std::string()),
  82. nudge_controller_(std::make_unique<DarkLightModeNudgeController>()) {
  83. DCHECK(!g_instance);
  84. g_instance = this;
  85. // May be null in unit tests.
  86. if (Shell::HasInstance()) {
  87. auto* shell = Shell::Get();
  88. shell->login_screen_controller()->data_dispatcher()->AddObserver(this);
  89. shell->wallpaper_controller()->AddObserver(this);
  90. }
  91. cros_styles::SetDebugColorsEnabled(base::FeatureList::IsEnabled(
  92. ash::features::kSemanticColorsDebugOverride));
  93. }
  94. DarkLightModeControllerImpl::~DarkLightModeControllerImpl() {
  95. DCHECK_EQ(g_instance, this);
  96. g_instance = nullptr;
  97. // May be null in unit tests.
  98. if (Shell::HasInstance()) {
  99. auto* shell = Shell::Get();
  100. auto* login_screen_controller = shell->login_screen_controller();
  101. auto* data_dispatcher = login_screen_controller
  102. ? login_screen_controller->data_dispatcher()
  103. : nullptr;
  104. if (data_dispatcher)
  105. data_dispatcher->RemoveObserver(this);
  106. shell->wallpaper_controller()->RemoveObserver(this);
  107. }
  108. cros_styles::SetDebugColorsEnabled(false);
  109. cros_styles::SetDarkModeEnabled(false);
  110. }
  111. // static
  112. DarkLightModeControllerImpl* DarkLightModeControllerImpl::Get() {
  113. return g_instance;
  114. }
  115. // static
  116. void DarkLightModeControllerImpl::RegisterProfilePrefs(
  117. PrefRegistrySimple* registry) {
  118. registry->RegisterIntegerPref(
  119. prefs::kDarkModeScheduleType,
  120. static_cast<int>(ScheduleType::kSunsetToSunrise));
  121. registry->RegisterBooleanPref(prefs::kDarkModeEnabled,
  122. kDefaultDarkModeEnabled);
  123. registry->RegisterIntegerPref(prefs::kDarkLightModeNudge,
  124. kDarkLightModeNudgeMaxShownCount);
  125. }
  126. void DarkLightModeControllerImpl::SetAutoScheduleEnabled(bool enabled) {
  127. SetScheduleType(enabled ? ScheduleType::kSunsetToSunrise
  128. : ScheduleType::kNone);
  129. }
  130. bool DarkLightModeControllerImpl::GetAutoScheduleEnabled() const {
  131. const ScheduleType type = GetScheduleType();
  132. // `DarkLightModeControllerImpl` does not support the custom scheduling.
  133. DCHECK_NE(type, ScheduleType::kCustom);
  134. return type == ScheduleType::kSunsetToSunrise;
  135. }
  136. void DarkLightModeControllerImpl::ToggleColorMode() {
  137. DCHECK(active_user_pref_service_);
  138. active_user_pref_service_->SetBoolean(prefs::kDarkModeEnabled,
  139. !IsDarkModeEnabled());
  140. active_user_pref_service_->CommitPendingWrite();
  141. NotifyColorModeChanges();
  142. // Updates showing logic of educational nudge on toggling the entry points of
  143. // dark/light mode.
  144. nudge_controller_->ToggledByUser();
  145. }
  146. void DarkLightModeControllerImpl::AddObserver(ColorModeObserver* observer) {
  147. observers_.AddObserver(observer);
  148. }
  149. void DarkLightModeControllerImpl::RemoveObserver(ColorModeObserver* observer) {
  150. observers_.RemoveObserver(observer);
  151. }
  152. bool DarkLightModeControllerImpl::IsDarkModeEnabled() const {
  153. if (!features::IsDarkLightModeEnabled() && override_light_mode_as_default_)
  154. return false;
  155. if (features::IsDarkLightModeEnabled()) {
  156. if (is_dark_mode_enabled_in_oobe_for_testing_.has_value())
  157. return is_dark_mode_enabled_in_oobe_for_testing_.value();
  158. if (oobe_state_ != OobeDialogState::HIDDEN) {
  159. if (active_user_pref_service_) {
  160. const PrefService::Preference* pref =
  161. active_user_pref_service_->FindPreference(
  162. prefs::kDarkModeScheduleType);
  163. // Managed users do not see the theme selection screen, so to avoid
  164. // confusion they should always see light colors during OOBE
  165. if (pref->IsManaged() || pref->IsRecommended())
  166. return false;
  167. if (!active_user_pref_service_->GetBoolean(prefs::kDarkModeEnabled))
  168. return false;
  169. }
  170. return base::Contains(kStatesSupportingDarkTheme, oobe_state_);
  171. }
  172. // On the login screen use the preference of the focused pod's user if they
  173. // had the preference stored in the known_user and the pod is focused.
  174. if (!active_user_pref_service_ &&
  175. is_dark_mode_enabled_for_focused_pod_.has_value()) {
  176. return is_dark_mode_enabled_for_focused_pod_.value();
  177. }
  178. }
  179. // Keep the color mode as DARK in login screen or when dark/light mode feature
  180. // is not enabled.
  181. if (!active_user_pref_service_ || !features::IsDarkLightModeEnabled())
  182. return true;
  183. return active_user_pref_service_->GetBoolean(prefs::kDarkModeEnabled);
  184. }
  185. void DarkLightModeControllerImpl::SetDarkModeEnabledForTest(bool enabled) {
  186. DCHECK(features::IsDarkLightModeEnabled());
  187. if (oobe_state_ != OobeDialogState::HIDDEN) {
  188. auto closure = GetNotifyOnDarkModeChangeClosure();
  189. is_dark_mode_enabled_in_oobe_for_testing_ = enabled;
  190. return;
  191. }
  192. if (IsDarkModeEnabled() != enabled)
  193. ToggleColorMode();
  194. }
  195. void DarkLightModeControllerImpl::OnOobeDialogStateChanged(
  196. OobeDialogState state) {
  197. auto closure = GetNotifyOnDarkModeChangeClosure();
  198. oobe_state_ = state;
  199. }
  200. void DarkLightModeControllerImpl::OnFocusPod(const AccountId& account_id) {
  201. auto closure = GetNotifyOnDarkModeChangeClosure();
  202. if (!account_id.is_valid()) {
  203. is_dark_mode_enabled_for_focused_pod_.reset();
  204. return;
  205. }
  206. is_dark_mode_enabled_for_focused_pod_ =
  207. user_manager::KnownUser(ash::Shell::Get()->local_state())
  208. .FindBoolPath(account_id, prefs::kDarkModeEnabled);
  209. }
  210. void DarkLightModeControllerImpl::OnWallpaperColorsChanged() {
  211. if (!features::IsDarkLightModeEnabled())
  212. return;
  213. RefreshColorsOnColorMode(IsDarkModeEnabled());
  214. }
  215. void DarkLightModeControllerImpl::OnActiveUserPrefServiceChanged(
  216. PrefService* prefs) {
  217. if (!features::IsDarkLightModeEnabled())
  218. return;
  219. active_user_pref_service_ = prefs;
  220. pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  221. pref_change_registrar_->Init(prefs);
  222. pref_change_registrar_->Add(
  223. prefs::kDarkModeEnabled,
  224. base::BindRepeating(&DarkLightModeControllerImpl::NotifyColorModeChanges,
  225. base::Unretained(this)));
  226. // Immediately tell all the observers to load this user's saved preferences.
  227. NotifyColorModeChanges();
  228. ScheduledFeature::OnActiveUserPrefServiceChanged(prefs);
  229. }
  230. void DarkLightModeControllerImpl::OnSessionStateChanged(
  231. session_manager::SessionState state) {
  232. if (!features::IsDarkLightModeEnabled())
  233. return;
  234. if (state != session_manager::SessionState::OOBE &&
  235. state != session_manager::SessionState::LOGIN_PRIMARY) {
  236. oobe_state_ = OobeDialogState::HIDDEN;
  237. }
  238. // Disable dark mode for Shimless RMA
  239. if (features::IsShimlessRMADarkModeDisabled() &&
  240. state == session_manager::SessionState::RMA) {
  241. RefreshColorsOnColorMode(/*is_dark_mode_enabled=*/false);
  242. return;
  243. }
  244. RefreshColorsOnColorMode(IsDarkModeEnabled());
  245. if (state == session_manager::SessionState::ACTIVE)
  246. nudge_controller_->MaybeShowNudge();
  247. }
  248. void DarkLightModeControllerImpl::SetShowNudgeForTesting(bool value) {
  249. nudge_controller_->set_show_nudge_for_testing(value); // IN-TEST
  250. }
  251. void DarkLightModeControllerImpl::RefreshFeatureState() {}
  252. const char* DarkLightModeControllerImpl::GetFeatureName() const {
  253. return "DarkLightModeControllerImpl";
  254. }
  255. void DarkLightModeControllerImpl::NotifyColorModeChanges() {
  256. const bool is_enabled = IsDarkModeEnabled();
  257. cros_styles::SetDarkModeEnabled(is_enabled);
  258. for (auto& observer : observers_)
  259. observer.OnColorModeChanged(is_enabled);
  260. RefreshColorsOnColorMode(IsDarkModeEnabled());
  261. }
  262. base::ScopedClosureRunner
  263. DarkLightModeControllerImpl::GetNotifyOnDarkModeChangeClosure() {
  264. return base::ScopedClosureRunner(
  265. // Unretained is safe here because GetNotifyOnDarkModeChangeClosure is a
  266. // private function and callback should be called on going out of scope of
  267. // the calling method.
  268. base::BindOnce(&DarkLightModeControllerImpl::NotifyIfDarkModeChanged,
  269. base::Unretained(this), IsDarkModeEnabled()));
  270. }
  271. void DarkLightModeControllerImpl::NotifyIfDarkModeChanged(
  272. bool old_is_dark_mode_enabled) {
  273. if (old_is_dark_mode_enabled == IsDarkModeEnabled())
  274. return;
  275. NotifyColorModeChanges();
  276. }
  277. } // namespace ash