// 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 #include "ash/constants/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/launcher_nudge_controller.h" #include "ash/shelf/shelf.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/display/display.h" #include "ui/display/screen.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() { 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); } ShelfController::~ShelfController() { model_.DestroyItemDelegates(); } void ShelfController::Init() { launcher_nudge_controller_ = std::make_unique(); } void ShelfController::Shutdown() { 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 ChromeShelfController'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); LauncherNudgeController::RegisterProfilePrefs(registry); } void ShelfController::OnActiveUserSessionChanged(const AccountId& account_id) { if (model_.in_shelf_party()) model_.ToggleShelfParty(); } void ShelfController::OnSessionStateChanged( session_manager::SessionState state) { if (model_.in_shelf_party()) model_.ToggleShelfParty(); } void ShelfController::OnActiveUserPrefServiceChanged( PrefService* pref_service) { SetShelfBehaviorsFromPrefs(); pref_change_registrar_ = std::make_unique(); 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)); 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); } } } 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); } } } 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)) { bool has_badge = update.HasBadge().value_or(false); model_.UpdateItemNotification(update.AppId(), has_badge); } } void ShelfController::OnAppRegistryCacheWillBeDestroyed( apps::AppRegistryCache* cache) { Observe(nullptr); } void ShelfController::ShelfItemAdded(int index) { if (!cache_ || !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().value_or(false); model_.UpdateItemNotification(update.AppId(), has_badge); }); } void ShelfController::UpdateAppNotificationBadging() { bool new_badging_enabled = pref_change_registrar_ ? pref_change_registrar_->prefs()->GetBoolean( prefs::kAppNotificationBadgingEnabled) : false; if (notification_badging_pref_enabled_.has_value() && notification_badging_pref_enabled_.value() == new_badging_enabled) { return; } notification_badging_pref_enabled_ = new_badging_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() ? update.HasBadge().value_or(false) : false; model_.UpdateItemNotification(update.AppId(), has_badge); }); } } } // namespace ash