blob: 6337eb616c538818bbecdd290f2b05c23757a258 [file] [log] [blame]
// Copyright 2021 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/wm/desks/templates/desks_templates_presenter.h"
#include "ash/public/cpp/desk_template.h"
#include "ash/public/cpp/desks_templates_delegate.h"
#include "ash/public/cpp/toast_data.h"
#include "ash/public/cpp/toast_manager.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/wm/desks/desks_bar_view.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/expanded_desks_bar_button.h"
#include "ash/wm/desks/templates/desks_templates_grid_view.h"
#include "ash/wm/desks/templates/desks_templates_metrics_util.h"
#include "ash/wm/desks/zero_state_button.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_session.h"
#include "base/bind.h"
#include "base/i18n/number_formatting.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
namespace {
DesksTemplatesPresenter* g_instance = nullptr;
// Toast name.
constexpr char kMaximumDeskLaunchTemplateToastName[] =
"MaximumDeskLaunchTemplateToast";
// Helper to get the desk model from the shell delegate. Should always return a
// usable desk model, either from chrome sync, or a local storage.
desks_storage::DeskModel* GetDeskModel() {
auto* desk_model = Shell::Get()->desks_templates_delegate()->GetDeskModel();
DCHECK(desk_model);
return desk_model;
}
// Callback ran after creating and activating a new desk for launching a
// template. Launches apps into the active desk.
void OnNewDeskCreatedForTemplate(std::unique_ptr<DeskTemplate> desk_template,
bool on_create_activate_success) {
if (!on_create_activate_success)
return;
Shell::Get()->desks_templates_delegate()->LaunchAppsFromTemplate(
std::move(desk_template));
}
} // namespace
DesksTemplatesPresenter::DesksTemplatesPresenter(
OverviewSession* overview_session)
: overview_session_(overview_session) {
DCHECK(overview_session_);
DCHECK_EQ(g_instance, nullptr);
g_instance = this;
auto* desk_model = GetDeskModel();
desk_model_observation_.Observe(desk_model);
GetAllEntries();
}
DesksTemplatesPresenter::~DesksTemplatesPresenter() {
DCHECK_EQ(g_instance, this);
g_instance = nullptr;
}
// static
DesksTemplatesPresenter* DesksTemplatesPresenter::Get() {
DCHECK(g_instance);
return g_instance;
}
size_t DesksTemplatesPresenter::GetEntryCount() const {
return GetDeskModel()->GetEntryCount();
}
size_t DesksTemplatesPresenter::GetMaxEntryCount() const {
return GetDeskModel()->GetMaxEntryCount();
}
void DesksTemplatesPresenter::UpdateDesksTemplatesUI() {
// The save as desk template button is hidden in tablet mode. The desks
// templates button on the desk bar view and the desks templates grid are
// hidden in tablet mode and if there no templates to view.
const bool in_tablet_mode =
Shell::Get()->tablet_mode_controller()->InTabletMode();
should_show_templates_ui_ = !in_tablet_mode && GetEntryCount() > 0u;
for (auto& overview_grid : overview_session_->grid_list()) {
if (!should_show_templates_ui_ &&
overview_grid->IsShowingDesksTemplatesGrid()) {
// When deleting, it is possible to delete the last template. In this
// case, close the template grid and go back to overview.
overview_grid->HideDesksTemplatesGrid(/*exit_overview=*/false);
continue;
}
if (DesksBarView* desks_bar_view =
const_cast<DesksBarView*>(overview_grid->desks_bar_view())) {
// When templates is enabled but templates haven't loaded, the templates
// button may be visible but have a size of 0x0 so we have to make a
// Layout() call here.
desks_bar_view->UpdateDesksTemplatesButtonVisibility();
desks_bar_view->UpdateButtonsForDesksTemplatesGrid();
desks_bar_view->Layout();
overview_grid->UpdateSaveDeskAsTemplateButton();
}
}
}
void DesksTemplatesPresenter::GetAllEntries() {
weak_ptr_factory_.InvalidateWeakPtrs();
GetDeskModel()->GetAllEntries(
base::BindOnce(&DesksTemplatesPresenter::OnGetAllEntries,
weak_ptr_factory_.GetWeakPtr()));
}
void DesksTemplatesPresenter::DeleteEntry(const std::string& template_uuid) {
weak_ptr_factory_.InvalidateWeakPtrs();
GetDeskModel()->DeleteEntry(
template_uuid, base::BindOnce(&DesksTemplatesPresenter::OnDeleteEntry,
weak_ptr_factory_.GetWeakPtr()));
}
void DesksTemplatesPresenter::LaunchDeskTemplate(
const std::string& template_uuid) {
// If we are at the max desk limit (currently is 8), a new desk
// cannot be created, and a toast will be displayed to the user.
if (!DesksController::Get()->CanCreateDesks()) {
ToastData toast_data = {
/*id=*/kMaximumDeskLaunchTemplateToastName,
/*text=*/
l10n_util::GetStringFUTF16(
IDS_ASH_DESKS_TEMPLATES_REACH_MAXIMUM_DESK_TOAST,
base::FormatNumber(desks_util::kMaxNumberOfDesks))};
ToastManager::Get()->Show(toast_data);
return;
}
weak_ptr_factory_.InvalidateWeakPtrs();
GetDeskModel()->GetEntryByUUID(
template_uuid,
base::BindOnce(&DesksTemplatesPresenter::OnGetTemplateForDeskLaunch,
weak_ptr_factory_.GetWeakPtr()));
}
void DesksTemplatesPresenter::MaybeSaveActiveDeskAsTemplate() {
DesksController::Get()->CaptureActiveDeskAsTemplate(
base::BindOnce(&DesksTemplatesPresenter::SaveOrUpdateDeskTemplate,
weak_ptr_factory_.GetWeakPtr(),
/*is_update=*/false));
}
void DesksTemplatesPresenter::SaveOrUpdateDeskTemplate(
bool is_update,
std::unique_ptr<DeskTemplate> desk_template) {
if (!desk_template)
return;
auto desk_template_clone = desk_template->Clone();
weak_ptr_factory_.InvalidateWeakPtrs();
if (!is_update)
RecordWindowAndTabCountHistogram(desk_template.get());
// Save or update `desk_template_clone` as an entry in DeskModel.
GetDeskModel()->AddOrUpdateEntry(
std::move(desk_template_clone),
base::BindOnce(&DesksTemplatesPresenter::OnAddOrUpdateEntry,
weak_ptr_factory_.GetWeakPtr(), is_update));
}
void DesksTemplatesPresenter::OnDeskModelDestroying() {
desk_model_observation_.Reset();
}
void DesksTemplatesPresenter::EntriesAddedOrUpdatedRemotely(
const std::vector<const DeskTemplate*>& new_entries) {
if (overview_session_->IsShowingDesksTemplatesGrid())
GetAllEntries();
}
void DesksTemplatesPresenter::EntriesRemovedRemotely(
const std::vector<std::string>& uuids) {
if (overview_session_->IsShowingDesksTemplatesGrid())
GetAllEntries();
}
void DesksTemplatesPresenter::OnGetAllEntries(
desks_storage::DeskModel::GetAllEntriesStatus status,
const std::vector<DeskTemplate*>& entries) {
if (status != desks_storage::DeskModel::GetAllEntriesStatus::kOk)
return;
DCHECK_EQ(GetEntryCount(), entries.size());
// This updates `should_show_templates_ui_`.
UpdateDesksTemplatesUI();
for (auto& overview_grid : overview_session_->grid_list()) {
// Populate `DesksTemplatesGridView` with the desk template entries.
if (views::Widget* grid_widget =
overview_grid->desks_templates_grid_widget()) {
static_cast<DesksTemplatesGridView*>(grid_widget->GetContentsView())
->UpdateGridUI(entries, overview_grid->GetGridEffectiveBounds());
}
}
if (on_update_ui_closure_for_testing_)
std::move(on_update_ui_closure_for_testing_).Run();
}
void DesksTemplatesPresenter::OnDeleteEntry(
desks_storage::DeskModel::DeleteEntryStatus status) {
if (status != desks_storage::DeskModel::DeleteEntryStatus::kOk)
return;
RecordDeleteTemplateHistogram();
RecordUserTemplateCountHistogram(GetEntryCount(), GetMaxEntryCount());
GetAllEntries();
}
void DesksTemplatesPresenter::OnGetTemplateForDeskLaunch(
desks_storage::DeskModel::GetEntryByUuidStatus status,
std::unique_ptr<DeskTemplate> entry) {
if (status != desks_storage::DeskModel::GetEntryByUuidStatus::kOk)
return;
// `CreateAndActivateNewDeskForTemplate` may destroy `this`. Copy the member
// variables to a local to prevent UAF. See https://crbug.com/1284138.
base::OnceClosure on_update_ui_closure_for_testing =
std::move(on_update_ui_closure_for_testing_);
// Launch the windows as specified in the template to a new desk.
// Calling `CreateAndActivateNewDeskForTemplate` results in exiting overview
// mode, which means the presenter doesn't exist anymore on callback (since it
// is owned by `OverviewSession`). Because of this, we bind a non-member
// function in the anonymous namespace.
const auto template_name = entry->template_name();
DesksController::Get()->CreateAndActivateNewDeskForTemplate(
template_name,
base::BindOnce(&OnNewDeskCreatedForTemplate, std::move(entry)));
if (on_update_ui_closure_for_testing)
std::move(on_update_ui_closure_for_testing).Run();
RecordLaunchTemplateHistogram();
}
void DesksTemplatesPresenter::OnAddOrUpdateEntry(
bool was_update,
desks_storage::DeskModel::AddOrUpdateEntryStatus status) {
// TODO(crbug.com/1284449): Add visible cue when failing to save a desk
// template.
RecordAddOrUpdateTemplateStatusHistogram(status);
if (status != desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk)
return;
// If the templates grid is already shown, just update the entries.
if (overview_session_->IsShowingDesksTemplatesGrid()) {
GetAllEntries();
return;
}
// Update the button here in case it has been disabled.
const auto& grid_list = overview_session_->grid_list();
DCHECK(!grid_list.empty());
overview_session_->ShowDesksTemplatesGrids(
grid_list.front()->desks_bar_view()->IsZeroState());
for (auto& overview_grid : grid_list)
overview_grid->UpdateSaveDeskAsTemplateButton();
if (!was_update) {
RecordNewTemplateHistogram();
RecordUserTemplateCountHistogram(GetEntryCount(), GetMaxEntryCount());
}
}
} // namespace ash