blob: 4c91cff811cb6802fe05df5a8e0cc6bf1a5e020b [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/ui/app_list/chrome_app_list_model_updater.h"
#include <unordered_map>
#include <utility>
#include "ash/public/cpp/app_list/app_list_config.h"
#include "ash/public/cpp/app_list/app_list_controller.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/app_list/app_list_client_impl.h"
#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ui/app_list/chrome_app_list_item.h"
#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
#include "extensions/common/constants.h"
#include "ui/base/models/menu_model.h"
ChromeAppListModelUpdater::ChromeAppListModelUpdater(Profile* profile)
: profile_(profile), weak_ptr_factory_(this) {}
ChromeAppListModelUpdater::~ChromeAppListModelUpdater() = default;
void ChromeAppListModelUpdater::SetActive(bool active) {
const bool was_active = !!app_list_controller_;
if (was_active == active)
return;
app_list_controller_ =
active ? AppListClientImpl::GetInstance()->GetAppListController()
: nullptr;
if (!app_list_controller_)
return;
// Activating this model updater should sync the cached model to Ash.
std::vector<std::unique_ptr<ash::AppListItemMetadata>> items_to_sync;
for (auto const& item : items_)
items_to_sync.push_back(item.second->CloneMetadata());
DCHECK(profile_);
app_list_controller_->SetModelData(model_id(), std::move(items_to_sync),
search_engine_is_google_);
}
void ChromeAppListModelUpdater::AddItem(
std::unique_ptr<ChromeAppListItem> app_item) {
std::unique_ptr<ash::AppListItemMetadata> item_data =
app_item->CloneMetadata();
// Add to Chrome first leave all updates to observer methods.
AddChromeItem(std::move(app_item));
if (app_list_controller_)
app_list_controller_->AddItem(std::move(item_data));
}
void ChromeAppListModelUpdater::AddItemToFolder(
std::unique_ptr<ChromeAppListItem> app_item,
const std::string& folder_id) {
std::unique_ptr<ash::AppListItemMetadata> item_data =
app_item->CloneMetadata();
// Add to Chrome first leave all updates to observer methods.
ChromeAppListItem* item_added = AddChromeItem(std::move(app_item));
item_added->SetChromeFolderId(folder_id);
if (app_list_controller_) {
app_list_controller_->AddItemToFolder(std::move(item_data), folder_id);
// Set the item's default icon if it has one.
if (!item_added->icon().isNull())
app_list_controller_->SetItemIcon(item_added->id(), item_added->icon());
}
}
void ChromeAppListModelUpdater::RemoveItem(const std::string& id) {
if (app_list_controller_)
app_list_controller_->RemoveItem(id);
RemoveChromeItem(id);
}
void ChromeAppListModelUpdater::RemoveUninstalledItem(const std::string& id) {
if (app_list_controller_)
app_list_controller_->RemoveUninstalledItem(id);
RemoveChromeItem(id);
}
void ChromeAppListModelUpdater::MoveItemToFolder(const std::string& id,
const std::string& folder_id) {
if (app_list_controller_)
app_list_controller_->MoveItemToFolder(id, folder_id);
else
MoveChromeItemToFolder(id, folder_id);
}
void ChromeAppListModelUpdater::SetStatus(ash::AppListModelStatus status) {
if (!app_list_controller_)
return;
app_list_controller_->SetStatus(status);
}
void ChromeAppListModelUpdater::SetState(ash::AppListState state) {
if (!app_list_controller_)
return;
app_list_controller_->SetState(state);
}
void ChromeAppListModelUpdater::HighlightItemInstalledFromUI(
const std::string& id) {
if (!app_list_controller_)
return;
app_list_controller_->HighlightItemInstalledFromUI(id);
}
void ChromeAppListModelUpdater::SetSearchEngineIsGoogle(bool is_google) {
search_engine_is_google_ = is_google;
if (app_list_controller_)
app_list_controller_->SetSearchEngineIsGoogle(is_google);
}
void ChromeAppListModelUpdater::SetSearchTabletAndClamshellAccessibleName(
const base::string16& tablet_accessible_name,
const base::string16& clamshell_accessible_name) {
if (!app_list_controller_)
return;
app_list_controller_->SetSearchTabletAndClamshellAccessibleName(
tablet_accessible_name, clamshell_accessible_name);
}
void ChromeAppListModelUpdater::SetSearchHintText(
const base::string16& hint_text) {
if (!app_list_controller_)
return;
app_list_controller_->SetSearchHintText(hint_text);
}
void ChromeAppListModelUpdater::UpdateSearchBox(const base::string16& text,
bool initiated_by_user) {
if (!app_list_controller_)
return;
app_list_controller_->UpdateSearchBox(text, initiated_by_user);
}
void ChromeAppListModelUpdater::PublishSearchResults(
const std::vector<ChromeSearchResult*>& results) {
for (auto* const result : results)
result->set_model_updater(this);
if (!app_list_controller_)
return;
std::vector<ash::mojom::SearchResultMetadataPtr> result_data;
for (auto* result : results)
result_data.push_back(result->CloneMetadata());
app_list_controller_->PublishSearchResults(std::move(result_data));
}
void ChromeAppListModelUpdater::ActivateChromeItem(const std::string& id,
int event_flags) {
ChromeAppListItem* item = FindItem(id);
if (!item)
return;
DCHECK(!item->is_folder());
item->PerformActivate(event_flags);
}
////////////////////////////////////////////////////////////////////////////////
// Methods for updating Chrome items that never talk to ash.
ChromeAppListItem* ChromeAppListModelUpdater::AddChromeItem(
std::unique_ptr<ChromeAppListItem> app_item) {
ChromeAppListItem* item = app_item.get();
items_[app_item->id()] = std::move(app_item);
return item;
}
void ChromeAppListModelUpdater::RemoveChromeItem(const std::string& id) {
items_.erase(id);
}
void ChromeAppListModelUpdater::MoveChromeItemToFolder(
const std::string& id,
const std::string& folder_id) {
ChromeAppListItem* item = FindItem(id);
if (!item)
return;
item->SetChromeFolderId(folder_id);
}
////////////////////////////////////////////////////////////////////////////////
// Methods only used by ChromeAppListItem that talk to ash directly.
void ChromeAppListModelUpdater::SetItemIcon(const std::string& id,
const gfx::ImageSkia& icon) {
if (!app_list_controller_)
return;
app_list_controller_->SetItemIcon(id, icon);
}
void ChromeAppListModelUpdater::SetItemName(const std::string& id,
const std::string& name) {
if (!app_list_controller_)
return;
ChromeAppListItem* item = FindItem(id);
if (!item)
return;
std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata();
data->name = name;
app_list_controller_->SetItemMetadata(id, std::move(data));
}
void ChromeAppListModelUpdater::SetItemNameAndShortName(
const std::string& id,
const std::string& name,
const std::string& short_name) {
if (!app_list_controller_)
return;
ChromeAppListItem* item = FindItem(id);
if (!item)
return;
std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata();
data->name = name;
data->short_name = short_name;
app_list_controller_->SetItemMetadata(id, std::move(data));
}
void ChromeAppListModelUpdater::SetItemPosition(
const std::string& id,
const syncer::StringOrdinal& new_position) {
if (!app_list_controller_)
return;
ChromeAppListItem* item = FindItem(id);
if (!item)
return;
std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata();
data->position = new_position;
app_list_controller_->SetItemMetadata(id, std::move(data));
}
void ChromeAppListModelUpdater::SetItemIsPersistent(const std::string& id,
bool is_persistent) {
if (!app_list_controller_)
return;
ChromeAppListItem* item = FindItem(id);
if (!item)
return;
std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata();
data->is_persistent = is_persistent;
app_list_controller_->SetItemMetadata(id, std::move(data));
}
void ChromeAppListModelUpdater::SetItemFolderId(const std::string& id,
const std::string& folder_id) {
if (!app_list_controller_)
return;
ChromeAppListItem* item = FindItem(id);
if (!item)
return;
std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata();
data->folder_id = folder_id;
app_list_controller_->SetItemMetadata(id, std::move(data));
}
void ChromeAppListModelUpdater::SetItemIsInstalling(const std::string& id,
bool is_installing) {
if (!app_list_controller_)
return;
app_list_controller_->SetItemIsInstalling(id, is_installing);
}
void ChromeAppListModelUpdater::SetItemPercentDownloaded(
const std::string& id,
int32_t percent_downloaded) {
if (!app_list_controller_)
return;
app_list_controller_->SetItemPercentDownloaded(id, percent_downloaded);
}
////////////////////////////////////////////////////////////////////////////////
// Methods only used by ChromeSearchResult that talk to ash directly.
void ChromeAppListModelUpdater::SetSearchResultMetadata(
const std::string& id,
ash::mojom::SearchResultMetadataPtr metadata) {
if (!app_list_controller_)
return;
app_list_controller_->SetSearchResultMetadata(std::move(metadata));
}
void ChromeAppListModelUpdater::SetSearchResultIsInstalling(
const std::string& id,
bool is_installing) {
if (!app_list_controller_)
return;
app_list_controller_->SetSearchResultIsInstalling(id, is_installing);
}
void ChromeAppListModelUpdater::SetSearchResultPercentDownloaded(
const std::string& id,
int percent_downloaded) {
if (!app_list_controller_)
return;
app_list_controller_->SetSearchResultPercentDownloaded(id,
percent_downloaded);
}
void ChromeAppListModelUpdater::NotifySearchResultItemInstalled(
const std::string& id) {
if (!app_list_controller_)
return;
app_list_controller_->NotifySearchResultItemInstalled(id);
}
////////////////////////////////////////////////////////////////////////////////
// Methods for item querying
ChromeAppListItem* ChromeAppListModelUpdater::FindItem(const std::string& id) {
return items_.count(id) ? items_[id].get() : nullptr;
}
size_t ChromeAppListModelUpdater::ItemCount() {
return items_.size();
}
ChromeAppListItem* ChromeAppListModelUpdater::ItemAtForTest(size_t index) {
DCHECK_LT(index, items_.size());
DCHECK_LE(0u, index);
auto it = items_.begin();
for (size_t i = 0; i < index; ++i)
++it;
return it->second.get();
}
ChromeAppListItem* ChromeAppListModelUpdater::FindFolderItem(
const std::string& folder_id) {
ChromeAppListItem* item = FindItem(folder_id);
return (item && item->is_folder()) ? item : nullptr;
}
bool ChromeAppListModelUpdater::FindItemIndexForTest(const std::string& id,
size_t* index) {
*index = 0;
for (auto it = items_.begin(); it != items_.end(); ++it) {
if (it->second->id() == id)
return true;
++(*index);
}
return false;
}
bool ChromeAppListModelUpdater::SearchEngineIsGoogle() {
return search_engine_is_google_;
}
void ChromeAppListModelUpdater::GetIdToAppListIndexMap(
GetIdToAppListIndexMapCallback callback) {
if (!app_list_controller_)
return;
app_list_controller_->GetIdToAppListIndexMap(base::BindOnce(
[](GetIdToAppListIndexMapCallback callback,
const base::flat_map<std::string, uint16_t>& indexes) {
std::move(callback).Run(indexes);
},
std::move(callback)));
}
size_t ChromeAppListModelUpdater::BadgedItemCount() {
size_t count = 0u;
for (auto& key_val : items_) {
if (key_val.second->IsBadged())
++count;
}
return count;
}
void ChromeAppListModelUpdater::GetContextMenuModel(
const std::string& id,
GetMenuModelCallback callback) {
ChromeAppListItem* item = FindItem(id);
// TODO(stevenjb/jennyz): Implement this for folder items.
// TODO(newcomer): Add histograms for folder items.
if (!item || item->is_folder()) {
std::move(callback).Run(nullptr);
return;
}
item->GetContextMenuModel(std::move(callback));
}
syncer::StringOrdinal ChromeAppListModelUpdater::GetFirstAvailablePosition()
const {
std::vector<ChromeAppListItem*> top_level_items;
for (auto& entry : items_) {
ChromeAppListItem* item = entry.second.get();
DCHECK(item->position().IsValid())
<< "Item with invalid position: id=" << item->id()
<< ", name=" << item->name() << ", is_folder=" << item->is_folder()
<< ", is_page_break=" << item->is_page_break();
if (item->folder_id().empty() && item->position().IsValid())
top_level_items.emplace_back(item);
}
return GetFirstAvailablePositionInternal(top_level_items);
}
////////////////////////////////////////////////////////////////////////////////
// Methods for AppListSyncableService
void ChromeAppListModelUpdater::ResolveOemFolderPosition(
const syncer::StringOrdinal& preferred_oem_position,
ResolveOemFolderPositionCallback callback) {
if (!app_list_controller_)
return;
app_list_controller_->ResolveOemFolderPosition(
preferred_oem_position,
base::BindOnce(
[](base::WeakPtr<ChromeAppListModelUpdater> self,
ResolveOemFolderPositionCallback callback,
std::unique_ptr<ash::AppListItemMetadata> folder_data) {
if (!self)
return;
ChromeAppListItem* chrome_oem_folder = nullptr;
if (folder_data) {
chrome_oem_folder = self->FindFolderItem(ash::kOemFolderId);
chrome_oem_folder->SetMetadata(std::move(folder_data));
}
std::move(callback).Run(chrome_oem_folder);
},
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ChromeAppListModelUpdater::AddItemToOemFolder(
std::unique_ptr<ChromeAppListItem> item,
app_list::AppListSyncableService::SyncItem* oem_sync_item,
const std::string& oem_folder_name,
const syncer::StringOrdinal& preferred_oem_position) {
syncer::StringOrdinal position_to_try = preferred_oem_position;
// If we find a valid postion in the sync item, then we'll try it.
if (oem_sync_item && oem_sync_item->item_ordinal.IsValid())
position_to_try = oem_sync_item->item_ordinal;
if (app_list_controller_) {
app_list_controller_->FindOrCreateOemFolder(
oem_folder_name, position_to_try,
base::BindOnce(
[](base::WeakPtr<ChromeAppListModelUpdater> self,
std::unique_ptr<ChromeAppListItem> item) {
if (!self)
return;
self->AddItemToFolder(std::move(item), ash::kOemFolderId);
},
weak_ptr_factory_.GetWeakPtr(), std::move(item)));
} else {
ChromeAppListItem* item_added = AddChromeItem(std::move(item));
item_added->SetChromeFolderId(ash::kOemFolderId);
// If we don't have an OEM folder in Chrome, create one first.
ChromeAppListItem* oem_folder = FindFolderItem(ash::kOemFolderId);
if (!oem_folder) {
std::unique_ptr<ChromeAppListItem> new_oem_folder =
std::make_unique<ChromeAppListItem>(profile_, ash::kOemFolderId,
this);
oem_folder = AddChromeItem(std::move(new_oem_folder));
oem_folder->SetChromeIsFolder(true);
}
oem_folder->SetChromeName(oem_folder_name);
oem_folder->SetChromePosition(position_to_try);
}
}
void ChromeAppListModelUpdater::UpdateAppItemFromSyncItem(
app_list::AppListSyncableService::SyncItem* sync_item,
bool update_name,
bool update_folder) {
// In chrome & ash:
ChromeAppListItem* chrome_item = FindItem(sync_item->item_id);
if (!chrome_item)
return;
VLOG(2) << this << " UpdateAppItemFromSyncItem: " << sync_item->ToString();
if (sync_item->item_ordinal.IsValid() &&
(!chrome_item->position().IsValid() ||
!chrome_item->position().Equals(sync_item->item_ordinal))) {
// This updates the position in both chrome and ash:
chrome_item->SetPosition(sync_item->item_ordinal);
}
// Only update the item name if it is a Folder or the name is empty.
if (update_name && sync_item->item_name != chrome_item->name() &&
(chrome_item->is_folder() || chrome_item->name().empty())) {
// This updates the name in both chrome and ash:
chrome_item->SetName(sync_item->item_name);
}
if (update_folder && chrome_item->folder_id() != sync_item->parent_id) {
VLOG(2) << " Moving Item To Folder: " << sync_item->parent_id;
// This updates the folder in both chrome and ash:
MoveItemToFolder(chrome_item->id(), sync_item->parent_id);
}
}
void ChromeAppListModelUpdater::AddObserver(
AppListModelUpdaterObserver* observer) {
observers_.AddObserver(observer);
}
void ChromeAppListModelUpdater::RemoveObserver(
AppListModelUpdaterObserver* observer) {
observers_.RemoveObserver(observer);
}
////////////////////////////////////////////////////////////////////////////////
// Methods called from Ash:
void ChromeAppListModelUpdater::OnFolderCreated(
std::unique_ptr<ash::AppListItemMetadata> item) {
DCHECK(item->is_folder);
ChromeAppListItem* chrome_item = FindItem(item->id);
// If the item already exists, we should have set its information properly.
if (chrome_item)
return;
// Otherwise, we detect an item is created in Ash which is not added into our
// Chrome list yet. This only happens when a folder is created.
std::unique_ptr<ChromeAppListItem> new_item =
std::make_unique<ChromeAppListItem>(profile_, item->id, this);
chrome_item = AddChromeItem(std::move(new_item));
chrome_item->SetMetadata(std::move(item));
for (AppListModelUpdaterObserver& observer : observers_)
observer.OnAppListItemAdded(chrome_item);
}
void ChromeAppListModelUpdater::OnFolderDeleted(
std::unique_ptr<ash::AppListItemMetadata> item) {
DCHECK(item->is_folder);
ChromeAppListItem* chrome_item = FindItem(item->id);
if (!chrome_item)
return;
for (AppListModelUpdaterObserver& observer : observers_)
observer.OnAppListItemWillBeDeleted(chrome_item);
items_.erase(item->id);
}
void ChromeAppListModelUpdater::OnItemUpdated(
std::unique_ptr<ash::AppListItemMetadata> item) {
ChromeAppListItem* chrome_item = FindItem(item->id);
// Ignore the item if it does not exist. This happens when a race occurs
// between the browser and ash. e.g. An item is removed on browser side while
// there is an in-flight OnItemUpdated() call from ash.
if (!chrome_item)
return;
// Preserve icon once it cannot be modified at ash.
item->icon = chrome_item->icon();
chrome_item->SetMetadata(std::move(item));
for (AppListModelUpdaterObserver& observer : observers_)
observer.OnAppListItemUpdated(chrome_item);
}
void ChromeAppListModelUpdater::OnPageBreakItemAdded(
const std::string& id,
const syncer::StringOrdinal& position) {
ChromeAppListItem* chrome_item = FindItem(id);
// If the item already exists, we should have set its information properly.
if (chrome_item)
return;
// Otherwise, create a new "page break" item.
auto new_item = std::make_unique<ChromeAppListItem>(profile_, id, this);
new_item->SetPosition(position);
new_item->SetIsPageBreak(true);
chrome_item = AddChromeItem(std::move(new_item));
for (AppListModelUpdaterObserver& observer : observers_)
observer.OnAppListItemAdded(chrome_item);
}
void ChromeAppListModelUpdater::OnPageBreakItemDeleted(const std::string& id) {
ChromeAppListItem* chrome_item = FindItem(id);
if (!chrome_item) {
LOG(ERROR) << "OnPageBreakItemDeleted: " << id << " does not exist.";
return;
}
DCHECK(chrome_item->is_page_break());
for (AppListModelUpdaterObserver& observer : observers_)
observer.OnAppListItemWillBeDeleted(chrome_item);
items_.erase(id);
}