| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/app_list/chrome_app_list_model_updater.h" |
| |
| #include <unordered_map> |
| #include <utility> |
| |
| #include "ash/app_list/model/app_list_folder_item.h" |
| #include "ash/app_list/model/app_list_item.h" |
| #include "ash/app_list/model/app_list_item_list.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/public/cpp/app_list/app_list_config.h" |
| #include "ash/public/cpp/app_list/app_list_controller.h" |
| #include "ash/public/cpp/app_list/app_list_metrics.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "chrome/browser/ash/app_list/app_list_controller_delegate.h" |
| #include "chrome/browser/ash/app_list/app_list_sync_model_sanitizer.h" |
| #include "chrome/browser/ash/app_list/chrome_app_list_item.h" |
| #include "chrome/browser/ash/app_list/chrome_app_list_item_manager.h" |
| #include "chrome/browser/ash/app_list/reorder/app_list_reorder_core.h" |
| #include "chrome/browser/ash/app_list/reorder/app_list_reorder_delegate.h" |
| #include "chrome/browser/ash/app_list/search/chrome_search_result.h" |
| #include "chrome/browser/feature_engagement/tracker_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/ash/app_icon_color_cache.h" |
| #include "components/feature_engagement/public/feature_constants.h" |
| #include "components/feature_engagement/public/tracker.h" |
| #include "extensions/common/constants.h" |
| #include "ui/base/models/menu_model.h" |
| #include "ui/display/screen.h" |
| |
| namespace { |
| |
| std::unique_ptr<ash::AppListItem> CreateAppListItem( |
| std::unique_ptr<ash::AppListItemMetadata> metadata, |
| ash::AppListModelDelegate* delegate) { |
| std::unique_ptr<ash::AppListItem> app_list_item = |
| metadata->is_folder |
| ? std::make_unique<ash::AppListFolderItem>(metadata->id, delegate) |
| : std::make_unique<ash::AppListItem>(metadata->id); |
| app_list_item->SetMetadata(std::move(metadata)); |
| return app_list_item; |
| } |
| |
| } // namespace |
| |
| // TemporarySortManager -------------------------------------------------------- |
| |
| // A helper class for managing temporary sort data. Used only when the app list |
| // is under temporary sort. |
| class ChromeAppListModelUpdater::TemporarySortManager { |
| public: |
| TemporarySortManager( |
| ash::AppListSortOrder temporary_order, |
| const std::map<std::string, std::unique_ptr<ChromeAppListItem>>& |
| permanent_items) |
| : temporary_order_(temporary_order) { |
| // Fill permanent position storage. |
| for (const auto& id_item_pair : permanent_items) |
| AddPermanentPosition(id_item_pair.first, id_item_pair.second->position()); |
| } |
| TemporarySortManager(const TemporarySortManager&) = delete; |
| TemporarySortManager& operator=(const TemporarySortManager&) = delete; |
| ~TemporarySortManager() = default; |
| |
| bool HasId(const std::string& id) const { |
| return permanent_position_storage_.find(id) != |
| permanent_position_storage_.cend(); |
| } |
| |
| const syncer::StringOrdinal& GetPermanentPositionForId( |
| const std::string& id) const { |
| auto iter = permanent_position_storage_.find(id); |
| DCHECK(iter != permanent_position_storage_.cend()); |
| return iter->second; |
| } |
| |
| void AddPermanentPosition(const std::string& id, |
| const syncer::StringOrdinal& position) { |
| DCHECK(!HasId(id)); |
| permanent_position_storage_.emplace(id, position); |
| } |
| |
| void SetPermanentPosition(const std::string& id, |
| const syncer::StringOrdinal& position) { |
| auto iter = permanent_position_storage_.find(id); |
| DCHECK(iter != permanent_position_storage_.end()); |
| iter->second = position; |
| } |
| |
| void DeletePermanentPosition(const std::string& id) { |
| auto iter = permanent_position_storage_.find(id); |
| DCHECK(iter != permanent_position_storage_.end()); |
| permanent_position_storage_.erase(iter); |
| } |
| |
| void Deactivate() { |
| DCHECK(is_active_); |
| is_active_ = false; |
| } |
| |
| ash::AppListSortOrder temporary_order() const { return temporary_order_; } |
| void set_temporary_order(ash::AppListSortOrder new_order) { |
| temporary_order_ = new_order; |
| } |
| |
| bool is_active() const { return is_active_; } |
| |
| private: |
| // Indicates the app list order that is not committed yet. When under |
| // temporary sort, the app list items on the local device (i.e. the device on |
| // which `temporary_order` is triggered) are sorted with `temporary_order_`. |
| // But the new positions are not synced with other devices. |
| ash::AppListSortOrder temporary_order_ = ash::AppListSortOrder::kCustom; |
| |
| // For each key-value pair, the key is an item id while the value is the |
| // permanent position (i.e. the position that is shared with other synced |
| // devices) of the item indexed by the key. |
| std::map<std::string, syncer::StringOrdinal> permanent_position_storage_; |
| |
| // `TemporarySortManager` is active when: |
| // (1) App list is under temporary sort, and |
| // (2) App list is not in the progress of ending temporary sort. |
| // Note that model updates will not be propagated to observers until the |
| // temporary sort manager is deactivated |
| bool is_active_ = true; |
| }; |
| |
| // ChromeAppListModelUpdater --------------------------------------------------- |
| |
| ChromeAppListModelUpdater::ChromeAppListModelUpdater( |
| Profile* profile, |
| app_list::reorder::AppListReorderDelegate* order_delegate, |
| app_list::AppListSyncModelSanitizer* sync_model_sanitizer) |
| : profile_(profile), |
| order_delegate_(order_delegate), |
| sync_model_sanitizer_(sync_model_sanitizer), |
| item_manager_(std::make_unique<ChromeAppListItemManager>()), |
| model_(this) { |
| DCHECK(order_delegate_); |
| model_.AddObserver(this); |
| } |
| |
| ChromeAppListModelUpdater::~ChromeAppListModelUpdater() { |
| model_.RemoveObserver(this); |
| } |
| |
| void ChromeAppListModelUpdater::SetActive(bool active) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::SetActive"); |
| is_active_ = active; |
| |
| if (active) { |
| ash::AppListController::Get()->SetActiveModel( |
| model_id(), &model_, &search_model_, &quick_app_access_model_); |
| } else if (is_under_temporary_sort()) { |
| // Commit the temporary order when the model updater is deactivated. |
| EndTemporarySortAndTakeAction(EndAction::kCommit); |
| } |
| } |
| |
| void ChromeAppListModelUpdater::AddItem( |
| std::unique_ptr<ChromeAppListItem> app_item) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::AddItem"); |
| std::unique_ptr<ash::AppListItemMetadata> item_data = |
| app_item->CloneMetadata(); |
| |
| // Add to Chrome first leave all updates to observer methods. |
| item_manager_->AddChromeItem(std::move(app_item)); |
| const std::string folder_id = item_data->folder_id; |
| item_data->folder_id.clear(); |
| if (folder_id.empty()) { |
| model_.AddItem(CreateAppListItem(std::move(item_data), this)); |
| } else { |
| model_.AddItemToFolder(CreateAppListItem(std::move(item_data), this), |
| folder_id); |
| } |
| } |
| |
| void ChromeAppListModelUpdater::AddAppItemToFolder( |
| std::unique_ptr<ChromeAppListItem> app_item, |
| const std::string& folder_id, |
| bool add_from_local) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::AddAppItemToFolder"); |
| DCHECK(!app_item->is_folder()); |
| |
| if (is_under_temporary_sort()) { |
| // Store `app_item`'s position before calculating a new position under the |
| // temporary sorting order. |
| DCHECK(temporary_sort_manager_->is_active()); |
| temporary_sort_manager_->AddPermanentPosition(app_item->id(), |
| app_item->position()); |
| |
| // Calculate `app_item`'s position under the temporary order. |
| syncer::StringOrdinal position_under_temporary_order; |
| bool is_successful = app_list::reorder::CalculateItemPositionInOrder( |
| temporary_sort_manager_->temporary_order(), app_item->metadata(), |
| GetItems(), |
| /*global_items=*/nullptr, &position_under_temporary_order); |
| |
| // When the app list is under temporary sorting, local items should be |
| // ordered. Therefore `is_successful` should be true. |
| DCHECK(is_successful); |
| |
| if (!is_successful) { |
| DCHECK(!position_under_temporary_order.IsValid()); |
| position_under_temporary_order = |
| order_delegate_->CalculateGlobalFrontPosition(); |
| } |
| |
| DCHECK(position_under_temporary_order.IsValid()); |
| app_item->SetChromePosition(position_under_temporary_order); |
| } |
| |
| std::unique_ptr<ash::AppListItemMetadata> item_data = |
| app_item->CloneMetadata(); |
| // Add to Chrome first leave all updates to observer methods. |
| // TODO(https://crbug.com/1261796): now we are using the APIs provided by |
| // `ChromeAppListItem` to set item attributes. It is buggy-prone because |
| // when an item's attribute is updated in this way, `item_manager_` is not |
| // aware of the update. We should ensure that after an item is added to |
| // `item_manager_` the item is always updated through `item_manager_`. |
| app_item->SetChromeFolderId(folder_id); |
| ChromeAppListItem* item_added = |
| item_manager_->AddChromeItem(std::move(app_item)); |
| item_data->folder_id.clear(); |
| model_.AddItemToFolder(CreateAppListItem(std::move(item_data), this), |
| folder_id); |
| // Set the item's default icon if it has one. |
| if (!item_added->icon().isNull()) { |
| const bool is_placeholder_icon = item_added->is_placeholder_icon(); |
| ash::AppListItem* item = model_.FindItem(item_added->id()); |
| item->SetDefaultIconAndColor(item_added->icon(), item_added->icon_color(), |
| is_placeholder_icon); |
| } |
| |
| if (add_from_local) { |
| // If the app list is under temporary sort and the new app is installed from |
| // the local device (i.e. the device on which temporary sorting is |
| // initiated), commit the temporary sorting order. |
| if (is_under_temporary_sort()) { |
| EndTemporarySortAndTakeAction(EndAction::kCommit); |
| } else if (folder_id.empty()) { |
| // If an app is a new install, sanitize the page breaks if productivity |
| // launcher is enabled. |
| // No need to sanitize page breaks after committing temporary sort, as |
| // page breaks get sanitized when the sorted order is set. |
| // Adding an item to a folder is not expected for new items, but also does |
| // not impact the top level grid pagination structure. |
| sync_model_sanitizer_->SanitizePageBreaks(GetTopLevelItemIds(), |
| /*reset_page_breaks=*/false); |
| } |
| } |
| } |
| |
| void ChromeAppListModelUpdater::RemoveItem(const std::string& id, |
| bool is_uninstall) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::RemoveItem"); |
| // The item matched by `id` may be unavailable on the local device. |
| if (!model_.FindItem(id)) { |
| return; |
| } |
| |
| ash::AppIconColorCache::GetInstance(profile_).RemoveColorDataForApp(id); |
| |
| // Copy the ID to the stack since it may to be destroyed in |
| // RemoveChromeItem(). See crbug.com/1190347. |
| std::string id_copy = id; |
| |
| item_manager_->RemoveChromeItem(id_copy); |
| model_.DeleteItem(id_copy); |
| |
| if (is_uninstall) { |
| // When item deletion is triggered by local app uninstallation instead of |
| // sync, commits the temporary order if any. |
| if (is_under_temporary_sort()) { |
| EndTemporarySortAndTakeAction(EndAction::kCommit); |
| } else { |
| // NOTE: Committing temporary sort will also reset page breaks, so they |
| // don't have to be sanitized again in that case. |
| sync_model_sanitizer_->SanitizePageBreaks(GetTopLevelItemIds(), |
| /*reset_page_breaks=*/false); |
| } |
| } |
| } |
| |
| void ChromeAppListModelUpdater::SetStatus(ash::AppListModelStatus status) { |
| model_.SetStatus(status); |
| } |
| |
| void ChromeAppListModelUpdater::SetSearchEngineIsGoogle(bool is_google) { |
| search_engine_is_google_ = is_google; |
| search_model_.SetSearchEngineIsGoogle(is_google); |
| } |
| |
| void ChromeAppListModelUpdater::RecalculateWouldTriggerLauncherSearchIph() { |
| TRACE_EVENT0( |
| "ui", |
| "ChromeAppListModelUpdater::RecalculateWouldTriggerLauncherSearchIph"); |
| raw_ptr<feature_engagement::Tracker> tracker = |
| feature_engagement::TrackerFactory::GetForBrowserContext(profile_); |
| if (!tracker) { |
| // Set false as a fail-safe behavior. |
| search_model_.SetWouldTriggerLauncherSearchIph(false); |
| return; |
| } |
| |
| // `AddOnInitializedCallback` will call the callback immediately if it's |
| // already initialized. |
| tracker->AddOnInitializedCallback(base::BindOnce( |
| &ChromeAppListModelUpdater::OnFeatureEngagementTrackerInitialized, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ChromeAppListModelUpdater::OnFeatureEngagementTrackerInitialized( |
| bool success) { |
| TRACE_EVENT0( |
| "ui", "ChromeAppListModelUpdater::OnFeatureEngagementTrackerInitialized"); |
| if (!success) { |
| // Set false as a fail-safe behavior. |
| search_model_.SetWouldTriggerLauncherSearchIph(false); |
| return; |
| } |
| |
| // To be on a safer side, query tracker instance again to minimize the |
| // duration of holding a tracker object. |
| raw_ptr<feature_engagement::Tracker> tracker = |
| feature_engagement::TrackerFactory::GetForBrowserContext(profile_); |
| if (!tracker) { |
| // Set false as a fail-safe behavior. |
| search_model_.SetWouldTriggerLauncherSearchIph(false); |
| return; |
| } |
| |
| search_model_.SetWouldTriggerLauncherSearchIph(tracker->WouldTriggerHelpUI( |
| feature_engagement::kIPHLauncherSearchHelpUiFeature)); |
| } |
| |
| void ChromeAppListModelUpdater::PublishSearchResults( |
| const std::vector<raw_ptr<ChromeSearchResult, VectorExperimental>>& results, |
| const std::vector<ash::AppListSearchResultCategory>& categories) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::PublishSearchResults"); |
| published_results_ = results; |
| |
| for (ChromeSearchResult* const result : results) { |
| result->set_model_updater(this); |
| } |
| |
| std::vector<std::unique_ptr<ash::SearchResult>> ash_results; |
| std::vector<std::unique_ptr<ash::SearchResultMetadata>> result_data; |
| for (ChromeSearchResult* result : results) { |
| auto ash_result = std::make_unique<ash::SearchResult>(); |
| ash_result->SetMetadata(result->CloneMetadata()); |
| ash_results.push_back(std::move(ash_result)); |
| } |
| search_model_.PublishResults(std::move(ash_results), categories); |
| } |
| |
| void ChromeAppListModelUpdater::ClearSearchResults() { |
| published_results_.clear(); |
| search_model_.DeleteAllResults(); |
| } |
| |
| std::vector<raw_ptr<ChromeSearchResult, VectorExperimental>> |
| ChromeAppListModelUpdater::GetPublishedSearchResultsForTest() { |
| return published_results_; |
| } |
| |
| 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); |
| } |
| |
| void ChromeAppListModelUpdater::LoadAppIcon(const std::string& id) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::LoadAppIcon"); |
| ChromeAppListItem* item = FindItem(id); |
| if (!item) |
| return; |
| item->LoadIcon(); |
| } |
| |
| void ChromeAppListModelUpdater::UpdateProgress(const std::string& id, |
| float progress) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::UpdateProgress"); |
| ChromeAppListItem* item = FindItem(id); |
| if (!item) { |
| return; |
| } |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->progress = progress; |
| model_.SetItemMetadata(id, std::move(data)); |
| } |
| |
| void ChromeAppListModelUpdater::SetAccessibleName(const std::string& id, |
| const std::string& name) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::UpdateAccessibleName"); |
| ChromeAppListItem* item = FindItem(id); |
| if (!item) { |
| return; |
| } |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->accessible_name = name; |
| model_.SetItemMetadata(id, std::move(data)); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Methods only used by ChromeAppListItem that talk to ash directly. |
| |
| void ChromeAppListModelUpdater::SetItemIconVersion(const std::string& id, |
| int icon_version) { |
| ash::AppListItem* item = model_.FindItem(id); |
| if (item) |
| item->SetIconVersion(icon_version); |
| } |
| |
| void ChromeAppListModelUpdater::SetItemIconAndColor( |
| const std::string& id, |
| const gfx::ImageSkia& icon, |
| const ash::IconColor& icon_color, |
| bool is_placeholder_icon) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::SetItemIconAndColor"); |
| if (icon.isNull()) |
| return; |
| |
| // TODO(https://crbug.com/1346386): it is awkward to check both `chrome_item` |
| // and `item`. Maybe remove one of them or both after investigation. |
| ChromeAppListItem* chrome_item = FindItem(id); |
| if (!chrome_item) |
| return; |
| |
| ash::AppListItem* item = model_.FindItem(id); |
| if (!item) |
| return; |
| |
| base::AutoReset auto_reset(&item_with_icon_update_, chrome_item->id()); |
| |
| const bool color_change = (icon_color != item->GetDefaultIconColor()); |
| |
| // Two similar icons may generate the same extracted icon color value. |
| // Therefore, always update the app list item icon. |
| item->SetDefaultIconAndColor(icon, icon_color, is_placeholder_icon); |
| |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| MaybeUpdatePositionWhenIconColorChange(data.get()); |
| |
| model_.SetItemMetadata(id, std::move(data)); |
| |
| // Sync the icon color if the color changes. Note that the icon is not synced. |
| // Therefore, we only check whether the color changes here. |
| // NOTE: before the code change that introduces this code block, the item |
| // position updates before the icon color. Therefore, call |
| // `OnAppListItemUpdated()` after `AppListModel::SetItemMetadata` to keep this |
| // order. |
| if (color_change) |
| OnAppListItemUpdated(item); |
| } |
| |
| void ChromeAppListModelUpdater::SetItemBadgeIcon( |
| const std::string& id, |
| const gfx::ImageSkia& badge_icon) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::SetItemBadgeIcon"); |
| if (badge_icon.isNull()) { |
| return; |
| } |
| ash::AppListItem* item = model_.FindItem(id); |
| if (!item) { |
| return; |
| } |
| item->SetHostBadgeIcon(badge_icon); |
| } |
| |
| void ChromeAppListModelUpdater::SetItemName(const std::string& id, |
| const std::string& name) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::SetItemName"); |
| ash::AppListItem* item = model_.FindItem(id); |
| if (!item) |
| return; |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->name = name; |
| model_.SetItemMetadata(id, std::move(data)); |
| } |
| |
| void ChromeAppListModelUpdater::SetAppStatus(const std::string& id, |
| ash::AppStatus app_status) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::SetAppStatus"); |
| ash::AppListItem* item = model_.FindItem(id); |
| if (!item) |
| return; |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->app_status = app_status; |
| model_.SetItemMetadata(id, std::move(data)); |
| } |
| |
| void ChromeAppListModelUpdater::SetItemPosition( |
| const std::string& id, |
| const syncer::StringOrdinal& new_position) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::SetItemPosition"); |
| ash::AppListItem* item = model_.FindItem(id); |
| if (!item) |
| return; |
| DCHECK(new_position.IsValid()); |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->position = new_position; |
| model_.SetItemMetadata(id, std::move(data)); |
| } |
| |
| void ChromeAppListModelUpdater::SetItemIsSystemFolder(const std::string& id, |
| bool is_system_folder) { |
| ash::AppListItem* item = model_.FindItem(id); |
| if (!item) |
| return; |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->is_system_folder = is_system_folder; |
| model_.SetItemMetadata(id, std::move(data)); |
| } |
| |
| void ChromeAppListModelUpdater::SetIsNewInstall(const std::string& id, |
| bool is_new_install) { |
| ash::AppListItem* item = model_.FindItem(id); |
| if (item) |
| item->SetIsNewInstall(is_new_install); // Notifies observers. |
| } |
| |
| void ChromeAppListModelUpdater::SetItemFolderId(const std::string& id, |
| const std::string& folder_id) { |
| ash::AppListItem* item = model_.FindItem(id); |
| if (!item) |
| return; |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->folder_id = folder_id; |
| model_.SetItemMetadata(id, std::move(data)); |
| } |
| |
| void ChromeAppListModelUpdater::SetNotificationBadgeColor(const std::string& id, |
| const SkColor color) { |
| ash::AppListItem* item = model_.FindItem(id); |
| if (item) |
| item->SetNotificationBadgeColor(color); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Methods only used by ChromeSearchResult that talk to ash directly. |
| |
| void ChromeAppListModelUpdater::SetSearchResultMetadata( |
| const std::string& id, |
| std::unique_ptr<ash::SearchResultMetadata> metadata) { |
| ash::SearchResult* result = search_model_.FindSearchResult(metadata->id); |
| if (result) |
| result->SetMetadata(std::move(metadata)); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Methods for item querying |
| |
| ChromeAppListItem* ChromeAppListModelUpdater::FindItem(const std::string& id) { |
| return item_manager_->FindItem(id); |
| } |
| |
| size_t ChromeAppListModelUpdater::ItemCount() { |
| return item_manager_->ItemCount(); |
| } |
| |
| std::vector<const ChromeAppListItem*> ChromeAppListModelUpdater::GetItems() |
| const { |
| std::vector<const ChromeAppListItem*> item_pointers; |
| const std::map<std::string, std::unique_ptr<ChromeAppListItem>>& items = |
| item_manager_->items(); |
| for (auto& entry : items) |
| item_pointers.push_back(entry.second.get()); |
| return item_pointers; |
| } |
| |
| std::set<std::string> ChromeAppListModelUpdater::GetTopLevelItemIds() const { |
| std::set<std::string> item_ids; |
| ash::AppListItemList* item_list = model_.top_level_item_list(); |
| for (size_t i = 0; i < item_list->item_count(); ++i) |
| item_ids.insert(item_list->item_at(i)->id()); |
| return item_ids; |
| } |
| |
| std::vector<ChromeAppListItem*> ChromeAppListModelUpdater::GetTopLevelItems() |
| const { |
| return item_manager_->GetTopLevelItems(); |
| } |
| |
| ChromeAppListItem* ChromeAppListModelUpdater::ItemAtForTest(size_t index) { |
| const std::map<std::string, std::unique_ptr<ChromeAppListItem>>& items = |
| item_manager_->items(); |
| DCHECK_LT(index, items.size()); |
| DCHECK_LE(0u, index); |
| auto it = items.cbegin(); |
| 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) { |
| const std::map<std::string, std::unique_ptr<ChromeAppListItem>>& items = |
| item_manager_->items(); |
| |
| *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_; |
| } |
| |
| size_t ChromeAppListModelUpdater::BadgedItemCount() { |
| return item_manager_->BadgedItemCount(); |
| } |
| |
| void ChromeAppListModelUpdater::GetContextMenuModel( |
| const std::string& id, |
| ash::AppListItemContext item_context, |
| 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(item_context, std::move(callback)); |
| } |
| |
| syncer::StringOrdinal ChromeAppListModelUpdater::GetPositionBeforeFirstItem() |
| const { |
| return GetPositionBeforeFirstItemInternal(GetTopLevelItems()); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Methods for AppListSyncableService |
| |
| void ChromeAppListModelUpdater::UpdateAppItemFromSyncItem( |
| app_list::AppListSyncableService::SyncItem* sync_item, |
| bool update_name, |
| bool update_folder) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::UpdateAppItemFromSyncItem"); |
| // In chrome & ash: |
| ChromeAppListItem* chrome_item = FindItem(sync_item->item_id); |
| if (!chrome_item) |
| return; |
| |
| std::unique_ptr<ash::AppListItemMetadata> data = chrome_item->CloneMetadata(); |
| VLOG(2) << this << " UpdateAppItemFromSyncItem: " << sync_item->ToString(); |
| bool has_changes = false; |
| const bool position_change = |
| (sync_item->item_ordinal.IsValid() && |
| (!chrome_item->position().IsValid() || |
| !chrome_item->position().Equals(sync_item->item_ordinal))); |
| if (position_change) { |
| has_changes = true; |
| data->position = 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())) { |
| has_changes = true; |
| data->name = sync_item->item_name; |
| } |
| |
| const bool folder_change = |
| (update_folder && chrome_item->folder_id() != sync_item->parent_id); |
| if (folder_change) { |
| has_changes = true; |
| VLOG(2) << " Moving Item To Folder: " << sync_item->parent_id; |
| data->folder_id = sync_item->parent_id; |
| } |
| |
| // Note that the icon and the icon color are not set here because the update |
| // on the icon as well the icon color is initiated from the ash side. |
| |
| if (!has_changes) |
| return; |
| |
| // This updates the position in both chrome and ash: |
| model_.SetItemMetadata(chrome_item->id(), std::move(data)); |
| |
| // The code below handles position change or folder change under temporary |
| // sort. |
| |
| // Note that `UpdateAppItemFromSyncItem()` can be called when temporary sort |
| // order is committed. In this case, temporary sort is in the progress of |
| // ending so nothing to do. |
| // TODO(https://crbug.com/1268080): currently committing sort order could |
| // change local item positions. It is due to the sync items that are not |
| // existent on the local device. When this issue gets fixed, check that |
| // `temporary_sort_manager_` is active when it is not null. |
| const bool is_temporary_sort_active = |
| (is_under_temporary_sort() && temporary_sort_manager_->is_active()); |
| if (!is_temporary_sort_active || (!position_change && !folder_change)) |
| return; |
| |
| // TODO(https://crbug.com/1260447): the features of temporary sort are |
| // partially implemented. The cases of app installation/removal are not |
| // handled right now. As a result, `temporary_sort_manager_` may not cover all |
| // items. Therefore manually check the existence of `id` here. When all the |
| // features are completed, replace with a DCHECK statement. |
| const std::string& item_id = sync_item->item_id; |
| if (position_change && temporary_sort_manager_->HasId(item_id)) { |
| temporary_sort_manager_->SetPermanentPosition(item_id, |
| sync_item->item_ordinal); |
| } |
| |
| // Revert the temporary sort order if an item's position or parent folder |
| // changes on a remote device. Because the remote update may conflict with the |
| // temporary sort order. |
| // The item local positions are not committed because remote updates, usually |
| // triggered by user in active ways on remote devices such as dragging then |
| // dropping an item, are believed to reflect user expectation on item layout |
| // so remote updates are more important than revertible local updates. |
| EndTemporarySortAndTakeAction(EndAction::kRevert); |
| } |
| |
| void ChromeAppListModelUpdater::AddObserver( |
| AppListModelUpdaterObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void ChromeAppListModelUpdater::RemoveObserver( |
| AppListModelUpdaterObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| ash::AppListSortOrder ChromeAppListModelUpdater::GetTemporarySortOrderForTest() |
| const { |
| return temporary_sort_manager_->temporary_order(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Methods called from Ash: |
| |
| void ChromeAppListModelUpdater::OnAppListItemAdded(ash::AppListItem* item) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::OnAppListItemAdded"); |
| ChromeAppListItem* chrome_item = FindItem(item->id()); |
| // If the item already exists, we should have set its information properly. |
| if (!chrome_item) { |
| // 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 or when a |
| // page break is added. |
| DCHECK(item->is_folder()); |
| std::unique_ptr<ChromeAppListItem> new_item = |
| std::make_unique<ChromeAppListItem>(profile_, item->id(), this); |
| new_item->SetMetadata(item->CloneMetadata()); |
| chrome_item = item_manager_->AddChromeItem(std::move(new_item)); |
| } |
| |
| // Notify observers that an item is added to the AppListModel in ash. |
| // Note that items of apps are added from Chrome side so there would be an |
| // existing |chrome_item| when running here. |
| MaybeNotifyObserversOfItemChange(chrome_item, ItemChangeType::kAdd); |
| } |
| |
| void ChromeAppListModelUpdater::OnAppListItemUpdated(ash::AppListItem* item) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::OnAppListItemUpdated"); |
| 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; |
| |
| // Do not update the icon or the color of `chrome_item` if `item` is not |
| // in icon update process. |
| if (!item_with_icon_update_ || *item_with_icon_update_ != item->id()) { |
| item->SetDefaultIconAndColor(chrome_item->icon(), chrome_item->icon_color(), |
| item->GetMetadata()->is_placeholder_icon); |
| } |
| |
| const std::string copy_id = item->id(); |
| item_manager_->UpdateChromeItem(copy_id, item->CloneMetadata()); |
| MaybeNotifyObserversOfItemChange(chrome_item, ItemChangeType::kUpdate); |
| } |
| |
| void ChromeAppListModelUpdater::OnAppListItemWillBeDeleted( |
| ash::AppListItem* item) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::OnAppListItemWillBeDeleted"); |
| if (is_under_temporary_sort()) { |
| DCHECK(temporary_sort_manager_->HasId(item->id())); |
| temporary_sort_manager_->DeletePermanentPosition(item->id()); |
| } |
| |
| if (!item->is_folder()) |
| return; |
| |
| ChromeAppListItem* chrome_item = FindItem(item->id()); |
| if (!chrome_item) { |
| LOG(ERROR) << "OnAppListItemWillBeDeleted: " << item->id() |
| << " does not exist."; |
| return; |
| } |
| |
| MaybeNotifyObserversOfItemChange(chrome_item, ItemChangeType::kDelete); |
| item_manager_->RemoveChromeItem(item->id()); |
| } |
| |
| void ChromeAppListModelUpdater::RequestMoveItemToFolder( |
| std::string id, |
| const std::string& folder_id) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::RequestMoveItemToFolder"); |
| DCHECK(!folder_id.empty()); |
| |
| ash::AppListItem* item = model_.FindItem(id); |
| if (item) { |
| // Indicates the item's target position after moving to folder. The target |
| // position relies on the items under the target folder. Therefore calculate |
| // `target_position` before moving the item to the folder. |
| syncer::StringOrdinal target_position; |
| |
| const syncer::StringOrdinal old_position = |
| item_manager_->FindItem(id)->position(); |
| |
| const bool is_sorted = is_under_temporary_sort() || |
| order_delegate_->GetPermanentSortingOrder() != |
| ash::AppListSortOrder::kCustom; |
| |
| // Verify that when the app list is under sorting, `old_position` should be |
| // valid. But the case that `old_position` is invalid is handled for safety. |
| DCHECK(!is_sorted || old_position.IsValid()); |
| |
| if (is_sorted && old_position.IsValid()) { |
| // When items are sorted, item positions are set so all items in the model |
| // are in correct sort order (regardless of their parent IDs). Therefore, |
| // item position will be in correct sort order relative to items already |
| // in the target folder. |
| target_position = old_position; |
| } else { |
| ChromeAppListItem* last_child = |
| item_manager_->FindLastChildInFolder(folder_id); |
| target_position = last_child |
| ? last_child->position().CreateAfter() |
| : syncer::StringOrdinal::CreateInitialOrdinal(); |
| } |
| |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->folder_id = folder_id; |
| data->position = target_position; |
| model_.SetItemMetadata(id, std::move(data)); |
| } |
| |
| // When user moves a local item to a folder, the user is believed to accept |
| // the item layout after reordering. Therefore local positions are |
| // committed. |
| if (is_under_temporary_sort()) { |
| EndTemporarySortAndTakeAction(EndAction::kCommit); |
| } else { |
| // NOTE: Committing temporary sort will also reset page breaks, so they |
| // don't have to be sanitized again in that case. |
| sync_model_sanitizer_->SanitizePageBreaks(GetTopLevelItemIds(), |
| /*reset_page_breaks=*/false); |
| } |
| } |
| |
| void ChromeAppListModelUpdater::RequestMoveItemToRoot( |
| std::string id, |
| syncer::StringOrdinal target_position) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::RequestMoveItemToRoot"); |
| ash::AppListItem* item = model_.FindItem(id); |
| if (!item) |
| return; |
| |
| DCHECK(!item->folder_id().empty()); |
| |
| std::unique_ptr<ash::AppListItemMetadata> data = item->CloneMetadata(); |
| data->folder_id = ""; |
| data->position = target_position; |
| model_.SetItemMetadata(id, std::move(data)); |
| |
| if (is_under_temporary_sort()) { |
| EndTemporarySortAndTakeAction(EndAction::kCommitAndClearSort); |
| } else { |
| ResetPrefSortOrderInNonTemporaryMode( |
| ash::AppListOrderUpdateEvent::kItemMovedToRoot); |
| sync_model_sanitizer_->SanitizePageBreaks(GetTopLevelItemIds(), |
| /*reset_page_breaks=*/false); |
| } |
| } |
| |
| void ChromeAppListModelUpdater::RequestAppListSort( |
| ash::AppListSortOrder order) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::RequestAppListSort"); |
| CHECK_NE(ash::AppListSortOrder::kCustom, order); |
| |
| // Ignore sort requests if sorting makes no visual difference. |
| if (item_manager_->ItemCount() < 2) |
| return; |
| |
| if (is_under_temporary_sort()) { |
| DCHECK(temporary_sort_manager_->is_active()); |
| |
| // Sorting can be triggered when app list is under temporary sort. |
| if (temporary_sort_manager_->temporary_order() == order) { |
| // Order does not change so nothing to do. |
| return; |
| } |
| |
| // Permanent positions are already stored. Therefore only update |
| // the temporary order here. |
| temporary_sort_manager_->set_temporary_order(order); |
| } else { |
| // The app list was not under temporary sort - initialize it now. |
| temporary_sort_manager_ = |
| std::make_unique<TemporarySortManager>(order, item_manager_->items()); |
| } |
| |
| // The code below changes the item positions based on the new order. |
| |
| // Calculate item positions under temporary sort order. |
| std::vector<app_list::reorder::ReorderParam> reorder_params = |
| app_list::reorder::GenerateReorderParamsForAppListItems(order, |
| GetItems()); |
| |
| // Return early if reordering is not required. |
| if (!reorder_params.size()) |
| return; |
| |
| ash::AppListController::Get()->UpdateAppListWithNewTemporarySortOrder( |
| order, |
| /*animate=*/true, |
| base::BindOnce( |
| &ChromeAppListModelUpdater::UpdateItemPositionWithReorderParam, |
| weak_ptr_factory_.GetWeakPtr(), std::move(reorder_params))); |
| } |
| |
| void ChromeAppListModelUpdater::RequestAppListSortRevert() { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::RequestAppListSortRevert"); |
| if (!is_under_temporary_sort()) |
| return; |
| |
| EndTemporarySortAndTakeAction(EndAction::kRevert); |
| } |
| |
| void ChromeAppListModelUpdater::RequestCommitTemporarySortOrder() { |
| if (!is_under_temporary_sort()) { |
| return; |
| } |
| |
| EndTemporarySortAndTakeAction(EndAction::kCommit); |
| } |
| |
| void ChromeAppListModelUpdater::RequestPositionUpdate( |
| std::string id, |
| const syncer::StringOrdinal& new_position, |
| ash::RequestPositionUpdateReason reason) { |
| DCHECK(FindItem(id)); |
| SetItemPosition(id, new_position); |
| |
| // Commit positions and clear the sort order if a local item is moved. |
| if (reason == ash::RequestPositionUpdateReason::kMoveItem) { |
| if (is_under_temporary_sort()) { |
| EndTemporarySortAndTakeAction(EndAction::kCommitAndClearSort); |
| } else { |
| ResetPrefSortOrderInNonTemporaryMode( |
| ash::AppListOrderUpdateEvent::kItemMoved); |
| |
| // NOTE: Committing temporary sort will also reset page breaks, so they |
| // don't have to be sanitized again in that case. |
| sync_model_sanitizer_->SanitizePageBreaks(GetTopLevelItemIds(), |
| /*reset_page_breaks=*/false); |
| } |
| } |
| } |
| |
| std::string ChromeAppListModelUpdater::RequestFolderCreation( |
| std::string merge_target_id, |
| std::string item_to_merge_id) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::RequestFolderCreation"); |
| // Folder creation is a user action, so temporary sort state should end. |
| // If feature to position the folder to correct sorted position is disabled, |
| // clear the sort. |
| const bool under_temporary_sort = is_under_temporary_sort(); |
| if (under_temporary_sort) { |
| EndTemporarySortAndTakeAction(EndAction::kCommit); |
| } |
| |
| ash::AppListItem* target_item = model_.FindItem(merge_target_id); |
| DCHECK(target_item); |
| DCHECK(!target_item->is_folder()); |
| DCHECK_EQ("", target_item->folder_id()); |
| |
| ash::AppListItem* item_to_merge = model_.FindItem(item_to_merge_id); |
| DCHECK(item_to_merge); |
| DCHECK(!item_to_merge->is_folder()); |
| |
| const ash::AppListSortOrder current_sort_order = |
| order_delegate_->GetPermanentSortingOrder(); |
| |
| // Create a new folder. |
| const std::string new_folder_id = ash::AppListFolderItem::GenerateId(); |
| std::unique_ptr<ChromeAppListItem> new_folder_item = |
| std::make_unique<ChromeAppListItem>(profile_, new_folder_id, this); |
| new_folder_item->SetChromeIsFolder(true); |
| |
| // Calculate the new folder's sorted position - if apps grid is not sorted, |
| // default to the original item position. |
| syncer::StringOrdinal target_position = target_item->position(); |
| if (current_sort_order != ash::AppListSortOrder::kCustom) { |
| syncer::StringOrdinal sorted_position; |
| bool has_sorted_position = |
| order_delegate_->CalculateItemPositionInPermanentSortOrder( |
| new_folder_item->metadata(), &sorted_position); |
| if (has_sorted_position) |
| target_position = sorted_position; |
| } |
| new_folder_item->SetChromePosition(target_position); |
| |
| ChromeAppListItem* chrome_item = |
| item_manager_->AddChromeItem(std::move(new_folder_item)); |
| model_.AddItem(CreateAppListItem(chrome_item->CloneMetadata(), this)); |
| |
| // Adjust parent and position of the item getting mergrd into the target item. |
| std::unique_ptr<ash::AppListItemMetadata> item_to_merge_data = |
| item_to_merge->CloneMetadata(); |
| item_to_merge_data->folder_id = new_folder_id; |
| |
| // When sort is enabled, the item positing relative to `target_item` should |
| // already be correct, otherwise move the item at the end of the folder. |
| if (current_sort_order == ash::AppListSortOrder::kCustom) |
| item_to_merge_data->position = target_item->position().CreateAfter(); |
| model_.SetItemMetadata(item_to_merge_id, std::move(item_to_merge_data)); |
| |
| // Set the target item new folder ID. |
| std::unique_ptr<ash::AppListItemMetadata> target_data = |
| target_item->CloneMetadata(); |
| target_data->folder_id = new_folder_id; |
| model_.SetItemMetadata(merge_target_id, std::move(target_data)); |
| |
| sync_model_sanitizer_->SanitizePageBreaks(GetTopLevelItemIds(), |
| /*reset_page_breaks=*/false); |
| return new_folder_id; |
| } |
| |
| void ChromeAppListModelUpdater::RequestFolderRename( |
| std::string folder_id, |
| const std::string& new_name) { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::RequestFolderRename"); |
| ChromeAppListItem* folder_item = FindItem(folder_id); |
| if (!folder_item) |
| return; |
| |
| ash::AppListSortOrder current_sort_order = ash::AppListSortOrder::kCustom; |
| const bool under_temporary_sort = is_under_temporary_sort(); |
| if (under_temporary_sort) { |
| current_sort_order = temporary_sort_manager_->temporary_order(); |
| } else { |
| current_sort_order = order_delegate_->GetPermanentSortingOrder(); |
| } |
| |
| // If user tries to take an action, and rename a folder - commit temporary |
| // sort. |
| if (under_temporary_sort) { |
| EndTemporarySortAndTakeAction(EndAction::kCommit); |
| } |
| |
| folder_item->SetChromeName(new_name); |
| |
| bool position_changed = false; |
| // If app list is sorted alphabetically, the folder name change impacts the |
| // folder position within the sorted list. |
| const bool is_name_sort = |
| current_sort_order == ash::AppListSortOrder::kNameAlphabetical || |
| current_sort_order == ash::AppListSortOrder::kNameReverseAlphabetical || |
| current_sort_order == |
| ash::AppListSortOrder::kAlphabeticalEphemeralAppFirst; |
| if (is_name_sort) { |
| syncer::StringOrdinal sorted_position; |
| position_changed = |
| order_delegate_->CalculateItemPositionInPermanentSortOrder( |
| folder_item->metadata(), &sorted_position); |
| if (position_changed) |
| folder_item->SetChromePosition(sorted_position); |
| } |
| |
| model_.SetItemMetadata(folder_id, folder_item->CloneMetadata()); |
| |
| if (position_changed) { |
| sync_model_sanitizer_->SanitizePageBreaks(GetTopLevelItemIds(), |
| /*reset_page_breaks=*/false); |
| } |
| } |
| |
| void ChromeAppListModelUpdater::OnAppListHidden() { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::OnAppListHidden"); |
| if (!is_under_temporary_sort()) |
| return; |
| |
| DCHECK(temporary_sort_manager_->is_active()); |
| |
| // Commit the temporary sort order if app list gets hidden. |
| EndTemporarySortAndTakeAction(EndAction::kCommit); |
| } |
| |
| // Private methods ------------------------------------------------------------- |
| |
| void ChromeAppListModelUpdater::MaybeNotifyObserversOfItemChange( |
| ChromeAppListItem* chrome_item, |
| ItemChangeType type) { |
| TRACE_EVENT0("ui", |
| "ChromeAppListModelUpdater::MaybeNotifyObserversOfItemChange"); |
| // If `temporary_sort_manager_` is active, item changes are not propagated |
| // to observers. |
| if (is_under_temporary_sort() && temporary_sort_manager_->is_active()) |
| return; |
| |
| switch (type) { |
| case ItemChangeType::kAdd: |
| for (AppListModelUpdaterObserver& observer : observers_) |
| observer.OnAppListItemAdded(chrome_item); |
| break; |
| case ItemChangeType::kUpdate: |
| for (AppListModelUpdaterObserver& observer : observers_) |
| observer.OnAppListItemUpdated(chrome_item); |
| break; |
| case ItemChangeType::kDelete: |
| for (AppListModelUpdaterObserver& observer : observers_) |
| observer.OnAppListItemWillBeDeleted(chrome_item); |
| break; |
| } |
| } |
| |
| void ChromeAppListModelUpdater::EndTemporarySortAndTakeAction( |
| EndAction action) { |
| TRACE_EVENT0("ui", |
| "ChromeAppListModelUpdater::EndTemporarySortAndTakeAction"); |
| CHECK(is_under_temporary_sort() && temporary_sort_manager_->is_active()); |
| |
| // Allow item updates to be propagated to observers. |
| temporary_sort_manager_->Deactivate(); |
| |
| base::OnceClosure update_position_closure; |
| switch (action) { |
| case EndAction::kCommit: |
| CommitTemporaryPositions(); |
| order_delegate_->SetAppListPreferredOrder( |
| temporary_sort_manager_->temporary_order()); |
| break; |
| case EndAction::kRevert: |
| update_position_closure = base::BindOnce( |
| &ChromeAppListModelUpdater::UpdateItemPositionWithReorderParam, |
| weak_ptr_factory_.GetWeakPtr(), |
| CalculateReorderParamsForRevertOrder()); |
| break; |
| case EndAction::kCommitAndClearSort: |
| CommitTemporaryPositions(); |
| order_delegate_->SetAppListPreferredOrder(ash::AppListSortOrder::kCustom); |
| break; |
| } |
| |
| temporary_sort_manager_.reset(); |
| |
| const bool animate = !update_position_closure.is_null(); |
| ash::AppListController::Get()->UpdateAppListWithNewTemporarySortOrder( |
| /*new_order=*/std::nullopt, animate, std::move(update_position_closure)); |
| } |
| |
| void ChromeAppListModelUpdater::CommitTemporaryPositions() { |
| TRACE_EVENT0("ui", "ChromeAppListModelUpdater::CommitTemporaryPositions"); |
| const std::map<std::string, std::unique_ptr<ChromeAppListItem>>& items = |
| item_manager_->items(); |
| for (const auto& id_item_pair : items) { |
| const syncer::StringOrdinal& temporary_position = |
| id_item_pair.second->position(); |
| |
| if (!temporary_position.IsValid()) { |
| // Not sure whether this branch can be executed. Handle this case for |
| // safety. TODO(https://crbug.com/1263795): check whether the positions |
| // stored in `item_manager_` are always valid. If so, remove this code. |
| continue; |
| } |
| |
| // TODO(https://crbug.com/1260447): the features of temporary sort are |
| // partially implemented. The cases of app installation/removal are not |
| // handled right now. As a result, the ids in `temporary_sort_manager_` |
| // can be inconsistent with those in `item_manager_`. Therefore manually |
| // check the existence of `id` here. When the issue gets addressed, replace |
| // with a DCHECK statement. |
| const std::string& id = id_item_pair.first; |
| if (!temporary_sort_manager_->HasId(id)) |
| continue; |
| |
| const syncer::StringOrdinal& permanent_position = |
| temporary_sort_manager_->GetPermanentPositionForId(id); |
| if (permanent_position.IsValid() && |
| permanent_position.Equals(temporary_position)) { |
| // The temporary sort order does not modify the item's position so no |
| // work to do for this item. |
| continue; |
| } |
| |
| // Propagate the item position update. |
| MaybeNotifyObserversOfItemChange(id_item_pair.second.get(), |
| ItemChangeType::kUpdate); |
| } |
| } |
| |
| std::vector<app_list::reorder::ReorderParam> |
| ChromeAppListModelUpdater::CalculateReorderParamsForRevertOrder() const { |
| TRACE_EVENT0( |
| "ui", "ChromeAppListModelUpdater::CalculateReorderParamsForRevertOrder"); |
| std::vector<app_list::reorder::ReorderParam> reorder_params; |
| |
| const std::map<std::string, std::unique_ptr<ChromeAppListItem>>& items = |
| item_manager_->items(); |
| for (const auto& id_item_pair : items) { |
| const std::string& id = id_item_pair.first; |
| |
| // TODO(https://crbug.com/1260447): the features of temporary sort are |
| // partially implemented. The cases of app installation/removal are not |
| // handled right now. As a result, the ids in `temporary_sort_manager_` |
| // can be inconsistent with those in `item_manager_`. Therefore manually |
| // check the existence of `id` here. When the issue gets addressed, replace |
| // with a DCHECK statement. |
| if (!temporary_sort_manager_->HasId(id)) |
| continue; |
| |
| const syncer::StringOrdinal& temporary_position = |
| id_item_pair.second->position(); |
| const syncer::StringOrdinal& permanent_position = |
| temporary_sort_manager_->GetPermanentPositionForId(id); |
| |
| if (temporary_position.IsValid() && permanent_position.IsValid() && |
| temporary_position.Equals(permanent_position)) { |
| // Skip if `temporary_position` is equal to `permanent_position`. |
| continue; |
| } |
| |
| reorder_params.emplace_back(id, permanent_position); |
| } |
| |
| return reorder_params; |
| } |
| |
| void ChromeAppListModelUpdater::UpdateItemPositionWithReorderParam( |
| const std::vector<app_list::reorder::ReorderParam>& reorder_params) { |
| TRACE_EVENT0("ui", |
| "ChromeAppListModelUpdater::UpdateItemPositionWithReorderParam"); |
| for (const auto& reorder_param : reorder_params) |
| SetItemPosition(reorder_param.sync_item_id, reorder_param.ordinal); |
| } |
| |
| void ChromeAppListModelUpdater::ResetPrefSortOrderInNonTemporaryMode( |
| ash::AppListOrderUpdateEvent event) { |
| TRACE_EVENT0( |
| "ui", "ChromeAppListModelUpdater::ResetPrefSortOrderInNonTemporaryMode"); |
| if (!order_delegate_ || order_delegate_->GetPermanentSortingOrder() == |
| ash::AppListSortOrder::kCustom) { |
| return; |
| } |
| |
| order_delegate_->SetAppListPreferredOrder(ash::AppListSortOrder::kCustom); |
| |
| ReportPrefOrderClearAction(event, |
| display::Screen::GetScreen()->InTabletMode()); |
| } |
| |
| void ChromeAppListModelUpdater::MaybeUpdatePositionWhenIconColorChange( |
| ash::AppListItemMetadata* data) { |
| TRACE_EVENT0( |
| "ui", |
| "ChromeAppListModelUpdater::MaybeUpdatePositionWhenIconColorChange"); |
| // No op if the color info is invalid. |
| if (!data->icon_color.IsValid()) |
| return; |
| |
| syncer::StringOrdinal position_under_color_order; |
| bool success = false; |
| if (is_under_temporary_sort() && temporary_sort_manager_->temporary_order() == |
| ash::AppListSortOrder::kColor) { |
| success = app_list::reorder::CalculateItemPositionInOrder( |
| temporary_sort_manager_->temporary_order(), *data, GetItems(), |
| /*global_items=*/nullptr, &position_under_color_order); |
| } else if (order_delegate_ && order_delegate_->GetPermanentSortingOrder() == |
| ash::AppListSortOrder::kColor) { |
| success = order_delegate_->CalculateItemPositionInPermanentSortOrder( |
| *data, &position_under_color_order); |
| } |
| |
| if (success) |
| data->position = position_under_color_order; |
| } |