// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/focus_mode/focus_mode_controller.h"
#include <memory>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/api/tasks/tasks_types.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/url_constants.h"
#include "ash/media/media_controller_impl.h"
#include "ash/public/cpp/ash_web_view_factory.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/system/anchored_nudge_data.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/do_not_disturb_notification_controller.h"
#include "ash/system/focus_mode/focus_mode_histogram_names.h"
#include "ash/system/focus_mode/focus_mode_metrics_recorder.h"
#include "ash/system/focus_mode/focus_mode_session.h"
#include "ash/system/focus_mode/focus_mode_tasks_provider.h"
#include "ash/system/focus_mode/focus_mode_tray.h"
#include "ash/system/focus_mode/focus_mode_util.h"
#include "ash/system/focus_mode/sounds/focus_mode_sounds_controller.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/system/unified/unified_system_tray.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/time/time.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/message_center.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
FocusModeController* g_instance = nullptr;
// The default Focus Mode session duration.
constexpr base::TimeDelta kDefaultSessionDuration = base::Minutes(25);
constexpr base::TimeDelta kSessionEndSoundDelay = base::Milliseconds(200);
bool IsQuietModeOnSetByFocusMode() {
auto* message_center = message_center::MessageCenter::Get();
return message_center->IsQuietMode() &&
message_center->GetLastQuietModeChangeSourceType() ==
message_center::QuietModeSourceType::kFocusMode;
}
// Updates the notification if DND was turned on by the focus mode.
void MaybeUpdateDndNotification() {
if (!IsQuietModeOnSetByFocusMode()) {
return;
}
if (auto* notification_controller =
DoNotDisturbNotificationController::Get()) {
notification_controller->MaybeUpdateNotification();
}
}
FocusModeTray* GetFocusModeTrayInActiveWindow() {
auto* window = Shell::Get()->GetRootWindowForNewWindows();
if (!window) {
return nullptr;
}
auto* root_window_controller = RootWindowController::ForWindow(window);
if (!root_window_controller) {
return nullptr;
}
auto* status_area_widget = root_window_controller->GetStatusAreaWidget();
if (!status_area_widget) {
return nullptr;
}
return status_area_widget->focus_mode_tray();
}
void ShowEndingMomentNudge(
const size_t congratulatory_index,
const std::optional<FocusModeSession>& current_session) {
auto* tray = GetFocusModeTrayInActiveWindow();
if (!tray) {
return;
}
// NOTE: we anchor to `tray->image_view()` in order to center the nudge
// properly because there is extra spacing on the actual `FocusModeTray` view.
const auto& title_and_emoji =
focus_mode_util::GetCongratulatoryTextAndEmoji(congratulatory_index);
AnchoredNudgeData nudge_data(focus_mode_util::kFocusModeEndingMomentNudgeId,
NudgeCatalogName::kFocusModeEndingMomentNudge,
title_and_emoji, tray->image_view());
nudge_data.arrow = views::BubbleBorder::BOTTOM_CENTER;
nudge_data.duration = NudgeDuration::kDefaultDuration;
nudge_data.anchored_to_shelf = true;
nudge_data.announce_chromevox = false;
nudge_data.click_callback =
base::BindRepeating(&FocusModeTray::ShowBubble, base::Unretained(tray));
AnchoredNudgeManager::Get()->Show(nudge_data);
CHECK(current_session);
const std::u16string duration_string =
focus_mode_util::GetDurationString(current_session->session_duration(),
/*digital_format=*/false);
Shell::Get()
->accessibility_controller()
->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_ENDING_MOMENT_NUDGE_ALERT,
title_and_emoji, duration_string));
}
void HideEndingMomentNudge() {
if (AnchoredNudgeManager* nudge_manager = AnchoredNudgeManager::Get()) {
nudge_manager->Cancel(focus_mode_util::kFocusModeEndingMomentNudgeId);
}
}
void OnTaskFetched(FocusModeTasksModel::Delegate::FetchTaskCallback callback,
const FocusModeTask& task) {
if (task.empty()) {
std::move(callback).Run(std::nullopt);
return;
}
std::move(callback).Run(task);
}
} // namespace
FocusModeController::FocusModeController(
std::unique_ptr<FocusModeDelegate> delegate)
: session_duration_(kDefaultSessionDuration),
delegate_(std::move(delegate)) {
CHECK_EQ(g_instance, nullptr);
g_instance = this;
focus_mode_sounds_controller_ = std::make_unique<FocusModeSoundsController>();
focus_mode_sounds_controller_->AddObserver(this);
tasks_model_.SetDelegate(weak_factory_.GetWeakPtr());
tasks_model_observation_.Observe(&tasks_model_);
Shell::Get()->session_controller()->AddObserver(this);
}
FocusModeController::~FocusModeController() {
Shell::Get()->session_controller()->RemoveObserver(this);
focus_mode_sounds_controller_->RemoveObserver(this);
// TODO(b/338694884): Move this to startup.
if (IsQuietModeOnSetByFocusMode()) {
message_center::MessageCenter::Get()->SetQuietMode(
false, message_center::QuietModeSourceType::kFocusMode);
}
CHECK_EQ(g_instance, this);
g_instance = nullptr;
}
// static
FocusModeController* FocusModeController::Get() {
CHECK(g_instance);
return g_instance;
}
// static
bool FocusModeController::CanExtendSessionDuration(
const FocusModeSession::Snapshot& snapshot) {
return snapshot.session_duration < focus_mode_util::kMaximumDuration;
}
// static
void FocusModeController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterTimeDeltaPref(
prefs::kFocusModeSessionDuration,
/*default_value=*/kDefaultSessionDuration,
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
registry->RegisterBooleanPref(
prefs::kFocusModeDoNotDisturb,
/*default_value=*/true,
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
registry->RegisterDictionaryPref(
prefs::kFocusModeSelectedTask,
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
registry->RegisterDictionaryPref(
prefs::kFocusModeSoundSection,
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
// Pref only set via policy.
registry->RegisterStringPref(prefs::kFocusModeSoundsEnabled,
focus_mode_util::kFocusModeSoundsEnabled);
}
void FocusModeController::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void FocusModeController::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void FocusModeController::ToggleFocusMode(
focus_mode_histogram_names::ToggleSource source) {
if (in_focus_session()) {
base::UmaHistogramEnumeration(
/*name=*/focus_mode_histogram_names::
kToggleEndButtonDuringSessionHistogramName,
/*sample=*/source);
ResetFocusSession();
return;
}
StartFocusSession(source);
}
void FocusModeController::OnActiveUserSessionChanged(
const AccountId& account_id) {
ResetFocusSession();
tasks_model_.Reset();
tasks_provider_.Reset();
// Since we cannot guarantee that `TasksClientImpl::InvalidateCache()` has
// been called before this when the active user session changes, we should
// just call `FocusModeController::UpdateFromUserPrefs()` as a PostTask to
// prevent the `TasksClientImpl::GetTasks()` callback from potentially being
// failed.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&FocusModeController::UpdateFromUserPrefs,
weak_factory_.GetWeakPtr()));
}
void FocusModeController::OnSelectedPlaylistChanged() {
// If a user swaps playlists or deselects the playlist, we should close the
// previous media widget. The reason we don't just reuse the existing widget
// with a new playlist is that we need to refresh the web view source title so
// that it's populated correctly in the media controls.
if (media_widget_) {
CloseMediaWidget();
}
if (focus_mode_metrics_recorder_) {
focus_mode_metrics_recorder_->SetHasSelectedSoundType(
focus_mode_sounds_controller_->selected_playlist());
}
// Only attempt to create the media widget if we are in an active focus
// session.
if (in_focus_session()) {
MaybeCreateMediaWidget();
}
}
void FocusModeController::OnSelectedTaskChanged(
const std::optional<FocusModeTask>& task) {
if (in_focus_session() || in_ending_moment()) {
SaveSelectedTaskSettingsToUserPrefs(task);
}
if (focus_mode_metrics_recorder_ && task) {
focus_mode_metrics_recorder_->IncrementTasksSelectedCount();
}
}
void FocusModeController::OnTasksUpdated(
const std::vector<FocusModeTask>& tasks) {}
void FocusModeController::OnTaskCompleted(const FocusModeTask& completed_task) {
if (focus_mode_metrics_recorder_) {
focus_mode_metrics_recorder_->IncrementTasksCompletedCount();
}
}
void FocusModeController::FetchTask(
const TaskId& task_id,
FocusModeTasksModel::Delegate::FetchTaskCallback callback) {
tasks_provider_.GetTask(task_id.list_id, task_id.id,
base::BindOnce(OnTaskFetched, std::move(callback)));
}
void FocusModeController::FetchTasks() {
tasks_provider_.GetSortedTaskList(base::BindOnce(
&FocusModeController::OnTasksReceived, weak_factory_.GetWeakPtr()));
}
void FocusModeController::AddTask(
const FocusModeTasksModel::TaskUpdate& update,
FocusModeTasksModel::Delegate::FetchTaskCallback callback) {
tasks_provider_.AddTask(*update.title,
base::BindOnce(OnTaskFetched, std::move(callback)));
}
void FocusModeController::UpdateTask(
const FocusModeTasksModel::TaskUpdate& update) {
const TaskId& task_id = *update.task_id;
const std::string& title = update.title.has_value() ? *update.title : "";
const bool completed =
update.completed.has_value() ? update.completed.value() : false;
tasks_provider_.UpdateTask(task_id.list_id, task_id.id, title, completed,
base::DoNothing());
}
void FocusModeController::ExtendSessionDuration() {
CHECK(current_session_);
const bool was_in_ending_moment = in_ending_moment();
const base::Time now = base::Time::Now();
// We call this with `now` to make sure that all the actions taken are synced
// to the same time, since the state depends on `now`.
current_session_->ExtendSession(now);
std::string message;
if (was_in_ending_moment) {
PerformActionsForMusic();
paused_by_ending_moment_ = false;
focus_mode_metrics_recorder_->RecordHistogramOnEndingMoment(
focus_mode_histogram_names::EndingMomentBubbleClosedReason::kExtended);
message = l10n_util::GetStringUTF8(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_EXTEND_TEN_MINUTES_BUTTON_ALERT);
} else {
const std::u16string duration_string = focus_mode_util::GetDurationString(
current_session_->GetTimeRemaining(now), /*digital_format=*/false);
message = l10n_util::GetStringFUTF8(
IDS_ASH_STATUS_TRAY_FOCUS_MODE_INCREASE_TEN_MINUTES_BUTTON_ALERT,
duration_string);
}
Shell::Get()
->accessibility_controller()
->TriggerAccessibilityAlertWithMessage(message);
const auto session_snapshot = current_session_->GetSnapshot(now);
for (auto& observer : observers_) {
observer.OnActiveSessionDurationChanged(session_snapshot);
}
if (!timer_.IsRunning()) {
// Start the `session_duration_` timer again.
timer_.Start(FROM_HERE, base::Seconds(1), this,
&FocusModeController::OnTimerTick, base::TimeTicks::Now());
for (auto& observer : observers_) {
observer.OnFocusModeChanged(/*in_focus_session=*/true);
}
}
MaybeUpdateDndNotification();
}
void FocusModeController::ResetFocusSession() {
if (focus_mode_metrics_recorder_) {
focus_mode_metrics_recorder_->RecordHistogramsOnEnd();
if (!in_focus_session()) {
focus_mode_metrics_recorder_->RecordHistogramOnEndingMoment(
current_session()->persistent_ending()
? focus_mode_histogram_names::EndingMomentBubbleClosedReason::
kOpended
: focus_mode_histogram_names::EndingMomentBubbleClosedReason::
kIgnored);
}
focus_mode_metrics_recorder_.reset();
}
if (timer_.IsRunning()) {
timer_.Stop();
}
HideEndingMomentNudge();
SetFocusTrayVisibility(false);
if (media_widget_) {
CloseMediaWidget();
}
if (IsQuietModeOnSetByFocusMode()) {
message_center::MessageCenter::Get()->SetQuietMode(
false, message_center::QuietModeSourceType::kFocusMode);
}
const bool was_in_focus_session = in_focus_session();
current_session_.reset();
if (was_in_focus_session) {
for (auto& observer : observers_) {
observer.OnFocusModeChanged(/*in_focus_session=*/false);
}
}
}
void FocusModeController::EnablePersistentEnding() {
// This is only used right now for when we click the tray icon to open the
// bubble during the ending moment. This prevents the bubble from being closed
// automatically.
if (!in_ending_moment()) {
return;
}
if (timer_.IsRunning()) {
timer_.Stop();
}
// Update the session to stay in the ending moment state.
current_session_->set_persistent_ending();
HideEndingMomentNudge();
}
void FocusModeController::SetInactiveSessionDuration(
const base::TimeDelta& new_session_duration) {
CHECK(!in_focus_session());
const base::TimeDelta valid_new_session_duration =
std::clamp(new_session_duration, focus_mode_util::kMinimumDuration,
focus_mode_util::kMaximumDuration);
if (session_duration_ == valid_new_session_duration) {
return;
}
// We do not immediately commit the change directly to the user prefs because
// the user has not yet indicated their preferred timer duration by starting
// the timer.
session_duration_ = valid_new_session_duration;
for (auto& observer : observers_) {
observer.OnInactiveSessionDurationChanged(session_duration_);
}
}
bool FocusModeController::HasStartedSessionBefore() const {
// Since `kFocusModeDoNotDisturb` is always set whenever a focus session is
// started, we can use this as an indicator of if the user has ever started a
// focus session before.
if (PrefService* active_user_prefs =
Shell::Get()->session_controller()->GetActivePrefService()) {
return active_user_prefs->HasPrefPath(prefs::kFocusModeDoNotDisturb);
}
return false;
}
FocusModeSession::Snapshot FocusModeController::GetSnapshot(
const base::Time& now) const {
return current_session_ ? current_session_->GetSnapshot(now)
: FocusModeSession::Snapshot{};
}
base::TimeDelta FocusModeController::GetSessionDuration() const {
return in_focus_session() ? current_session_->session_duration()
: session_duration_;
}
base::Time FocusModeController::GetActualEndTime() const {
if (!current_session_) {
return base::Time();
}
return in_ending_moment() ? current_session_->end_time() +
focus_mode_util::kEndingMomentDuration
: current_session_->end_time();
}
void FocusModeController::SetSelectedTask(const FocusModeTask& task) {
if (task.task_id.empty()) {
tasks_model_.ClearSelectedTask();
return;
}
tasks_model_.SetSelectedTask(task);
}
bool FocusModeController::HasSelectedTask() const {
return !!tasks_model_.selected_task();
}
void FocusModeController::CompleteTask() {
const FocusModeTask* selected_task = tasks_model_.selected_task();
if (!selected_task) {
return;
}
tasks_model_.UpdateTask(
FocusModeTasksModel::TaskUpdate::CompletedUpdate(selected_task->task_id));
}
void FocusModeController::MaybeShowEndingMomentNudge() {
// Do not show the nudge if there is a persistent tray bubble open during the
// ending moment.
if (!in_ending_moment() || current_session_->persistent_ending()) {
return;
}
if (auto* anchored_nudge_manager = AnchoredNudgeManager::Get();
anchored_nudge_manager->IsNudgeShown(
focus_mode_util::kFocusModeEndingMomentNudgeId)) {
return;
}
ShowEndingMomentNudge(congratulatory_index_, current_session_);
}
void FocusModeController::TriggerEndingMomentImmediately() {
if (!in_focus_session()) {
return;
}
current_session_->set_end_time(base::Time::Now());
OnTimerTick();
}
const base::UnguessableToken& FocusModeController::GetMediaSessionRequestId() {
if (!test_media_request_id_.is_empty()) {
CHECK_IS_TEST();
return test_media_request_id_;
}
return focus_mode_media_view_
? focus_mode_media_view_->GetMediaSessionRequestId()
: base::UnguessableToken::Null();
}
void FocusModeController::RequestTasksUpdateForTesting() {
tasks_model_.RequestUpdate();
}
bool FocusModeController::TasksProviderHasCachedTasksForTesting() const {
return !tasks_provider_.TasksForTesting().empty(); // IN-TEST
}
media_session::mojom::MediaSessionInfoPtr
FocusModeController::GetSystemMediaSessionInfo() {
if (test_media_session_info_) {
CHECK_IS_TEST();
return std::move(test_media_session_info_);
}
return Shell::Get()->media_controller()->GetMediaSessionInfo();
}
void FocusModeController::StartFocusSession(
focus_mode_histogram_names::ToggleSource source) {
paused_by_ending_moment_ = false;
focus_mode_sounds_controller_->reset_paused_event_count();
focus_mode_metrics_recorder_ =
std::make_unique<FocusModeMetricsRecorder>(session_duration_);
const FocusModeTask* selected_task = tasks_model_.selected_task();
focus_mode_metrics_recorder_->RecordHistogramsOnStart(
source, selected_task ? selected_task->task_id : TaskId());
if (selected_task) {
focus_mode_metrics_recorder_->IncrementTasksSelectedCount();
}
const auto& selected_playlist =
focus_mode_sounds_controller_->selected_playlist();
focus_mode_metrics_recorder_->SetHasSelectedSoundType(selected_playlist);
if (!selected_playlist.empty()) {
focus_mode_sounds_controller_->SoundsStarted();
}
current_session_ = FocusModeSession(session_duration_,
session_duration_ + base::Time::Now());
SaveSettingsToUserPrefs();
// Start timer for the specified `session_duration_`. Set `current_session_`
// before `SetQuietMode` called, because we may indirectly call
// `GetActualEndTime` to create a notification.
timer_.Start(FROM_HERE, base::Seconds(1), this,
&FocusModeController::OnTimerTick, base::TimeTicks::Now());
auto* message_center = message_center::MessageCenter::Get();
CHECK(message_center);
if (turn_on_do_not_disturb_ && !message_center->IsQuietMode()) {
// Only turn on DND if it is not enabled before starting a session and
// `turn_on_do_not_disturb_` is true.
message_center->SetQuietMode(
true, message_center::QuietModeSourceType::kFocusMode);
} else if (!turn_on_do_not_disturb_ && IsQuietModeOnSetByFocusMode()) {
// This is the case where a user toggles off DND in the focus panel before
// it has been switched off by the termination of the ending moment.
message_center->SetQuietMode(
false, message_center::QuietModeSourceType::kFocusMode);
} else if (turn_on_do_not_disturb_ && IsQuietModeOnSetByFocusMode()) {
// This can only happen if a new focus session is started during an ending
// moment. If the DND state is preserved (i.e. `turn_on_do_not_disturb_` is
// still true), then just update the notification.
MaybeUpdateDndNotification();
}
CloseSystemTrayBubble();
SetFocusTrayVisibility(true);
HideEndingMomentNudge();
MaybeCreateMediaWidget();
for (auto& observer : observers_) {
observer.OnFocusModeChanged(/*in_focus_session=*/true);
}
}
void FocusModeController::OnTimerTick() {
CHECK(current_session_);
auto session_snapshot = current_session_->GetSnapshot(base::Time::Now());
switch (session_snapshot.state) {
case FocusModeSession::State::kOn:
for (auto& observer : observers_) {
observer.OnTimerTick(session_snapshot);
}
return;
case FocusModeSession::State::kEnding:
timer_.Stop();
congratulatory_index_ = base::RandInt(
/*min=*/0, /*max=*/focus_mode_util::kCongratulatoryTitleNum - 1);
if (media_widget_) {
paused_by_ending_moment_ =
focus_mode_sounds_controller_->selected_playlist().state ==
focus_mode_util::SoundState::kPlaying;
if (paused_by_ending_moment_) {
focus_mode_sounds_controller_->PausePlayback();
}
}
// Set a timer to terminate the ending moment. If the focus tray bubble is
// open, the ending moment will exist until the bubble is closed.
if (!IsFocusTrayBubbleVisible()) {
timer_.Start(FROM_HERE, focus_mode_util::kEndingMomentDuration, this,
&FocusModeController::ResetFocusSession,
base::TimeTicks::Now());
MaybeUpdateDndNotification();
} else {
current_session_->set_persistent_ending();
}
// Play sounds effect after 200ms delay.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindOnce([]() {
if (Shell::HasInstance()) {
Shell::Get()->system_sounds_delegate()->Play(
Sound::kFocusModeEndingMoment);
}
}),
kSessionEndSoundDelay);
for (auto& observer : observers_) {
observer.OnFocusModeChanged(/*in_focus_session=*/false);
}
return;
case FocusModeSession::State::kOff:
ResetFocusSession();
return;
}
}
void FocusModeController::UpdateFromUserPrefs() {
PrefService* active_user_prefs =
Shell::Get()->session_controller()->GetActivePrefService();
if (!active_user_prefs) {
// Can be null in tests.
return;
}
session_duration_ =
active_user_prefs->GetTimeDelta(prefs::kFocusModeSessionDuration);
turn_on_do_not_disturb_ =
active_user_prefs->GetBoolean(prefs::kFocusModeDoNotDisturb);
if (session_duration_ <= base::TimeDelta()) {
session_duration_ = kDefaultSessionDuration;
}
UpdateSelectedTaskFromUserPrefs();
focus_mode_sounds_controller_->UpdateFromUserPrefs();
}
void FocusModeController::UpdateSelectedTaskFromUserPrefs() {
PrefService* active_user_prefs =
Shell::Get()->session_controller()->GetActivePrefService();
if (!active_user_prefs) {
// Can be null in tests.
return;
}
// Get the selected task from the dict and also update the selected task if
// there is a task.
const auto& selected_task_dict =
active_user_prefs->GetDict(prefs::kFocusModeSelectedTask);
if (selected_task_dict.empty()) {
return;
}
TaskId pref_task = {
.list_id =
*(selected_task_dict.FindString(focus_mode_util::kTaskListIdKey)),
.id = *(selected_task_dict.FindString(focus_mode_util::kTaskIdKey))};
if (!pref_task.empty()) {
tasks_model_.SetSelectedTaskFromPrefs(pref_task);
}
}
void FocusModeController::SaveSettingsToUserPrefs() {
PrefService* active_user_prefs =
Shell::Get()->session_controller()->GetActivePrefService();
if (!active_user_prefs) {
return;
}
active_user_prefs->SetTimeDelta(prefs::kFocusModeSessionDuration,
session_duration_);
active_user_prefs->SetBoolean(prefs::kFocusModeDoNotDisturb,
turn_on_do_not_disturb_);
const auto* selected_task = tasks_model_.selected_task();
SaveSelectedTaskSettingsToUserPrefs(
selected_task ? std::make_optional<FocusModeTask>(*selected_task)
: std::nullopt);
}
void FocusModeController::SaveSelectedTaskSettingsToUserPrefs(
const std::optional<FocusModeTask>& task) {
if (PrefService* active_user_prefs =
Shell::Get()->session_controller()->GetActivePrefService()) {
base::Value::Dict selected_task_dict;
// If there is a selected task, we will save its `task_id.list_id` and
// `task_id.id`; otherwise, we will store an empty dict.
if (task) {
selected_task_dict.Set(focus_mode_util::kTaskListIdKey,
task->task_id.list_id);
selected_task_dict.Set(focus_mode_util::kTaskIdKey, task->task_id.id);
}
active_user_prefs->SetDict(prefs::kFocusModeSelectedTask,
std::move(selected_task_dict));
}
}
void FocusModeController::CloseSystemTrayBubble() {
for (auto* root_window_controller : Shell::GetAllRootWindowControllers()) {
if (root_window_controller->IsSystemTrayVisible()) {
root_window_controller->GetStatusAreaWidget()
->unified_system_tray()
->CloseBubble();
}
}
}
void FocusModeController::SetFocusTrayVisibility(bool visible) {
for (auto* root_window_controller : Shell::GetAllRootWindowControllers()) {
if (auto* status_area_widget =
root_window_controller->GetStatusAreaWidget()) {
auto* tray = status_area_widget->focus_mode_tray();
if (!visible) {
tray->CloseBubble();
}
tray->SetVisiblePreferred(visible);
}
}
}
bool FocusModeController::IsFocusTrayBubbleVisible() const {
for (auto* root_window_controller : Shell::GetAllRootWindowControllers()) {
if (root_window_controller->GetStatusAreaWidget()
->focus_mode_tray()
->GetBubbleView()) {
return true;
}
}
return false;
}
bool FocusModeController::MaybeCreateMediaWidget() {
if (media_widget_ ||
focus_mode_sounds_controller_->selected_playlist().empty()) {
return false;
}
CHECK(in_focus_session());
views::Widget::InitParams params(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.name = "FocusModeMediaWidget";
params.parent = Shell::GetContainer(Shell::GetPrimaryRootWindow(),
kShellWindowId_OverlayContainer);
params.child = true;
// The media window should be hidden.
params.layer_type = ui::LAYER_NOT_DRAWN;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
// The media window does not receive any events.
params.activatable = views::Widget::InitParams::Activatable::kNo;
params.accept_events = false;
media_widget_ = std::make_unique<views::Widget>();
media_widget_->Init(std::move(params));
AshWebView::InitParams web_view_params;
web_view_params.suppress_navigation = true;
web_view_params.enable_wake_locks = false;
web_view_params.source_title =
focus_mode_util::GetSourceTitleForMediaControls(
focus_mode_sounds_controller_->selected_playlist());
focus_mode_media_view_ = media_widget_->SetContentsView(
AshWebViewFactory::Get()->Create(web_view_params));
focus_mode_media_view_->Navigate(GURL(chrome::kChromeUIFocusModeMediaURL));
return true;
}
void FocusModeController::CloseMediaWidget() {
CHECK(media_widget_);
focus_mode_media_view_.ClearAndDelete();
focus_mode_media_view_ = nullptr;
media_widget_.reset();
}
void FocusModeController::PerformActionsForMusic() {
const auto& selected_playlist =
focus_mode_sounds_controller_->selected_playlist();
// Do nothing if there is no selected playlist, or a new media widget was
// created.
if (selected_playlist.empty() || MaybeCreateMediaWidget()) {
return;
}
// If the music was paused by the user before the ending moment, we should
// keep it in paused state after extending the session; otherwise, we will
// continue to play the existing music because it was paused by the ending
// moment.
if (paused_by_ending_moment_) {
focus_mode_sounds_controller_->ResumePlayingPlayback();
}
}
void FocusModeController::OnTasksReceived(
const std::vector<FocusModeTask>& tasks) {
std::vector<FocusModeTask> copy = tasks;
tasks_model_.SetTaskList(std::move(copy));
}
} // namespace ash