shelf_config.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. // Copyright (c) 2019 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/public/cpp/shelf_config.h"
  5. #include "ash/accessibility/accessibility_controller_impl.h"
  6. #include "ash/accessibility/accessibility_observer.h"
  7. #include "ash/app_list/app_list_controller_impl.h"
  8. #include "ash/constants/ash_features.h"
  9. #include "ash/session/session_controller_impl.h"
  10. #include "ash/shell.h"
  11. #include "ash/style/ash_color_provider.h"
  12. #include "ash/style/dark_light_mode_controller_impl.h"
  13. #include "ash/system/model/system_tray_model.h"
  14. #include "ash/wallpaper/wallpaper_controller_impl.h"
  15. #include "ash/wm/overview/overview_controller.h"
  16. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  17. #include "base/bind.h"
  18. #include "base/metrics/histogram_functions.h"
  19. #include "base/scoped_observation.h"
  20. #include "ui/gfx/color_analysis.h"
  21. #include "ui/gfx/color_palette.h"
  22. #include "ui/gfx/color_utils.h"
  23. namespace ash {
  24. namespace {
  25. // Used in as a value in histogram to record the reason shelf navigation buttons
  26. // are shown in tablet mode.
  27. // The values assigned to enum items should not be changed/reassigned.
  28. constexpr int kControlButtonsShownForShelfNavigationButtonsSetting = 1;
  29. constexpr int kControlButtonsShownForSpokenFeedback = 1 << 1;
  30. constexpr int kControlButtonsShownForSwitchAccess = 1 << 2;
  31. constexpr int kControlButtonsShownForAutoclick = 1 << 3;
  32. constexpr int kControlButtonsShownReasonCount = 1 << 4;
  33. // When any edge of the primary display is less than or equal to this threshold,
  34. // dense shelf will be active.
  35. constexpr int kDenseShelfScreenSizeThreshold = 600;
  36. // Drags on the shelf that are greater than this number times the shelf size
  37. // will trigger shelf visibility changes.
  38. constexpr float kDragHideRatioThreshold = 0.4f;
  39. // Records the histogram value tracking the reason shelf control buttons are
  40. // shown in tablet mode.
  41. void RecordReasonForShowingShelfControls() {
  42. AccessibilityControllerImpl* accessibility_controller =
  43. Shell::Get()->accessibility_controller();
  44. int buttons_shown_reason_mask = 0;
  45. if (accessibility_controller
  46. ->tablet_mode_shelf_navigation_buttons_enabled()) {
  47. buttons_shown_reason_mask |=
  48. kControlButtonsShownForShelfNavigationButtonsSetting;
  49. }
  50. if (accessibility_controller->spoken_feedback().enabled())
  51. buttons_shown_reason_mask |= kControlButtonsShownForSpokenFeedback;
  52. if (accessibility_controller->switch_access().enabled())
  53. buttons_shown_reason_mask |= kControlButtonsShownForSwitchAccess;
  54. if (accessibility_controller->autoclick().enabled())
  55. buttons_shown_reason_mask |= kControlButtonsShownForAutoclick;
  56. base::UmaHistogramExactLinear(
  57. "Ash.Shelf.NavigationButtonsInTabletMode.ReasonShown",
  58. buttons_shown_reason_mask, kControlButtonsShownReasonCount);
  59. }
  60. } // namespace
  61. class ShelfConfig::ShelfAccessibilityObserver : public AccessibilityObserver {
  62. public:
  63. explicit ShelfAccessibilityObserver(
  64. const base::RepeatingClosure& accessibility_state_changed_callback)
  65. : accessibility_state_changed_callback_(
  66. accessibility_state_changed_callback) {
  67. observation_.Observe(Shell::Get()->accessibility_controller());
  68. }
  69. ShelfAccessibilityObserver(const ShelfAccessibilityObserver& other) = delete;
  70. ShelfAccessibilityObserver& operator=(
  71. const ShelfAccessibilityObserver& other) = delete;
  72. ~ShelfAccessibilityObserver() override = default;
  73. // AccessibilityObserver:
  74. void OnAccessibilityStatusChanged() override {
  75. accessibility_state_changed_callback_.Run();
  76. }
  77. void OnAccessibilityControllerShutdown() override { observation_.Reset(); }
  78. private:
  79. base::RepeatingClosure accessibility_state_changed_callback_;
  80. base::ScopedObservation<AccessibilityControllerImpl, AccessibilityObserver>
  81. observation_{this};
  82. };
  83. class ShelfConfig::ShelfSplitViewObserver : public SplitViewObserver {
  84. public:
  85. explicit ShelfSplitViewObserver(
  86. SplitViewController* controller,
  87. const base::RepeatingCallback<void(SplitViewController::State,
  88. SplitViewController::State)>&
  89. split_view_state_changed_callback)
  90. : split_view_state_changed_callback_(split_view_state_changed_callback) {
  91. observation_.Observe(controller);
  92. }
  93. ShelfSplitViewObserver(const ShelfSplitViewObserver& other) = delete;
  94. ShelfSplitViewObserver& operator=(const ShelfSplitViewObserver& other) =
  95. delete;
  96. ~ShelfSplitViewObserver() override = default;
  97. // SplitViewObserver:
  98. void OnSplitViewStateChanged(SplitViewController::State previous_state,
  99. SplitViewController::State state) override {
  100. split_view_state_changed_callback_.Run(previous_state, state);
  101. }
  102. private:
  103. base::RepeatingCallback<void(SplitViewController::State,
  104. SplitViewController::State)>
  105. split_view_state_changed_callback_;
  106. base::ScopedObservation<SplitViewController, SplitViewObserver> observation_{
  107. this};
  108. };
  109. ShelfConfig::ShelfConfig()
  110. : use_in_app_shelf_in_overview_(false),
  111. overview_mode_(false),
  112. in_tablet_mode_(false),
  113. is_dense_(false),
  114. is_in_app_(true),
  115. in_split_view_with_overview_(false),
  116. shelf_controls_shown_(true),
  117. is_virtual_keyboard_shown_(false),
  118. is_app_list_visible_(false),
  119. shelf_button_icon_size_(44),
  120. shelf_button_icon_size_median_(40),
  121. shelf_button_icon_size_dense_(36),
  122. shelf_button_size_(56),
  123. shelf_button_size_median_(52),
  124. shelf_button_size_dense_(48),
  125. shelf_button_spacing_(8),
  126. shelf_status_area_hit_region_padding_(4),
  127. shelf_status_area_hit_region_padding_dense_(2),
  128. app_icon_group_margin_tablet_(16),
  129. app_icon_group_margin_clamshell_(12),
  130. workspace_area_visible_inset_(2),
  131. workspace_area_auto_hide_inset_(5),
  132. hidden_shelf_in_screen_portion_(3),
  133. status_indicator_offset_from_shelf_edge_(1),
  134. scrollable_shelf_ripple_padding_(2),
  135. shelf_tooltip_preview_height_(128),
  136. shelf_tooltip_preview_max_width_(192),
  137. shelf_tooltip_preview_max_ratio_(1.5), // = 3/2
  138. shelf_tooltip_preview_min_ratio_(0.666), // = 2/3
  139. shelf_blur_radius_(30),
  140. mousewheel_scroll_offset_threshold_(20),
  141. in_app_control_button_height_inset_(4),
  142. app_icon_end_padding_(4) {
  143. accessibility_observer_ = std::make_unique<ShelfAccessibilityObserver>(
  144. base::BindRepeating(&ShelfConfig::UpdateConfigForAccessibilityState,
  145. base::Unretained(this)));
  146. }
  147. ShelfConfig::~ShelfConfig() = default;
  148. // static
  149. ShelfConfig* ShelfConfig::Get() {
  150. return Shell::HasInstance() ? Shell::Get()->shelf_config() : nullptr;
  151. }
  152. void ShelfConfig::AddObserver(Observer* observer) {
  153. observers_.AddObserver(observer);
  154. }
  155. void ShelfConfig::RemoveObserver(Observer* observer) {
  156. observers_.RemoveObserver(observer);
  157. }
  158. void ShelfConfig::Init() {
  159. Shell* const shell = Shell::Get();
  160. shell->app_list_controller()->AddObserver(this);
  161. display_observer_.emplace(this);
  162. shell->system_tray_model()->virtual_keyboard()->AddObserver(this);
  163. shell->overview_controller()->AddObserver(this);
  164. shell->session_controller()->AddObserver(this);
  165. shell->tablet_mode_controller()->AddObserver(this);
  166. in_tablet_mode_ = shell->IsInTabletMode();
  167. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/false);
  168. }
  169. void ShelfConfig::Shutdown() {
  170. Shell* const shell = Shell::Get();
  171. shell->tablet_mode_controller()->RemoveObserver(this);
  172. shell->session_controller()->RemoveObserver(this);
  173. shell->overview_controller()->RemoveObserver(this);
  174. shell->system_tray_model()->virtual_keyboard()->RemoveObserver(this);
  175. display_observer_.reset();
  176. shell->app_list_controller()->RemoveObserver(this);
  177. }
  178. void ShelfConfig::OnOverviewModeWillStart() {
  179. DCHECK(!overview_mode_);
  180. use_in_app_shelf_in_overview_ = is_in_app();
  181. overview_mode_ = true;
  182. auto* split_view_controller =
  183. SplitViewController::Get(Shell::GetPrimaryRootWindow());
  184. in_split_view_with_overview_ = split_view_controller->InSplitViewMode();
  185. split_view_observer_ = std::make_unique<ShelfSplitViewObserver>(
  186. split_view_controller,
  187. base::BindRepeating(&ShelfConfig::OnSplitViewStateChanged,
  188. base::Unretained(this)));
  189. }
  190. void ShelfConfig::OnOverviewModeEnding(OverviewSession* overview_session) {
  191. split_view_observer_.reset();
  192. overview_mode_ = false;
  193. in_split_view_with_overview_ = false;
  194. use_in_app_shelf_in_overview_ = false;
  195. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/false);
  196. }
  197. void ShelfConfig::OnSplitViewStateChanged(
  198. SplitViewController::State previous_state,
  199. SplitViewController::State state) {
  200. in_split_view_with_overview_ = (state != SplitViewController::State::kNoSnap);
  201. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/false);
  202. }
  203. void ShelfConfig::OnTabletModeStarting() {
  204. // Update the shelf config at the "starting" stage of the tablet mode
  205. // transition, so that the shelf bounds are set and remains stable during the
  206. // transition animation. Otherwise, updating the shelf bounds during the
  207. // animation will lead to work-area bounds changes which lead to many
  208. // re-layouts, hurting the animation's smoothness. https://crbug.com/1044316.
  209. DCHECK(!in_tablet_mode_);
  210. in_tablet_mode_ = true;
  211. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/true);
  212. }
  213. void ShelfConfig::OnSessionStateChanged(session_manager::SessionState state) {
  214. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/false);
  215. }
  216. void ShelfConfig::OnTabletModeEnding() {
  217. // Many events can lead to UpdateConfig being called as a result of
  218. // OnTabletModeEnded(), therefore we need to listen to the "ending" stage
  219. // rather than the "ended", so |in_tablet_mode_| gets updated correctly, and
  220. // the shelf bounds are stabilized early so as not to have multiple
  221. // unnecessary work-area bounds changes.
  222. in_tablet_mode_ = false;
  223. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/true);
  224. }
  225. void ShelfConfig::OnDisplayMetricsChanged(const display::Display& display,
  226. uint32_t changed_metrics) {
  227. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/false);
  228. }
  229. void ShelfConfig::OnVirtualKeyboardVisibilityChanged() {
  230. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/false);
  231. }
  232. void ShelfConfig::OnAppListVisibilityWillChange(bool shown,
  233. int64_t display_id) {
  234. // Let's check that the app visibility mechanism isn't mis-firing, which
  235. // would lead to a lot of extraneous relayout work.
  236. DCHECK_NE(is_app_list_visible_, shown);
  237. UpdateConfig(/*new_is_app_list_visible=*/shown,
  238. /*tablet_mode_changed=*/false);
  239. }
  240. bool ShelfConfig::ShelfControlsForcedShownForAccessibility() const {
  241. auto* accessibility_controller = Shell::Get()->accessibility_controller();
  242. return accessibility_controller->spoken_feedback().enabled() ||
  243. accessibility_controller->autoclick().enabled() ||
  244. accessibility_controller->switch_access().enabled() ||
  245. accessibility_controller
  246. ->tablet_mode_shelf_navigation_buttons_enabled();
  247. }
  248. int ShelfConfig::GetShelfButtonSize(HotseatDensity density) const {
  249. if (is_dense_)
  250. return shelf_button_size_dense_;
  251. switch (density) {
  252. case HotseatDensity::kNormal:
  253. return shelf_button_size_;
  254. case HotseatDensity::kSemiDense:
  255. return shelf_button_size_median_;
  256. case HotseatDensity::kDense:
  257. return shelf_button_size_dense_;
  258. }
  259. }
  260. int ShelfConfig::GetShelfButtonIconSize(HotseatDensity density) const {
  261. if (is_dense_)
  262. return shelf_button_icon_size_dense_;
  263. switch (density) {
  264. case HotseatDensity::kNormal:
  265. return shelf_button_icon_size_;
  266. case HotseatDensity::kSemiDense:
  267. return shelf_button_icon_size_median_;
  268. case HotseatDensity::kDense:
  269. return shelf_button_icon_size_dense_;
  270. }
  271. }
  272. int ShelfConfig::GetHotseatSize(HotseatDensity density) const {
  273. if (!in_tablet_mode_)
  274. return shelf_size();
  275. return GetShelfButtonSize(density);
  276. }
  277. int ShelfConfig::shelf_size() const {
  278. return GetShelfSize(false /*ignore_in_app_state*/);
  279. }
  280. int ShelfConfig::in_app_shelf_size() const {
  281. return is_dense_ ? 36 : 40;
  282. }
  283. int ShelfConfig::system_shelf_size() const {
  284. return GetShelfSize(true /*ignore_in_app_state*/);
  285. }
  286. int ShelfConfig::shelf_drag_handle_centering_size() const {
  287. const session_manager::SessionState session_state =
  288. Shell::Get()->session_controller()->GetSessionState();
  289. return session_state == session_manager::SessionState::ACTIVE
  290. ? in_app_shelf_size()
  291. : 28;
  292. }
  293. int ShelfConfig::hotseat_bottom_padding() const {
  294. return 8;
  295. }
  296. int ShelfConfig::button_spacing() const {
  297. return shelf_button_spacing_;
  298. }
  299. int ShelfConfig::control_size() const {
  300. if (!in_tablet_mode_)
  301. return 36;
  302. return is_dense_ ? 36 : 40;
  303. }
  304. int ShelfConfig::control_border_radius() const {
  305. return (is_in_app() && in_tablet_mode_)
  306. ? control_size() / 2 - in_app_control_button_height_inset_
  307. : control_size() / 2;
  308. }
  309. int ShelfConfig::control_button_edge_spacing(bool is_primary_axis_edge) const {
  310. if (is_primary_axis_edge)
  311. return in_tablet_mode_ ? 8 : 6;
  312. return (shelf_size() - control_size()) / 2;
  313. }
  314. base::TimeDelta ShelfConfig::hotseat_background_animation_duration() const {
  315. // This matches the duration of the maximize/minimize animation.
  316. return base::Milliseconds(300);
  317. }
  318. base::TimeDelta ShelfConfig::shelf_animation_duration() const {
  319. return hotseat_background_animation_duration();
  320. }
  321. int ShelfConfig::status_area_hit_region_padding() const {
  322. return is_dense_ ? shelf_status_area_hit_region_padding_dense_
  323. : shelf_status_area_hit_region_padding_;
  324. }
  325. float ShelfConfig::drag_hide_ratio_threshold() const {
  326. return kDragHideRatioThreshold;
  327. }
  328. void ShelfConfig::UpdateConfig(bool new_is_app_list_visible,
  329. bool tablet_mode_changed) {
  330. const gfx::Rect screen_size =
  331. display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  332. const bool new_is_dense =
  333. !in_tablet_mode_ ||
  334. (screen_size.width() <= kDenseShelfScreenSizeThreshold ||
  335. screen_size.height() <= kDenseShelfScreenSizeThreshold);
  336. const bool can_hide_shelf_controls =
  337. in_tablet_mode_ && features::IsHideShelfControlsInTabletModeEnabled();
  338. const bool new_shelf_controls_shown =
  339. !can_hide_shelf_controls || ShelfControlsForcedShownForAccessibility();
  340. // Record reason to show shelf control buttons only if tablet mode changes, or
  341. // if the buttons visibility state changes
  342. if (can_hide_shelf_controls && new_shelf_controls_shown &&
  343. (tablet_mode_changed || !shelf_controls_shown_)) {
  344. RecordReasonForShowingShelfControls();
  345. }
  346. // TODO(https://crbug.com/1058205): Test this behavior.
  347. // If the virtual keyboard is shown, the back button and in-app shelf should
  348. // be shown so users can exit the keyboard. SystemTrayModel may be null in
  349. // tests.
  350. const bool new_is_virtual_keyboard_shown = Shell::Get()->system_tray_model()
  351. ? Shell::Get()
  352. ->system_tray_model()
  353. ->virtual_keyboard()
  354. ->arc_keyboard_visible()
  355. : false;
  356. const bool new_is_in_app =
  357. CalculateIsInApp(new_is_app_list_visible, new_is_virtual_keyboard_shown);
  358. const bool changed =
  359. tablet_mode_changed || is_dense_ != new_is_dense ||
  360. is_in_app_ != new_is_in_app ||
  361. shelf_controls_shown_ != new_shelf_controls_shown ||
  362. is_virtual_keyboard_shown_ != new_is_virtual_keyboard_shown ||
  363. is_app_list_visible_ != new_is_app_list_visible;
  364. if (!changed)
  365. return;
  366. is_dense_ = new_is_dense;
  367. shelf_controls_shown_ = new_shelf_controls_shown;
  368. is_virtual_keyboard_shown_ = new_is_virtual_keyboard_shown;
  369. is_app_list_visible_ = new_is_app_list_visible;
  370. is_in_app_ = new_is_in_app;
  371. OnShelfConfigUpdated();
  372. }
  373. int ShelfConfig::GetShelfSize(bool ignore_in_app_state) const {
  374. // In clamshell mode, the shelf always has the same size.
  375. if (!in_tablet_mode_)
  376. return 48;
  377. // Use in app shelf when split view is enabled.
  378. if (!ignore_in_app_state && (is_in_app() || in_split_view_with_overview_))
  379. return in_app_shelf_size();
  380. return is_dense_ ? 48 : 56;
  381. }
  382. SkColor ShelfConfig::GetShelfControlButtonColor() const {
  383. const session_manager::SessionState session_state =
  384. Shell::Get()->session_controller()->GetSessionState();
  385. if (in_tablet_mode_ &&
  386. session_state == session_manager::SessionState::ACTIVE) {
  387. return is_in_app() ? SK_ColorTRANSPARENT : GetDefaultShelfColor();
  388. }
  389. if (!features::IsDarkLightModeEnabled() &&
  390. session_state == session_manager::SessionState::OOBE) {
  391. return SkColorSetA(SK_ColorBLACK, 16); // 6% opacity
  392. }
  393. return AshColorProvider::Get()->GetControlsLayerColor(
  394. AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive);
  395. }
  396. SkColor ShelfConfig::GetShelfWithAppListColor() const {
  397. return SkColorSetA(SK_ColorBLACK, 20); // 8% opacity
  398. }
  399. SkColor ShelfConfig::GetMaximizedShelfColor() const {
  400. return SkColorSetA(GetDefaultShelfColor(), 0xFF); // 100% opacity
  401. }
  402. AshColorProvider::BaseLayerType ShelfConfig::GetShelfBaseLayerType() const {
  403. if (!in_tablet_mode_)
  404. return AshColorProvider::BaseLayerType::kTransparent80;
  405. if (!is_in_app())
  406. return AshColorProvider::BaseLayerType::kTransparent60;
  407. return DarkLightModeControllerImpl::Get()->IsDarkModeEnabled()
  408. ? AshColorProvider::BaseLayerType::kTransparent90
  409. : AshColorProvider::BaseLayerType::kOpaque;
  410. }
  411. SkColor ShelfConfig::GetDefaultShelfColor() const {
  412. if (!features::IsBackgroundBlurEnabled()) {
  413. return AshColorProvider::Get()->GetBaseLayerColor(
  414. AshColorProvider::BaseLayerType::kTransparent90);
  415. }
  416. AshColorProvider::BaseLayerType layer_type = GetShelfBaseLayerType();
  417. return AshColorProvider::Get()->GetBaseLayerColor(layer_type);
  418. }
  419. int ShelfConfig::GetShelfControlButtonBlurRadius() const {
  420. if (features::IsBackgroundBlurEnabled() && in_tablet_mode_ && !is_in_app())
  421. return shelf_blur_radius_;
  422. return 0;
  423. }
  424. int ShelfConfig::GetAppIconEndPadding() const {
  425. return app_icon_end_padding_;
  426. }
  427. int ShelfConfig::GetAppIconGroupMargin() const {
  428. return in_tablet_mode_ ? app_icon_group_margin_tablet_
  429. : app_icon_group_margin_clamshell_;
  430. }
  431. base::TimeDelta ShelfConfig::DimAnimationDuration() const {
  432. return base::Milliseconds(1000);
  433. }
  434. gfx::Tween::Type ShelfConfig::DimAnimationTween() const {
  435. return gfx::Tween::LINEAR;
  436. }
  437. gfx::Size ShelfConfig::DragHandleSize() const {
  438. const session_manager::SessionState session_state =
  439. Shell::Get()->session_controller()->GetSessionState();
  440. return session_state == session_manager::SessionState::ACTIVE
  441. ? gfx::Size(80, 4)
  442. : gfx::Size(120, 4);
  443. }
  444. void ShelfConfig::UpdateConfigForAccessibilityState() {
  445. UpdateConfig(is_app_list_visible_, /*tablet_mode_changed=*/false);
  446. }
  447. bool ShelfConfig::CalculateIsInApp(bool app_list_visible,
  448. bool virtual_keyboard_shown) const {
  449. Shell* shell = Shell::Get();
  450. const auto* session = shell->session_controller();
  451. if (!session ||
  452. session->GetSessionState() != session_manager::SessionState::ACTIVE) {
  453. return false;
  454. }
  455. if (virtual_keyboard_shown)
  456. return true;
  457. if (app_list_visible)
  458. return false;
  459. if (in_split_view_with_overview_)
  460. return true;
  461. if (overview_mode_)
  462. return use_in_app_shelf_in_overview_;
  463. return true;
  464. }
  465. void ShelfConfig::OnShelfConfigUpdated() {
  466. for (auto& observer : observers_)
  467. observer.OnShelfConfigUpdated();
  468. }
  469. } // namespace ash