blob: 2cf87f90d86509082c7af81bfbe5d739c7a4f93e [file] [log] [blame]
// Copyright 2018 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/app_list/app_list_controller_impl.h"
#include <utility>
#include <vector>
#include "ash/app_list/app_list_presenter_delegate_impl.h"
#include "ash/app_list/model/app_list_folder_item.h"
#include "ash/app_list/model/app_list_item.h"
#include "ash/app_list/views/app_list_main_view.h"
#include "ash/app_list/views/app_list_view.h"
#include "ash/app_list/views/contents_view.h"
#include "ash/public/cpp/app_list/answer_card_contents_registry.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/config.h"
#include "ash/session/session_controller.h"
#include "ash/shell.h"
#include "ash/shell_port.h"
#include "ash/wallpaper/wallpaper_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "extensions/common/constants.h"
#include "ui/display/screen.h"
namespace {
int64_t GetDisplayIdToShowAppListOn() {
return display::Screen::GetScreen()
->GetDisplayNearestWindow(ash::Shell::GetRootWindowForNewWindows())
.id();
}
} // namespace
namespace ash {
AppListControllerImpl::AppListControllerImpl()
: presenter_(std::make_unique<AppListPresenterDelegateImpl>(this)),
is_home_launcher_enabled_(app_list::features::IsHomeLauncherEnabled()) {
model_.AddObserver(this);
// Create only for non-mash. Mash uses window tree embed API to get a
// token to map answer card contents.
if (Shell::GetAshConfig() != Config::MASH_DEPRECATED) {
answer_card_contents_registry_ =
std::make_unique<app_list::AnswerCardContentsRegistry>();
}
SessionController* session_controller = Shell::Get()->session_controller();
session_controller->AddObserver(this);
// In case of crash-and-restart case where session state starts with ACTIVE
// and does not change to trigger OnSessionStateChanged(), notify the current
// session state here to ensure that the app list is shown.
OnSessionStateChanged(session_controller->GetSessionState());
Shell::Get()->tablet_mode_controller()->AddObserver(this);
Shell::Get()->wallpaper_controller()->AddObserver(this);
Shell::Get()->AddShellObserver(this);
keyboard::KeyboardController::Get()->AddObserver(this);
}
AppListControllerImpl::~AppListControllerImpl() {
keyboard::KeyboardController::Get()->RemoveObserver(this);
Shell::Get()->RemoveShellObserver(this);
Shell::Get()->wallpaper_controller()->RemoveObserver(this);
Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
Shell::Get()->session_controller()->RemoveObserver(this);
model_.RemoveObserver(this);
}
void AppListControllerImpl::SetClient(mojom::AppListClientPtr client_ptr) {
client_ = std::move(client_ptr);
}
void AppListControllerImpl::BindRequest(
mojom::AppListControllerRequest request) {
bindings_.AddBinding(this, std::move(request));
}
app_list::AppListModel* AppListControllerImpl::GetModel() {
return &model_;
}
app_list::SearchModel* AppListControllerImpl::GetSearchModel() {
return &search_model_;
}
void AppListControllerImpl::AddItem(AppListItemMetadataPtr item_data) {
const std::string folder_id = item_data->folder_id;
if (folder_id.empty()) {
model_.AddItem(CreateAppListItem(std::move(item_data)));
} else {
// When we're setting a whole model of a profile, each item may have its
// folder id set properly. However, |AppListModel::AddItemToFolder| requires
// the item to add is not in the target folder yet, and sets its folder id
// later. So we should clear the folder id here to avoid breaking checks.
item_data->folder_id.clear();
AddItemToFolder(std::move(item_data), folder_id);
}
}
void AppListControllerImpl::AddItemToFolder(AppListItemMetadataPtr item_data,
const std::string& folder_id) {
model_.AddItemToFolder(CreateAppListItem(std::move(item_data)), folder_id);
}
void AppListControllerImpl::RemoveItem(const std::string& id) {
model_.DeleteItem(id);
}
void AppListControllerImpl::RemoveUninstalledItem(const std::string& id) {
model_.DeleteUninstalledItem(id);
}
void AppListControllerImpl::MoveItemToFolder(const std::string& id,
const std::string& folder_id) {
app_list::AppListItem* item = model_.FindItem(id);
model_.MoveItemToFolder(item, folder_id);
}
void AppListControllerImpl::SetStatus(ash::AppListModelStatus status) {
model_.SetStatus(status);
}
void AppListControllerImpl::SetState(ash::AppListState state) {
model_.SetState(state);
}
void AppListControllerImpl::HighlightItemInstalledFromUI(
const std::string& id) {
model_.top_level_item_list()->HighlightItemInstalledFromUI(id);
}
void AppListControllerImpl::SetSearchEngineIsGoogle(bool is_google) {
search_model_.SetSearchEngineIsGoogle(is_google);
}
void AppListControllerImpl::SetSearchTabletAndClamshellAccessibleName(
const base::string16& tablet_accessible_name,
const base::string16& clamshell_accessible_name) {
search_model_.search_box()->SetTabletAndClamshellAccessibleName(
tablet_accessible_name, clamshell_accessible_name);
}
void AppListControllerImpl::SetSearchHintText(const base::string16& hint_text) {
search_model_.search_box()->SetHintText(hint_text);
}
void AppListControllerImpl::UpdateSearchBox(const base::string16& text,
bool initiated_by_user) {
search_model_.search_box()->Update(text, initiated_by_user);
}
void AppListControllerImpl::PublishSearchResults(
std::vector<SearchResultMetadataPtr> results) {
std::vector<std::unique_ptr<app_list::SearchResult>> new_results;
for (auto& result_metadata : results) {
std::unique_ptr<app_list::SearchResult> result =
std::make_unique<app_list::SearchResult>();
result->SetMetadata(std::move(result_metadata));
new_results.push_back(std::move(result));
}
search_model_.PublishResults(std::move(new_results));
}
void AppListControllerImpl::SetItemMetadata(const std::string& id,
AppListItemMetadataPtr data) {
app_list::AppListItem* item = model_.FindItem(id);
if (!item)
return;
// data may not contain valid position or icon. Preserve it in this case.
if (!data->position.IsValid())
data->position = item->position();
// Update the item's position and name based on the metadata.
if (!data->position.Equals(item->position()))
model_.SetItemPosition(item, data->position);
if (data->short_name.empty()) {
if (data->name != item->name()) {
model_.SetItemName(item, data->name);
}
} else {
if (data->name != item->name() || data->short_name != item->short_name()) {
model_.SetItemNameAndShortName(item, data->name, data->short_name);
}
}
// Folder icon is generated on ash side and chrome side passes a null
// icon here. Skip it.
if (data->icon.isNull())
data->icon = item->icon();
item->SetMetadata(std::move(data));
}
void AppListControllerImpl::SetItemIcon(const std::string& id,
const gfx::ImageSkia& icon) {
app_list::AppListItem* item = model_.FindItem(id);
if (item)
item->SetIcon(icon);
}
void AppListControllerImpl::SetItemIsInstalling(const std::string& id,
bool is_installing) {
app_list::AppListItem* item = model_.FindItem(id);
if (item)
item->SetIsInstalling(is_installing);
}
void AppListControllerImpl::SetItemPercentDownloaded(
const std::string& id,
int32_t percent_downloaded) {
app_list::AppListItem* item = model_.FindItem(id);
if (item)
item->SetPercentDownloaded(percent_downloaded);
}
void AppListControllerImpl::SetModelData(
std::vector<AppListItemMetadataPtr> apps,
bool is_search_engine_google) {
// Clear old model data.
model_.DeleteAllItems();
search_model_.DeleteAllResults();
// Populate new models. First populate folders and then other items to avoid
// automatically creating folder items in |AddItemToFolder|.
for (auto& app : apps) {
if (!app->is_folder)
continue;
DCHECK(app->folder_id.empty());
AddItem(std::move(app));
}
for (auto& app : apps) {
if (!app)
continue;
AddItem(std::move(app));
}
search_model_.SetSearchEngineIsGoogle(is_search_engine_google);
}
void AppListControllerImpl::SetSearchResultMetadata(
SearchResultMetadataPtr metadata) {
app_list::SearchResult* result = search_model_.FindSearchResult(metadata->id);
if (result)
result->SetMetadata(std::move(metadata));
}
void AppListControllerImpl::SetSearchResultIsInstalling(const std::string& id,
bool is_installing) {
app_list::SearchResult* result = search_model_.FindSearchResult(id);
if (result)
result->SetIsInstalling(is_installing);
}
void AppListControllerImpl::SetSearchResultPercentDownloaded(
const std::string& id,
int32_t percent_downloaded) {
app_list::SearchResult* result = search_model_.FindSearchResult(id);
if (result)
result->SetPercentDownloaded(percent_downloaded);
}
void AppListControllerImpl::NotifySearchResultItemInstalled(
const std::string& id) {
app_list::SearchResult* result = search_model_.FindSearchResult(id);
if (result)
result->NotifyItemInstalled();
}
void AppListControllerImpl::GetIdToAppListIndexMap(
GetIdToAppListIndexMapCallback callback) {
base::flat_map<std::string, uint16_t> id_to_app_list_index;
for (size_t i = 0; i < model_.top_level_item_list()->item_count(); ++i)
id_to_app_list_index[model_.top_level_item_list()->item_at(i)->id()] = i;
std::move(callback).Run(id_to_app_list_index);
}
void AppListControllerImpl::FindOrCreateOemFolder(
const std::string& oem_folder_name,
const syncer::StringOrdinal& preferred_oem_position,
FindOrCreateOemFolderCallback callback) {
app_list::AppListFolderItem* oem_folder = model_.FindFolderItem(kOemFolderId);
if (!oem_folder) {
std::unique_ptr<app_list::AppListFolderItem> new_folder =
std::make_unique<app_list::AppListFolderItem>(kOemFolderId);
syncer::StringOrdinal oem_position = preferred_oem_position.IsValid()
? preferred_oem_position
: GetOemFolderPos();
// Do not create a sync item for the OEM folder here, do it in
// ResolveFolderPositions() when the item position is finalized.
oem_folder = static_cast<app_list::AppListFolderItem*>(
model_.AddItem(std::move(new_folder)));
model_.SetItemPosition(oem_folder, oem_position);
}
model_.SetItemName(oem_folder, oem_folder_name);
std::move(callback).Run(oem_folder->CloneMetadata());
}
void AppListControllerImpl::ResolveOemFolderPosition(
const syncer::StringOrdinal& preferred_oem_position,
ResolveOemFolderPositionCallback callback) {
// In ash:
app_list::AppListFolderItem* ash_oem_folder = FindFolderItem(kOemFolderId);
ash::mojom::AppListItemMetadataPtr metadata = nullptr;
if (ash_oem_folder) {
const syncer::StringOrdinal& oem_folder_pos =
preferred_oem_position.IsValid() ? preferred_oem_position
: GetOemFolderPos();
model_.SetItemPosition(ash_oem_folder, oem_folder_pos);
metadata = ash_oem_folder->CloneMetadata();
}
std::move(callback).Run(std::move(metadata));
}
void AppListControllerImpl::DismissAppList() {
presenter_.Dismiss(base::TimeTicks());
}
void AppListControllerImpl::GetAppInfoDialogBounds(
GetAppInfoDialogBoundsCallback callback) {
app_list::AppListView* app_list_view = presenter_.GetView();
gfx::Rect bounds = gfx::Rect();
if (app_list_view)
bounds = app_list_view->GetAppInfoDialogBounds();
std::move(callback).Run(bounds);
}
void AppListControllerImpl::ShowAppListAndSwitchToState(
ash::AppListState state) {
bool app_list_was_open = true;
app_list::AppListView* app_list_view = presenter_.GetView();
if (!app_list_view) {
// TODO(calamity): This may cause the app list to show briefly before the
// state change. If this becomes an issue, add the ability to ash::Shell to
// load the app list without showing it.
presenter_.Show(GetDisplayIdToShowAppListOn(), base::TimeTicks());
app_list_was_open = false;
app_list_view = presenter_.GetView();
DCHECK(app_list_view);
}
if (state == ash::AppListState::kInvalidState)
return;
app_list::ContentsView* contents_view =
app_list_view->app_list_main_view()->contents_view();
contents_view->SetActiveState(state, app_list_was_open /* animate */);
}
void AppListControllerImpl::ShowAppList() {
presenter_.Show(GetDisplayIdToShowAppListOn(), base::TimeTicks());
}
////////////////////////////////////////////////////////////////////////////////
// app_list::AppListModelObserver:
void AppListControllerImpl::OnAppListItemAdded(app_list::AppListItem* item) {
if (item->is_folder())
client_->OnFolderCreated(item->CloneMetadata());
else if (item->is_page_break())
client_->OnPageBreakItemAdded(item->id(), item->position());
}
void AppListControllerImpl::OnSessionStateChanged(
session_manager::SessionState state) {
if (!IsHomeLauncherEnabledInTabletMode() ||
!display::Display::HasInternalDisplay() ||
state != session_manager::SessionState::ACTIVE) {
return;
}
// Show the app list after signing in in tablet mode.
Show(display::Display::InternalDisplayId(),
app_list::AppListShowSource::kTabletMode, base::TimeTicks());
}
void AppListControllerImpl::OnAppListItemWillBeDeleted(
app_list::AppListItem* item) {
if (client_ && item->is_folder())
client_->OnFolderDeleted(item->CloneMetadata());
}
void AppListControllerImpl::OnAppListItemUpdated(app_list::AppListItem* item) {
if (client_)
client_->OnItemUpdated(item->CloneMetadata());
}
////////////////////////////////////////////////////////////////////////////////
// Methods used in Ash
bool AppListControllerImpl::GetTargetVisibility() const {
return presenter_.GetTargetVisibility();
}
bool AppListControllerImpl::IsVisible() const {
return presenter_.IsVisible();
}
void AppListControllerImpl::Show(int64_t display_id,
app_list::AppListShowSource show_source,
base::TimeTicks event_time_stamp) {
UMA_HISTOGRAM_ENUMERATION(app_list::kAppListToggleMethodHistogram,
show_source, app_list::kMaxAppListToggleMethod);
presenter_.Show(display_id, event_time_stamp);
}
void AppListControllerImpl::UpdateYPositionAndOpacity(
int y_position_in_screen,
float background_opacity) {
presenter_.UpdateYPositionAndOpacity(y_position_in_screen,
background_opacity);
}
void AppListControllerImpl::EndDragFromShelf(
app_list::AppListViewState app_list_state) {
presenter_.EndDragFromShelf(app_list_state);
}
void AppListControllerImpl::ProcessMouseWheelEvent(
const ui::MouseWheelEvent& event) {
presenter_.ProcessMouseWheelOffset(event.offset().y());
}
void AppListControllerImpl::ToggleAppList(
int64_t display_id,
app_list::AppListShowSource show_source,
base::TimeTicks event_time_stamp) {
if (!IsVisible()) {
UMA_HISTOGRAM_ENUMERATION(app_list::kAppListToggleMethodHistogram,
show_source, app_list::kMaxAppListToggleMethod);
}
presenter_.ToggleAppList(display_id, event_time_stamp);
}
app_list::AppListViewState AppListControllerImpl::GetAppListViewState() {
return model_.state_fullscreen();
}
void AppListControllerImpl::FlushForTesting() {
bindings_.FlushForTesting();
}
void AppListControllerImpl::OnOverviewModeStarting() {
in_overview_mode_ = true;
if (!IsHomeLauncherEnabledInTabletMode()) {
DismissAppList();
return;
}
UpdateHomeLauncherVisibility();
}
void AppListControllerImpl::OnOverviewModeEnding() {
in_overview_mode_ = false;
UpdateHomeLauncherVisibility();
}
void AppListControllerImpl::OnTabletModeStarted() {
if (IsVisible()) {
presenter_.GetView()->OnTabletModeChanged(true);
return;
}
if (!is_home_launcher_enabled_ || !display::Display::HasInternalDisplay() ||
(Shell::Get()->session_controller() &&
Shell::Get()->session_controller()->login_status() !=
LoginStatus::USER)) {
return;
}
// Show the app list if the tablet mode starts.
Show(display::Display::InternalDisplayId(), app_list::kTabletMode,
base::TimeTicks());
UpdateHomeLauncherVisibility();
}
void AppListControllerImpl::OnTabletModeEnded() {
if (IsVisible())
presenter_.GetView()->OnTabletModeChanged(false);
if (!is_home_launcher_enabled_)
return;
// Dismiss the app list if the tablet mode ends.
DismissAppList();
}
void AppListControllerImpl::OnWallpaperColorsChanged() {
if (IsVisible())
presenter_.GetView()->OnWallpaperColorsChanged();
}
void AppListControllerImpl::OnKeyboardVisibilityStateChanged(
const bool is_visible) {
onscreen_keyboard_shown_ = is_visible;
app_list::AppListView* app_list_view = presenter_.GetView();
if (app_list_view)
app_list_view->OnScreenKeyboardShown(is_visible);
}
void AppListControllerImpl::OnWallpaperPreviewStarted() {
in_wallpaper_preview_ = true;
UpdateHomeLauncherVisibility();
}
void AppListControllerImpl::OnWallpaperPreviewEnded() {
in_wallpaper_preview_ = false;
UpdateHomeLauncherVisibility();
}
bool AppListControllerImpl::IsHomeLauncherEnabledInTabletMode() const {
return is_home_launcher_enabled_ && Shell::Get()
->tablet_mode_controller()
->IsTabletModeWindowManagerEnabled();
}
////////////////////////////////////////////////////////////////////////////////
// Methods of |client_|:
void AppListControllerImpl::StartSearch(const base::string16& raw_query) {
last_raw_query_ = raw_query;
if (client_) {
base::string16 query;
base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
client_->StartSearch(query);
}
}
void AppListControllerImpl::OpenSearchResult(const std::string& result_id,
int event_flags) {
app_list::SearchResult* result = search_model_.FindSearchResult(result_id);
if (!result)
return;
UMA_HISTOGRAM_ENUMERATION(app_list::kSearchResultOpenDisplayTypeHistogram,
result->display_type(),
ash::SearchResultDisplayType::kLast);
// Record the search metric if the SearchResult is not a suggested app.
if (result->display_type() != ash::SearchResultDisplayType::kRecommendation) {
// Count AppList.Search here because it is composed of search + action.
base::RecordAction(base::UserMetricsAction("AppList_OpenSearchResult"));
UMA_HISTOGRAM_COUNTS_100(app_list::kSearchQueryLength,
last_raw_query_.size());
if (result->distance_from_origin() >= 0) {
UMA_HISTOGRAM_COUNTS_100(app_list::kSearchResultDistanceFromOrigin,
result->distance_from_origin());
}
}
if (client_)
client_->OpenSearchResult(result_id, event_flags);
}
void AppListControllerImpl::InvokeSearchResultAction(
const std::string& result_id,
int action_index,
int event_flags) {
if (client_)
client_->InvokeSearchResultAction(result_id, action_index, event_flags);
}
void AppListControllerImpl::GetSearchResultContextMenuModel(
const std::string& result_id,
GetContextMenuModelCallback callback) {
if (client_)
client_->GetSearchResultContextMenuModel(result_id, std::move(callback));
}
void AppListControllerImpl::SearchResultContextMenuItemSelected(
const std::string& result_id,
int command_id,
int event_flags) {
if (client_) {
client_->SearchResultContextMenuItemSelected(result_id, command_id,
event_flags);
}
}
void AppListControllerImpl::ViewShown(int64_t display_id) {
if (client_)
client_->ViewShown(display_id);
}
void AppListControllerImpl::ViewClosing() {
if (client_)
client_->ViewClosing();
}
void AppListControllerImpl::GetWallpaperProminentColors(
GetWallpaperProminentColorsCallback callback) {
Shell::Get()->wallpaper_controller()->GetWallpaperColors(std::move(callback));
}
void AppListControllerImpl::ActivateItem(const std::string& id,
int event_flags) {
if (client_)
client_->ActivateItem(id, event_flags);
}
void AppListControllerImpl::GetContextMenuModel(
const std::string& id,
GetContextMenuModelCallback callback) {
if (client_)
client_->GetContextMenuModel(id, std::move(callback));
}
void AppListControllerImpl::ContextMenuItemSelected(const std::string& id,
int command_id,
int event_flags) {
if (client_)
client_->ContextMenuItemSelected(id, command_id, event_flags);
}
void AppListControllerImpl::ShowWallpaperContextMenu(
const gfx::Point& onscreen_location,
ui::MenuSourceType source_type) {
ShellPort::Get()->ShowContextMenu(onscreen_location, source_type);
}
void AppListControllerImpl::OnVisibilityChanged(bool visible) {
if (client_)
client_->OnAppListVisibilityChanged(visible);
}
void AppListControllerImpl::OnTargetVisibilityChanged(bool visible) {
if (client_)
client_->OnAppListTargetVisibilityChanged(visible);
}
void AppListControllerImpl::StartVoiceInteractionSession() {
if (client_)
client_->StartVoiceInteractionSession();
}
void AppListControllerImpl::ToggleVoiceInteractionSession() {
if (client_)
client_->ToggleVoiceInteractionSession();
}
////////////////////////////////////////////////////////////////////////////////
// Private used only:
syncer::StringOrdinal AppListControllerImpl::GetOemFolderPos() {
// Place the OEM folder just after the web store, which should always be
// followed by a pre-installed app (e.g. Search), so the poosition should be
// stable. TODO(stevenjb): consider explicitly setting the OEM folder location
// along with the name in ServicesCustomizationDocument::SetOemFolderName().
app_list::AppListItemList* item_list = model_.top_level_item_list();
if (!item_list->item_count()) {
LOG(ERROR) << "No top level item was found. "
<< "Placing OEM folder at the beginning.";
return syncer::StringOrdinal::CreateInitialOrdinal();
}
size_t web_store_app_index;
if (!item_list->FindItemIndex(extensions::kWebStoreAppId,
&web_store_app_index)) {
LOG(ERROR) << "Web store position is not found it top items. "
<< "Placing OEM folder at the end.";
return item_list->item_at(item_list->item_count() - 1)
->position()
.CreateAfter();
}
// Skip items with the same position.
const app_list::AppListItem* web_store_app_item =
item_list->item_at(web_store_app_index);
for (size_t j = web_store_app_index + 1; j < item_list->item_count(); ++j) {
const app_list::AppListItem* next_item = item_list->item_at(j);
DCHECK(next_item->position().IsValid());
if (!next_item->position().Equals(web_store_app_item->position())) {
const syncer::StringOrdinal oem_ordinal =
web_store_app_item->position().CreateBetween(next_item->position());
VLOG(1) << "Placing OEM Folder at: " << j
<< " position: " << oem_ordinal.ToDebugString();
return oem_ordinal;
}
}
const syncer::StringOrdinal oem_ordinal =
web_store_app_item->position().CreateAfter();
VLOG(1) << "Placing OEM Folder at: " << item_list->item_count()
<< " position: " << oem_ordinal.ToDebugString();
return oem_ordinal;
}
std::unique_ptr<app_list::AppListItem> AppListControllerImpl::CreateAppListItem(
AppListItemMetadataPtr metadata) {
std::unique_ptr<app_list::AppListItem> app_list_item =
metadata->is_folder
? std::make_unique<app_list::AppListFolderItem>(metadata->id)
: std::make_unique<app_list::AppListItem>(metadata->id);
app_list_item->SetMetadata(std::move(metadata));
return app_list_item;
}
app_list::AppListFolderItem* AppListControllerImpl::FindFolderItem(
const std::string& folder_id) {
return model_.FindFolderItem(folder_id);
}
void AppListControllerImpl::UpdateHomeLauncherVisibility() {
if (!IsHomeLauncherEnabledInTabletMode() || !presenter_.GetWindow())
return;
if (in_overview_mode_ || in_wallpaper_preview_)
presenter_.GetWindow()->Hide();
else
presenter_.GetWindow()->Show();
}
} // namespace ash