blob: 25595470ec9795b93a99ea8835c555bd28d99278 [file] [log] [blame]
// 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