| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/shelf/shelf_controller.h" |
| |
| #include "ash/public/cpp/ash_pref_names.h" |
| #include "ash/public/cpp/message_center/arc_notification_constants.h" |
| #include "ash/public/cpp/shelf_item_delegate.h" |
| #include "ash/public/cpp/shelf_prefs.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_widget.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/bind.h" |
| #include "chromeos/strings/grit/chromeos_strings.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_change_registrar.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/message_center/message_center.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Returns the Shelf instance for the display with the given |display_id|. |
| Shelf* GetShelfForDisplay(int64_t display_id) { |
| // The controller may be null for invalid ids or for displays being removed. |
| RootWindowController* root_window_controller = |
| Shell::GetRootWindowControllerWithDisplayId(display_id); |
| return root_window_controller ? root_window_controller->shelf() : nullptr; |
| } |
| |
| // Set each Shelf's auto-hide behavior from the per-display pref. |
| void SetShelfAutoHideFromPrefs() { |
| // TODO(jamescook): The session state check should not be necessary, but |
| // otherwise this wrongly tries to set the alignment on a secondary display |
| // during login before the ShelfLockingManager is created. |
| SessionControllerImpl* session_controller = |
| Shell::Get()->session_controller(); |
| PrefService* prefs = session_controller->GetLastActiveUserPrefService(); |
| if (!prefs || !session_controller->IsActiveUserSessionStarted()) |
| return; |
| |
| for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { |
| auto value = GetShelfAutoHideBehaviorPref(prefs, display.id()); |
| // Don't show the shelf in app mode. |
| if (session_controller->IsRunningInAppMode()) |
| value = ShelfAutoHideBehavior::kAlwaysHidden; |
| if (Shelf* shelf = GetShelfForDisplay(display.id())) |
| shelf->SetAutoHideBehavior(value); |
| } |
| } |
| |
| // Set each Shelf's alignment from the per-display pref. |
| void SetShelfAlignmentFromPrefs() { |
| // TODO(jamescook): The session state check should not be necessary, but |
| // otherwise this wrongly tries to set the alignment on a secondary display |
| // during login before the ShelfLockingManager is created. |
| SessionControllerImpl* session_controller = |
| Shell::Get()->session_controller(); |
| PrefService* prefs = session_controller->GetLastActiveUserPrefService(); |
| if (!prefs || !session_controller->IsActiveUserSessionStarted()) |
| return; |
| |
| for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { |
| if (Shelf* shelf = GetShelfForDisplay(display.id())) |
| shelf->SetAlignment(GetShelfAlignmentPref(prefs, display.id())); |
| } |
| } |
| |
| void UpdateShelfVisibility() { |
| for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { |
| if (Shelf* shelf = GetShelfForDisplay(display.id())) |
| shelf->UpdateVisibilityState(); |
| } |
| } |
| |
| // Set each Shelf's auto-hide behavior and alignment from the per-display prefs. |
| void SetShelfBehaviorsFromPrefs() { |
| SetShelfAutoHideFromPrefs(); |
| |
| // The shelf should always be bottom-aligned in tablet mode; alignment is |
| // assigned from prefs when tablet mode is exited. |
| if (Shell::Get()->tablet_mode_controller()->InTabletMode()) { |
| return; |
| } |
| |
| SetShelfAlignmentFromPrefs(); |
| } |
| |
| } // namespace |
| |
| ShelfController::ShelfController() |
| : is_notification_indicator_enabled_( |
| features::IsNotificationIndicatorEnabled()) { |
| ShelfModel::SetInstance(&model_); |
| |
| Shell::Get()->session_controller()->AddObserver(this); |
| Shell::Get()->tablet_mode_controller()->AddObserver(this); |
| Shell::Get()->window_tree_host_manager()->AddObserver(this); |
| model_.AddObserver(this); |
| message_center::MessageCenter::Get()->AddObserver(this); |
| } |
| |
| ShelfController::~ShelfController() { |
| model_.DestroyItemDelegates(); |
| } |
| |
| void ShelfController::Shutdown() { |
| message_center::MessageCenter::Get()->RemoveObserver(this); |
| model_.RemoveObserver(this); |
| Shell::Get()->window_tree_host_manager()->RemoveObserver(this); |
| Shell::Get()->tablet_mode_controller()->RemoveObserver(this); |
| Shell::Get()->session_controller()->RemoveObserver(this); |
| } |
| |
| // static |
| void ShelfController::RegisterProfilePrefs(PrefRegistrySimple* registry) { |
| // These prefs are public for ChromeLauncherController's OnIsSyncingChanged. |
| // See the pref names definitions for explanations of the synced, local, and |
| // per-display behaviors. |
| registry->RegisterStringPref( |
| prefs::kShelfAutoHideBehavior, kShelfAutoHideBehaviorNever, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF); |
| registry->RegisterStringPref(prefs::kShelfAutoHideBehaviorLocal, |
| std::string()); |
| registry->RegisterStringPref( |
| prefs::kShelfAlignment, kShelfAlignmentBottom, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF); |
| registry->RegisterStringPref(prefs::kShelfAlignmentLocal, std::string()); |
| registry->RegisterDictionaryPref(prefs::kShelfPreferences); |
| } |
| |
| void ShelfController::OnActiveUserPrefServiceChanged( |
| PrefService* pref_service) { |
| SetShelfBehaviorsFromPrefs(); |
| pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>(); |
| pref_change_registrar_->Init(pref_service); |
| pref_change_registrar_->Add(prefs::kShelfAlignmentLocal, |
| base::BindRepeating(&SetShelfAlignmentFromPrefs)); |
| pref_change_registrar_->Add(prefs::kShelfAutoHideBehaviorLocal, |
| base::BindRepeating(&SetShelfAutoHideFromPrefs)); |
| pref_change_registrar_->Add(prefs::kShelfPreferences, |
| base::BindRepeating(&SetShelfBehaviorsFromPrefs)); |
| |
| if (is_notification_indicator_enabled_) { |
| pref_change_registrar_->Add( |
| prefs::kAppNotificationBadgingEnabled, |
| base::BindRepeating(&ShelfController::UpdateAppNotificationBadging, |
| base::Unretained(this))); |
| |
| // Observe AppRegistryCache for the current active account to get |
| // notification updates. |
| AccountId account_id = |
| Shell::Get()->session_controller()->GetActiveAccountId(); |
| cache_ = |
| apps::AppRegistryCacheWrapper::Get().GetAppRegistryCache(account_id); |
| Observe(cache_); |
| |
| // Resetting the recorded pref forces the next call to |
| // UpdateAppNotificationBadging() to update notification badging for every |
| // app item. |
| notification_badging_pref_enabled_.reset(); |
| |
| // Update the notification badge indicator for all apps. This will also |
| // ensure that apps have the correct notification badge value for the |
| // multiprofile case when switching between users. |
| UpdateAppNotificationBadging(); |
| } |
| } |
| |
| void ShelfController::OnTabletModeStarted() { |
| // Do nothing when running in app mode. |
| if (Shell::Get()->session_controller()->IsRunningInAppMode()) |
| return; |
| |
| // Force the shelf to be visible and to be bottom aligned in tablet mode; the |
| // prefs are restored on exit. |
| for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { |
| if (Shelf* shelf = GetShelfForDisplay(display.id())) { |
| // Only animate into tablet mode if the shelf alignment will not change. |
| if (shelf->IsHorizontalAlignment()) |
| shelf->set_is_tablet_mode_animation_running(true); |
| shelf->SetAlignment(ShelfAlignment::kBottom); |
| shelf->shelf_widget()->OnTabletModeChanged(); |
| } |
| } |
| } |
| |
| void ShelfController::OnTabletModeEnded() { |
| // Do nothing when running in app mode. |
| if (Shell::Get()->session_controller()->IsRunningInAppMode()) |
| return; |
| |
| SetShelfBehaviorsFromPrefs(); |
| // Only animate out of tablet mode if the shelf alignment will not change. |
| for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { |
| if (Shelf* shelf = GetShelfForDisplay(display.id())) { |
| if (shelf->IsHorizontalAlignment()) |
| shelf->set_is_tablet_mode_animation_running(true); |
| shelf->shelf_widget()->OnTabletModeChanged(); |
| } |
| } |
| } |
| |
| void ShelfController::OnDisplayConfigurationChanged() { |
| // Update the alignment and auto-hide state from prefs, because a display may |
| // have been added, or the display ids for existing shelf instances may have |
| // changed. See https://crbug.com/748291 |
| SetShelfBehaviorsFromPrefs(); |
| |
| // Update shelf visibility to adapt to display changes. For instance shelf |
| // should be hidden on secondary display during inactive session states. |
| UpdateShelfVisibility(); |
| } |
| |
| void ShelfController::OnAppUpdate(const apps::AppUpdate& update) { |
| if (update.HasBadgeChanged() && |
| notification_badging_pref_enabled_.value_or(false) && |
| !quiet_mode_enabled_.value_or(false)) { |
| bool has_badge = update.HasBadge() == apps::mojom::OptionalBool::kTrue; |
| model_.UpdateItemNotification(update.AppId(), has_badge); |
| } |
| } |
| |
| void ShelfController::OnAppRegistryCacheWillBeDestroyed( |
| apps::AppRegistryCache* cache) { |
| Observe(nullptr); |
| } |
| |
| void ShelfController::ShelfItemAdded(int index) { |
| if (!cache_ || !is_notification_indicator_enabled_ || |
| !notification_badging_pref_enabled_.value_or(false)) |
| return; |
| |
| auto app_id = model_.items()[index].id.app_id; |
| |
| // Update the notification badge indicator for the newly added shelf item. |
| cache_->ForOneApp(app_id, [this](const apps::AppUpdate& update) { |
| bool has_badge = update.HasBadge() == apps::mojom::OptionalBool::kTrue; |
| model_.UpdateItemNotification(update.AppId(), has_badge); |
| }); |
| } |
| |
| void ShelfController::OnQuietModeChanged(bool in_quiet_mode) { |
| UpdateAppNotificationBadging(); |
| } |
| |
| void ShelfController::UpdateAppNotificationBadging() { |
| bool new_badging_enabled = pref_change_registrar_ |
| ? pref_change_registrar_->prefs()->GetBoolean( |
| prefs::kAppNotificationBadgingEnabled) |
| : false; |
| bool new_quiet_mode_enabled = |
| message_center::MessageCenter::Get()->IsQuietMode(); |
| |
| if (notification_badging_pref_enabled_.has_value() && |
| notification_badging_pref_enabled_.value() == new_badging_enabled && |
| quiet_mode_enabled_.has_value() && |
| quiet_mode_enabled_.value() == new_quiet_mode_enabled) { |
| return; |
| } |
| notification_badging_pref_enabled_ = new_badging_enabled; |
| quiet_mode_enabled_ = new_quiet_mode_enabled; |
| |
| if (cache_) { |
| cache_->ForEachApp([this](const apps::AppUpdate& update) { |
| // Set the app notification badge hidden when the pref is disabled. |
| bool has_badge = |
| notification_badging_pref_enabled_.value() && |
| !quiet_mode_enabled_.value() |
| ? (update.HasBadge() == apps::mojom::OptionalBool::kTrue) |
| : false; |
| |
| model_.UpdateItemNotification(update.AppId(), has_badge); |
| }); |
| } |
| } |
| |
| } // namespace ash |