Chromium Code Reviews| Index: chrome/browser/ui/app_list/app_list_extension_sorting.cc |
| diff --git a/chrome/browser/ui/app_list/app_list_extension_sorting.cc b/chrome/browser/ui/app_list/app_list_extension_sorting.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..91eca1a69363fa9851f832b394bd67fa382605f1 |
| --- /dev/null |
| +++ b/chrome/browser/ui/app_list/app_list_extension_sorting.cc |
| @@ -0,0 +1,330 @@ |
| +// Copyright (c) 2013 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/chrome_notification_types.h" |
| +#include "chrome/browser/extensions/extension_prefs.h" |
| +#include "chrome/browser/extensions/extension_service.h" |
| +#include "chrome/browser/extensions/extension_sorting.h" |
| +#include "chrome/browser/ui/app_list/app_list_extension_sorting.h" |
| +#include "chrome/browser/ui/app_list/app_list_extension_sorting_observer.h" |
| +#include "content/public/browser/notification_service.h" |
| + |
| +#if defined(OS_CHROMEOS) |
| +#include "chrome/browser/chromeos/extensions/default_app_order.h" |
| +#endif |
| + |
| +namespace { |
| + |
| +const char kPrefAppListOrdinal[] = "app_list_ordinal"; |
| + |
| +} // namespace |
| + |
| +AppListExtensionSorting::AppListExtensionSorting( |
| + ExtensionScopedPrefs* extension_scoped_prefs) |
| + : extension_scoped_prefs_(extension_scoped_prefs), |
| + extension_service_(NULL) {} |
| + |
| +AppListExtensionSorting::~AppListExtensionSorting() {} |
| + |
| +void AppListExtensionSorting::Initialize( |
| + const extensions::ExtensionIdList& extension_ids, |
| + const ExtensionSorting* ntp_extension_sorting) { |
| + ExtensionIdSet uninitialized_extensions; |
| + for (extensions::ExtensionIdList::const_iterator ext_it = |
| + extension_ids.begin(); ext_it != extension_ids.end(); ++ext_it) { |
| + if (!InitializeOrdinalFromPrefs(*ext_it)) |
| + uninitialized_extensions.insert(*ext_it); |
| + // Ensure that the web store app still isn't found in this list, since |
| + // it is added after this loop. |
| + DCHECK(*ext_it != extension_misc::kWebStoreAppId); |
| + DCHECK(*ext_it != extension_misc::kChromeAppId); |
| + } |
| + |
| + // Include the Web Store App since it is displayed on the app list. |
| + if (!InitializeOrdinalFromPrefs(extension_misc::kWebStoreAppId)) |
| + uninitialized_extensions.insert(extension_misc::kWebStoreAppId); |
| + // Include the Chrome App since it is displayed on the app list. |
| + if (!InitializeOrdinalFromPrefs(extension_misc::kChromeAppId)) |
| + uninitialized_extensions.insert(extension_misc::kChromeAppId); |
| + |
| + // Copy NTP app order into new pref. |
| + if (ntp_extension_sorting) { |
| + InitializeFromNTPExtensionSorting(ntp_extension_sorting, |
| + &uninitialized_extensions); |
| + } |
| + |
| + InitializeWithDefaultOrdinals(uninitialized_extensions); |
| +} |
| + |
| +bool AppListExtensionSorting::InitializeOrdinalFromPrefs( |
| + const std::string& extension_id) { |
| + syncer::StringOrdinal ordinal = GetAppListOrdinalFromPrefs(extension_id); |
| + if (ordinal.IsValid()) |
| + SetAppListOrdinal(extension_id, ordinal); |
| + return ordinal.IsValid(); |
| +} |
| + |
| +void AppListExtensionSorting::InitializeFromNTPExtensionSorting( |
| + const ExtensionSorting* ntp_extension_sorting, |
| + ExtensionIdSet* uninitialized_extensions) { |
| + extensions::ExtensionIdList ntp_sorted_extensions; |
| + ntp_extension_sorting->GetOrderedExtensionIds(&ntp_sorted_extensions); |
| + |
| + for (extensions::ExtensionIdList::const_iterator ntp_it = |
| + ntp_sorted_extensions.begin(); ntp_it != ntp_sorted_extensions.end(); |
|
koz (OOO until 15th September)
2013/08/20 07:16:57
I think it's more common to put the middle express
|
| + ++ntp_it) { |
| + if (uninitialized_extensions->count(*ntp_it)) { |
| + syncer::StringOrdinal ordinal = app_list_ordinal_map_.empty() |
| + ? syncer::StringOrdinal::CreateInitialOrdinal() |
| + : app_list_ordinal_map_.rbegin()->first.CreateAfter(); |
| + SetAppListOrdinal(*ntp_it, ordinal); |
| + UpdatePrefs(*ntp_it, ordinal); |
| + uninitialized_extensions->erase(*ntp_it); |
| + } |
| + } |
| +} |
| + |
| +void AppListExtensionSorting::InitializeWithDefaultOrdinals( |
| + const ExtensionIdSet& uninitialized_extensions) { |
| + // The following defines the default order of apps. |
| +#if defined(OS_CHROMEOS) |
| + std::vector<std::string> app_ids; |
| + chromeos::default_app_order::Get(&app_ids); |
| +#else |
| + const char* kDefaultAppOrder[] = { |
| + extension_misc::kChromeAppId, |
| + extension_misc::kWebStoreAppId, |
| + }; |
| + const std::vector<const char*> app_ids( |
| + kDefaultAppOrder, kDefaultAppOrder + arraysize(kDefaultAppOrder)); |
| +#endif |
| + |
| + syncer::StringOrdinal ordinal = app_list_ordinal_map_.empty() |
| + ? syncer::StringOrdinal::CreateInitialOrdinal() |
| + : app_list_ordinal_map_.begin()->first.CreateBefore(); |
| + |
| + // Create a map of extension ids to their default ordinals. The first ordinal |
| + // used will precede all ordinals in the app list. Subsequent ordinals are |
| + // created between this starting ordinal and the first ordinal in the app |
| + // list. |
| + std::map<std::string, syncer::StringOrdinal> default_ordinals; |
| + for (size_t i = 0; i < app_ids.size(); ++i) { |
| + const std::string extension_id = app_ids[i]; |
| + default_ordinals[extension_id] = ordinal; |
| + ordinal = app_list_ordinal_map_.empty() |
| + ? ordinal.CreateAfter() |
| + : ordinal.CreateBetween(app_list_ordinal_map_.begin()->first); |
| + } |
| + |
| + // Set default ordinals for uninitialized extensions. |
| + for (ExtensionIdSet::const_iterator it = uninitialized_extensions.begin(); |
| + it != uninitialized_extensions.end(); ++it) { |
| + std::map<std::string, syncer::StringOrdinal>::const_iterator default_it = |
| + default_ordinals.find(*it); |
| + if (default_it != default_ordinals.end()) { |
| + SetAppListOrdinal(*it, default_it->second); |
| + UpdatePrefs(*it, default_it->second); |
| + default_initialized_extensions_.push_back(*it); |
| + } |
| + } |
| +} |
| + |
| +void AppListExtensionSorting::FixSyncCollisions() { |
| + for (AppListOrdinalMap::iterator ordinal_map_it = |
| + app_list_ordinal_map_.begin(); |
| + ordinal_map_it != app_list_ordinal_map_.end(); ++ordinal_map_it) { |
| + syncer::StringOrdinal repeated_ordinal = ordinal_map_it->first; |
| + ExtensionIdSet& extension_set = ordinal_map_it->second; |
| + if (extension_set.size() == 1) |
| + continue; |
| + // Assign unique ordinals to conflicting extensions by assigning new |
| + // ordinals between the conflict ordinal and the next ordinal. |
| + AppListOrdinalMap::iterator next_it = ordinal_map_it; |
| + std::advance(next_it, 1); |
| + |
| + syncer::StringOrdinal upper_bound_ordinal = |
| + next_it == app_list_ordinal_map_.end() ? syncer::StringOrdinal() |
| + : next_it->first; |
| + syncer::StringOrdinal lower_bound_ordinal = repeated_ordinal; |
| + std::vector<std::string> conflicting_ids(extension_set.begin(), |
| + extension_set.end()); |
| + // Skip the first conflicting extension because the first extension can keep |
| + // the conflicted ordinal. Unique ordinals will be assigned so that |
| + // conflicts are sorted by their extension ids. |
| + for (size_t i = 1; i < conflicting_ids.size(); ++i) { |
| + syncer::StringOrdinal unique_ordinal; |
| + if (upper_bound_ordinal.IsValid()) { |
| + unique_ordinal = |
| + lower_bound_ordinal.CreateBetween(upper_bound_ordinal); |
| + } else { |
| + unique_ordinal = lower_bound_ordinal.CreateAfter(); |
| + } |
| + EraseAppListOrdinal(conflicting_ids[i]); |
| + SetAppListOrdinal(conflicting_ids[i], unique_ordinal); |
| + UpdatePrefsAndSync(conflicting_ids[i], unique_ordinal); |
| + lower_bound_ordinal = unique_ordinal; |
| + } |
| + } |
| + |
| + FOR_EACH_OBSERVER(AppListExtensionSortingObserver, observers_, |
| + OnAppListReordered()); |
| +} |
| + |
| +void AppListExtensionSorting::InsertAtFront(const std::string& extension_id) { |
| + DCHECK(!Contains(extension_id)); |
| + syncer::StringOrdinal ordinal = app_list_ordinal_map_.empty() |
| + ? syncer::StringOrdinal::CreateInitialOrdinal() |
| + : app_list_ordinal_map_.begin()->first.CreateBefore(); |
| + SetAppListOrdinal(extension_id, ordinal); |
| + UpdatePrefsAndSync(extension_id, ordinal); |
| +} |
| + |
| +void AppListExtensionSorting::InsertAtBack(const std::string& extension_id) { |
| + DCHECK(!Contains(extension_id)); |
| + syncer::StringOrdinal ordinal = app_list_ordinal_map_.empty() |
| + ? syncer::StringOrdinal::CreateInitialOrdinal() |
| + : app_list_ordinal_map_.rbegin()->first.CreateAfter(); |
| + SetAppListOrdinal(extension_id, ordinal); |
| + UpdatePrefsAndSync(extension_id, ordinal); |
| +} |
| + |
| +void AppListExtensionSorting::OnExtensionMoved( |
| + const std::string& moved_extension_id, |
| + const std::string& predecessor_extension_id, |
| + const std::string& successor_extension_id) { |
| + const syncer::StringOrdinal& predecessor_ordinal = |
| + predecessor_extension_id.empty() ? |
| + syncer::StringOrdinal() : |
| + GetAppListOrdinalFromPrefs(predecessor_extension_id); |
| + const syncer::StringOrdinal& successor_ordinal = |
| + successor_extension_id.empty() ? |
| + syncer::StringOrdinal() : |
| + GetAppListOrdinalFromPrefs(successor_extension_id); |
| + // We only need to change the StringOrdinal if there is at least one |
| + // neighbour. |
| + if (predecessor_ordinal.IsValid() || successor_ordinal.IsValid()) { |
| + syncer::StringOrdinal ordinal; |
| + if (!predecessor_ordinal.IsValid()) { |
| + // Only a successor. |
| + ordinal = successor_ordinal.CreateBefore(); |
| + } else if (!successor_ordinal.IsValid()) { |
| + // Only a predecessor. |
| + ordinal = predecessor_ordinal.CreateAfter(); |
| + } else { |
| + // Both a successor and predecessor |
| + ordinal = predecessor_ordinal.CreateBetween(successor_ordinal); |
| + } |
| + EraseAppListOrdinal(moved_extension_id); |
| + SetAppListOrdinal(moved_extension_id, ordinal); |
| + UpdatePrefsAndSync(moved_extension_id, ordinal); |
| + } |
| + |
| + FOR_EACH_OBSERVER(AppListExtensionSortingObserver, observers_, |
| + OnAppListReordered()); |
| +} |
| + |
| +void AppListExtensionSorting::Erase(const std::string& extension_id) { |
| + EraseAppListOrdinal(extension_id); |
| + UpdatePrefsAndSync(extension_id, syncer::StringOrdinal()); |
| +} |
| + |
| +void AppListExtensionSorting::EraseAppListOrdinal( |
| + const std::string& extension_id) { |
| + if (!Contains(extension_id)) |
| + return; |
| + AppListOrdinalMap::iterator ordinal_map_it = |
| + app_list_ordinal_map_.find(GetAppListOrdinalFromPrefs(extension_id)); |
| + if (ordinal_map_it == app_list_ordinal_map_.end()) |
| + NOTREACHED(); |
| + ordinal_map_it->second.erase(extension_id); |
| + if (ordinal_map_it->second.empty()) |
| + app_list_ordinal_map_.erase(ordinal_map_it); |
| +} |
| + |
| +bool AppListExtensionSorting::ExtensionPrecedes( |
| + const std::string& extension_id1, |
| + const std::string& extension_id2) { |
| + DCHECK(Contains(extension_id1) && Contains(extension_id2)); |
| + return GetAppListOrdinalFromPrefs(extension_id1).LessThan( |
| + GetAppListOrdinalFromPrefs(extension_id2)); |
| +} |
| + |
| +bool AppListExtensionSorting::Contains(const std::string& extension) { |
| + return GetAppListOrdinalFromPrefs(extension).IsValid(); |
| +} |
| + |
| +syncer::StringOrdinal AppListExtensionSorting::GetAppListOrdinalFromPrefs( |
| + const std::string& extension_id) { |
| + std::string raw_data; |
| + // If the preference read fails then raw_data will still be unset and we will |
| + // return an invalid StringOrdinal to signal that no app list ordinal was |
| + // found. |
| + extension_scoped_prefs_->ReadPrefAsString( |
| + extension_id, kPrefAppListOrdinal, &raw_data); |
| + return syncer::StringOrdinal(raw_data); |
| +} |
| + |
| +void AppListExtensionSorting::UpdateAppListOrdinalFromSync( |
| + const std::string& extension_id, |
| + const syncer::StringOrdinal& ordinal) { |
| + EraseAppListOrdinal(extension_id); |
| + SetAppListOrdinal(extension_id, ordinal); |
| + UpdatePrefsAndSync(extension_id, ordinal); |
| +} |
| + |
| +void AppListExtensionSorting::SetAppListOrdinal( |
| + const std::string& extension_id, |
| + const syncer::StringOrdinal& ordinal) { |
| + app_list_ordinal_map_[ordinal].insert(extension_id); |
| +} |
| + |
| +void AppListExtensionSorting::UpdatePrefs( |
| + const std::string& extension_id, |
| + const syncer::StringOrdinal& ordinal) { |
| + if (ordinal.EqualsOrBothInvalid(GetAppListOrdinalFromPrefs(extension_id))) |
| + return; |
| + Value* new_value = ordinal.IsValid() ? |
| + Value::CreateStringValue(ordinal.ToInternalValue()) : |
| + NULL; |
| + extension_scoped_prefs_->UpdateExtensionPref( |
| + extension_id, |
| + kPrefAppListOrdinal, |
| + new_value); |
| +} |
| + |
| +void AppListExtensionSorting::UpdatePrefsAndSync( |
| + const std::string& extension_id, |
| + const syncer::StringOrdinal& ordinal) { |
| + UpdatePrefs(extension_id, ordinal); |
| + // Sync extension pref. |
| + if (!extension_service_) |
| + NOTREACHED(); |
| + const extensions::Extension* extension = |
| + extension_service_->GetInstalledExtension(extension_id); |
| + if (extension) |
| + extension_service_->SyncExtensionChangeIfNeeded(*extension); |
| +} |
| + |
| +void AppListExtensionSorting::SetExtensionService( |
| + ExtensionServiceInterface* extension_service) { |
| + extension_service_ = extension_service; |
| + for (size_t i = 0; i < default_initialized_extensions_.size(); ++i) { |
| + const extensions::Extension* extension = |
| + extension_service_->GetInstalledExtension( |
| + default_initialized_extensions_[i]); |
| + if (extension) |
| + extension_service_->SyncExtensionChangeIfNeeded(*extension); |
| + } |
| + default_initialized_extensions_.clear(); |
| +} |
| + |
| +void AppListExtensionSorting::AddObserver( |
| + AppListExtensionSortingObserver* observer) { |
| + observers_.AddObserver(observer); |
| +} |
| + |
| +void AppListExtensionSorting::RemoveObserver( |
| + AppListExtensionSortingObserver* observer) { |
| + observers_.RemoveObserver(observer); |
| +} |