| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ui/app_list/apps_model_builder.h" | 5 #include "chrome/browser/ui/app_list/apps_model_builder.h" |
| 6 | 6 |
| 7 #include "base/i18n/case_conversion.h" | 7 #include <algorithm> |
| 8 #include "base/utf_string_conversions.h" | 8 #include <vector> |
| 9 #include "chrome/browser/browser_process.h" | 9 |
| 10 #include "chrome/browser/extensions/extension_prefs.h" |
| 10 #include "chrome/browser/extensions/extension_service.h" | 11 #include "chrome/browser/extensions/extension_service.h" |
| 12 #include "chrome/browser/prefs/pref_service.h" |
| 11 #include "chrome/browser/profiles/profile.h" | 13 #include "chrome/browser/profiles/profile.h" |
| 12 #include "chrome/browser/ui/app_list/extension_app_item.h" | 14 #include "chrome/browser/ui/app_list/extension_app_item.h" |
| 13 #include "chrome/common/chrome_notification_types.h" | 15 #include "chrome/common/chrome_notification_types.h" |
| 14 #include "chrome/common/extensions/extension.h" | 16 #include "chrome/common/extensions/extension.h" |
| 15 #include "content/public/browser/notification_service.h" | 17 #include "content/public/browser/notification_service.h" |
| 16 #include "ui/base/l10n/l10n_util_collator.h" | |
| 17 | 18 |
| 18 using extensions::Extension; | 19 using extensions::Extension; |
| 19 | 20 |
| 20 namespace { | 21 namespace { |
| 21 | 22 |
| 22 // TODO(benwells): Get the list of special apps from the controller. | 23 typedef std::vector<ExtensionAppItem*> Apps; |
| 23 const char* kSpecialApps[] = { | |
| 24 extension_misc::kChromeAppId, | |
| 25 extension_misc::kWebStoreAppId, | |
| 26 }; | |
| 27 | 24 |
| 28 // ModelItemSortData provides a string key to sort with | 25 bool AppPrecedes(const ExtensionAppItem* app1, const ExtensionAppItem* app2) { |
| 29 // l10n_util::StringComparator. | 26 const syncer::StringOrdinal& page1 = app1->GetPageOrdinal(); |
| 30 struct ModelItemSortData { | 27 const syncer::StringOrdinal& page2 = app2->GetPageOrdinal(); |
| 31 explicit ModelItemSortData(app_list::AppListItemModel* app) | 28 if (page1.LessThan(page2)) |
| 32 : app(app), | 29 return true; |
| 33 key(base::i18n::ToLower(UTF8ToUTF16(app->title()))) { | |
| 34 } | |
| 35 | 30 |
| 36 // Needed by StringComparator<Element> in SortVectorWithStringKey, which uses | 31 if (page1.Equals(page2)) |
| 37 // this method to get a key to sort. | 32 return app1->GetAppLaunchOrdinal().LessThan(app2->GetAppLaunchOrdinal()); |
| 38 const string16& GetStringKey() const { | |
| 39 return key; | |
| 40 } | |
| 41 | |
| 42 app_list::AppListItemModel* app; | |
| 43 string16 key; | |
| 44 }; | |
| 45 | |
| 46 // Returns true if given |extension_id| is listed in kSpecialApps. | |
| 47 bool IsSpecialApp(const std::string& extension_id) { | |
| 48 for (size_t i = 0; i < arraysize(kSpecialApps); ++i) { | |
| 49 if (extension_id == kSpecialApps[i]) | |
| 50 return true; | |
| 51 } | |
| 52 | 33 |
| 53 return false; | 34 return false; |
| 54 } | 35 } |
| 55 | 36 |
| 56 } // namespace | 37 } // namespace |
| 57 | 38 |
| 58 AppsModelBuilder::AppsModelBuilder(Profile* profile, | 39 AppsModelBuilder::AppsModelBuilder(Profile* profile, |
| 59 app_list::AppListModel::Apps* model, | 40 app_list::AppListModel::Apps* model, |
| 60 AppListController* controller) | 41 AppListController* controller) |
| 61 : profile_(profile), | 42 : profile_(profile), |
| 62 controller_(controller), | 43 controller_(controller), |
| 63 model_(model), | 44 model_(model) { |
| 64 special_apps_count_(0) { | 45 extensions::ExtensionPrefs* extension_prefs = |
| 46 profile_->GetExtensionService()->extension_prefs(); |
| 47 |
| 65 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, | 48 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, |
| 66 content::Source<Profile>(profile_)); | 49 content::Source<Profile>(profile_)); |
| 67 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, | 50 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| 68 content::Source<Profile>(profile_)); | 51 content::Source<Profile>(profile_)); |
| 52 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED, |
| 53 content::Source<ExtensionSorting>(extension_prefs->extension_sorting())); |
| 69 registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST, | 54 registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST, |
| 70 content::Source<Profile>(profile_)); | 55 content::Source<Profile>(profile_)); |
| 56 |
| 57 pref_change_registrar_.Init(extension_prefs->pref_service()); |
| 58 pref_change_registrar_.Add(extensions::ExtensionPrefs::kExtensionsPref, this); |
| 71 } | 59 } |
| 72 | 60 |
| 73 AppsModelBuilder::~AppsModelBuilder() { | 61 AppsModelBuilder::~AppsModelBuilder() { |
| 74 } | 62 } |
| 75 | 63 |
| 76 void AppsModelBuilder::Build() { | 64 void AppsModelBuilder::Build() { |
| 77 DCHECK(model_ && model_->item_count() == 0); | 65 DCHECK(model_ && model_->item_count() == 0); |
| 78 CreateSpecialApps(); | |
| 79 | 66 |
| 80 Apps apps; | 67 PopulateApps(); |
| 81 GetExtensionApps(&apps); | |
| 82 | |
| 83 SortAndPopulateModel(apps); | |
| 84 HighlightApp(); | 68 HighlightApp(); |
| 85 } | 69 } |
| 86 | 70 |
| 87 void AppsModelBuilder::SortAndPopulateModel(const Apps& apps) { | 71 void AppsModelBuilder::PopulateApps() { |
| 88 // Just return if there is nothing to populate. | |
| 89 if (apps.empty()) | |
| 90 return; | |
| 91 | |
| 92 // Sort apps case insensitive alphabetically. | |
| 93 std::vector<ModelItemSortData> sorted; | |
| 94 for (Apps::const_iterator it = apps.begin(); it != apps.end(); ++it) | |
| 95 sorted.push_back(ModelItemSortData(*it)); | |
| 96 | |
| 97 l10n_util::SortVectorWithStringKey(g_browser_process->GetApplicationLocale(), | |
| 98 &sorted, | |
| 99 false /* needs_stable_sort */); | |
| 100 for (std::vector<ModelItemSortData>::const_iterator it = sorted.begin(); | |
| 101 it != sorted.end(); | |
| 102 ++it) { | |
| 103 model_->Add(it->app); | |
| 104 } | |
| 105 } | |
| 106 | |
| 107 void AppsModelBuilder::InsertItemByTitle(app_list::AppListItemModel* app) { | |
| 108 DCHECK(model_); | |
| 109 | |
| 110 icu::Locale locale(g_browser_process->GetApplicationLocale().c_str()); | |
| 111 UErrorCode error = U_ZERO_ERROR; | |
| 112 scoped_ptr<icu::Collator> collator( | |
| 113 icu::Collator::createInstance(locale, error)); | |
| 114 if (U_FAILURE(error)) | |
| 115 collator.reset(); | |
| 116 | |
| 117 l10n_util::StringComparator<string16> c(collator.get()); | |
| 118 ModelItemSortData data(app); | |
| 119 for (size_t i = special_apps_count_; i < model_->item_count(); ++i) { | |
| 120 ModelItemSortData current(model_->GetItemAt(i)); | |
| 121 if (!c(current.key, data.key)) { | |
| 122 model_->AddAt(i, app); | |
| 123 return; | |
| 124 } | |
| 125 } | |
| 126 model_->Add(app); | |
| 127 } | |
| 128 | |
| 129 void AppsModelBuilder::GetExtensionApps(Apps* apps) { | |
| 130 DCHECK(profile_); | |
| 131 ExtensionService* service = profile_->GetExtensionService(); | 72 ExtensionService* service = profile_->GetExtensionService(); |
| 132 if (!service) | 73 if (!service) |
| 133 return; | 74 return; |
| 134 | 75 |
| 135 // Get extension apps. | 76 Apps apps; |
| 136 const ExtensionSet* extensions = service->extensions(); | 77 const ExtensionSet* extensions = service->extensions(); |
| 137 for (ExtensionSet::const_iterator app = extensions->begin(); | 78 for (ExtensionSet::const_iterator app = extensions->begin(); |
| 138 app != extensions->end(); ++app) { | 79 app != extensions->end(); ++app) { |
| 139 if ((*app)->ShouldDisplayInLauncher() && | 80 if ((*app)->ShouldDisplayInLauncher()) |
| 140 !IsSpecialApp((*app)->id())) { | 81 apps.push_back(new ExtensionAppItem(profile_, *app, controller_)); |
| 141 apps->push_back(new ExtensionAppItem(profile_, *app, controller_)); | |
| 142 } | |
| 143 } | 82 } |
| 144 | 83 |
| 145 #if defined(OS_CHROMEOS) | 84 #if defined(OS_CHROMEOS) |
| 146 // Explicitly add Talk extension if it's installed and enabled. | 85 // Explicitly add Talk extension if it's installed and enabled. |
| 147 // Add it here instead of in CreateSpecialApps() so it sorts naturally. | 86 // Add it here instead of in CreateSpecialApps() so it sorts naturally. |
| 148 // Prefer debug > alpha > beta > production version. | 87 // Prefer debug > alpha > beta > production version. |
| 149 const char* kTalkIds[] = { | 88 const char* kTalkIds[] = { |
| 150 extension_misc::kTalkDebugExtensionId, | 89 extension_misc::kTalkDebugExtensionId, |
| 151 extension_misc::kTalkAlphaExtensionId, | 90 extension_misc::kTalkAlphaExtensionId, |
| 152 extension_misc::kTalkBetaExtensionId, | 91 extension_misc::kTalkBetaExtensionId, |
| 153 extension_misc::kTalkExtensionId, | 92 extension_misc::kTalkExtensionId, |
| 154 }; | 93 }; |
| 155 for (size_t i = 0; i < arraysize(kTalkIds); ++i) { | 94 for (size_t i = 0; i < arraysize(kTalkIds); ++i) { |
| 156 const Extension* talk = service->GetExtensionById( | 95 const Extension* talk = service->GetExtensionById( |
| 157 kTalkIds[i], false /*include_disabled*/); | 96 kTalkIds[i], false /*include_disabled*/); |
| 158 if (talk) { | 97 if (talk) { |
| 159 apps->push_back(new ExtensionAppItem(profile_, talk, controller_)); | 98 apps.push_back(new ExtensionAppItem(profile_, talk, controller_)); |
| 160 break; | 99 break; |
| 161 } | 100 } |
| 162 } | 101 } |
| 163 #endif // OS_CHROMEOS | 102 #endif // OS_CHROMEOS |
| 103 |
| 104 if (apps.empty()) |
| 105 return; |
| 106 |
| 107 std::sort(apps.begin(), apps.end(), &AppPrecedes); |
| 108 |
| 109 for (Apps::const_iterator it = apps.begin(); |
| 110 it != apps.end(); |
| 111 ++it) { |
| 112 model_->Add(*it); |
| 113 } |
| 164 } | 114 } |
| 165 | 115 |
| 166 void AppsModelBuilder::CreateSpecialApps() { | 116 void AppsModelBuilder::ResortApps() { |
| 167 DCHECK(model_ && model_->item_count() == 0); | 117 Apps apps; |
| 118 for (size_t i = 0; i < model_->item_count(); ++i) |
| 119 apps.push_back(GetAppAt(i)); |
| 168 | 120 |
| 169 bool is_guest_session = Profile::IsGuestSession(); | 121 if (apps.empty()) |
| 170 ExtensionService* service = profile_->GetExtensionService(); | 122 return; |
| 171 DCHECK(service); | |
| 172 for (size_t i = 0; i < arraysize(kSpecialApps); ++i) { | |
| 173 const std::string extension_id(kSpecialApps[i]); | |
| 174 if (is_guest_session && extension_id == extension_misc::kWebStoreAppId) | |
| 175 continue; | |
| 176 | 123 |
| 177 const Extension* extension = service->GetInstalledExtension(extension_id); | 124 std::sort(apps.begin(), apps.end(), &AppPrecedes); |
| 178 if (!extension) | |
| 179 continue; | |
| 180 | 125 |
| 181 model_->Add(new ExtensionAppItem(profile_, extension, controller_)); | 126 // Adjusts the order of apps as needed in |model_| based on |apps|. |
| 127 for (size_t i = 0; i < apps.size(); ++i) { |
| 128 ExtensionAppItem* app_item = apps[i]; |
| 129 |
| 130 const size_t insert_index = i; |
| 131 |
| 132 bool found = false; |
| 133 size_t index = 0; |
| 134 |
| 135 // Finds |app_item| in remaining unsorted part in |model_|. |
| 136 for (size_t j = insert_index; j < model_->item_count(); ++j) { |
| 137 if (GetAppAt(j) == app_item) { |
| 138 found = true; |
| 139 index = j; |
| 140 break; |
| 141 } |
| 142 } |
| 143 |
| 144 if (found) { |
| 145 if (index != insert_index) { |
| 146 model_->RemoveAt(index); |
| 147 model_->AddAt(insert_index, app_item); |
| 148 } |
| 149 } else { |
| 150 NOTREACHED(); |
| 151 } |
| 182 } | 152 } |
| 153 } |
| 183 | 154 |
| 184 special_apps_count_ = model_->item_count(); | 155 void AppsModelBuilder::InsertApp(ExtensionAppItem* app) { |
| 156 DCHECK(model_); |
| 157 |
| 158 size_t start = 0; |
| 159 size_t end = model_->item_count(); |
| 160 while (start < end) { |
| 161 size_t mid = (start + end) / 2; |
| 162 |
| 163 if (AppPrecedes(GetAppAt(mid), app)) |
| 164 start = mid + 1; |
| 165 else |
| 166 end = mid; |
| 167 } |
| 168 model_->AddAt(start, app); |
| 185 } | 169 } |
| 186 | 170 |
| 187 int AppsModelBuilder::FindApp(const std::string& app_id) { | 171 int AppsModelBuilder::FindApp(const std::string& app_id) { |
| 188 DCHECK(model_); | 172 DCHECK(model_); |
| 189 for (size_t i = special_apps_count_; i < model_->item_count(); ++i) { | |
| 190 ChromeAppListItem* app = | |
| 191 static_cast<ChromeAppListItem*>(model_->GetItemAt(i)); | |
| 192 if (app->type() != ChromeAppListItem::TYPE_APP) | |
| 193 continue; | |
| 194 | 173 |
| 195 ExtensionAppItem* extension_item = static_cast<ExtensionAppItem*>(app); | 174 for (size_t i = 0; i < model_->item_count(); ++i) { |
| 196 if (extension_item->extension_id() == app_id) | 175 if (GetAppAt(i)->extension_id() == app_id) |
| 197 return i; | 176 return i; |
| 198 } | 177 } |
| 199 | 178 |
| 200 return -1; | 179 return -1; |
| 201 } | 180 } |
| 202 | 181 |
| 203 void AppsModelBuilder::HighlightApp() { | 182 void AppsModelBuilder::HighlightApp() { |
| 204 DCHECK(model_); | 183 DCHECK(model_); |
| 205 if (highlight_app_id_.empty()) | 184 if (highlight_app_id_.empty()) |
| 206 return; | 185 return; |
| 207 | 186 |
| 208 int index = FindApp(highlight_app_id_); | 187 int index = FindApp(highlight_app_id_); |
| 209 if (index == -1) | 188 if (index == -1) |
| 210 return; | 189 return; |
| 211 | 190 |
| 212 model_->GetItemAt(index)->SetHighlighted(true); | 191 model_->GetItemAt(index)->SetHighlighted(true); |
| 213 highlight_app_id_.clear(); | 192 highlight_app_id_.clear(); |
| 214 } | 193 } |
| 215 | 194 |
| 195 ExtensionAppItem* AppsModelBuilder::GetAppAt(size_t index) { |
| 196 DCHECK_LT(index, model_->item_count()); |
| 197 ChromeAppListItem* item = |
| 198 static_cast<ChromeAppListItem*>(model_->GetItemAt(index)); |
| 199 DCHECK_EQ(item->type(), ChromeAppListItem::TYPE_APP); |
| 200 |
| 201 return static_cast<ExtensionAppItem*>(item); |
| 202 } |
| 203 |
| 216 void AppsModelBuilder::Observe(int type, | 204 void AppsModelBuilder::Observe(int type, |
| 217 const content::NotificationSource& source, | 205 const content::NotificationSource& source, |
| 218 const content::NotificationDetails& details) { | 206 const content::NotificationDetails& details) { |
| 219 switch (type) { | 207 switch (type) { |
| 220 case chrome::NOTIFICATION_EXTENSION_LOADED: { | 208 case chrome::NOTIFICATION_EXTENSION_LOADED: { |
| 221 const Extension* extension = | 209 const Extension* extension = |
| 222 content::Details<const Extension>(details).ptr(); | 210 content::Details<const Extension>(details).ptr(); |
| 223 if (!extension->ShouldDisplayInLauncher()) | 211 if (!extension->ShouldDisplayInLauncher()) |
| 224 return; | 212 return; |
| 225 | 213 |
| 226 if (FindApp(extension->id()) != -1) | 214 if (FindApp(extension->id()) != -1) |
| 227 return; | 215 return; |
| 228 | 216 |
| 229 InsertItemByTitle(new ExtensionAppItem(profile_, extension, controller_)); | 217 InsertApp(new ExtensionAppItem(profile_, extension, controller_)); |
| 230 HighlightApp(); | 218 HighlightApp(); |
| 231 break; | 219 break; |
| 232 } | 220 } |
| 233 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { | 221 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { |
| 234 const Extension* extension = | 222 const Extension* extension = |
| 235 content::Details<extensions::UnloadedExtensionInfo>( | 223 content::Details<extensions::UnloadedExtensionInfo>( |
| 236 details)->extension; | 224 details)->extension; |
| 237 int index = FindApp(extension->id()); | 225 int index = FindApp(extension->id()); |
| 238 if (index >= 0) | 226 if (index >= 0) |
| 239 model_->DeleteAt(index); | 227 model_->DeleteAt(index); |
| 240 break; | 228 break; |
| 241 } | 229 } |
| 230 case chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED: { |
| 231 ResortApps(); |
| 232 break; |
| 233 } |
| 242 case chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST: { | 234 case chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST: { |
| 243 highlight_app_id_ = *content::Details<const std::string>(details).ptr(); | 235 highlight_app_id_ = *content::Details<const std::string>(details).ptr(); |
| 244 HighlightApp(); | 236 HighlightApp(); |
| 245 break; | 237 break; |
| 246 } | 238 } |
| 239 case chrome::NOTIFICATION_PREF_CHANGED: { |
| 240 ResortApps(); |
| 241 break; |
| 242 } |
| 247 default: | 243 default: |
| 248 NOTREACHED(); | 244 NOTREACHED(); |
| 249 } | 245 } |
| 250 } | 246 } |
| OLD | NEW |