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 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/app_list/apps_model_builder.h" 6 7 #include <algorithm> 8 9 #include "base/auto_reset.h" 10 #include "base/prefs/pref_service.h" 11 #include "chrome/browser/chrome_notification_types.h" 12 #include "chrome/browser/extensions/extension_prefs.h" 13 #include "chrome/browser/extensions/extension_service.h" 14 #include "chrome/browser/extensions/extension_sorting.h" 15 #include "chrome/browser/extensions/extension_system.h" 16 #include "chrome/browser/extensions/install_tracker.h" 17 #include "chrome/browser/extensions/install_tracker_factory.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/browser/ui/app_list/extension_app_item.h" 20 #include "chrome/common/extensions/extension.h" 21 #include "chrome/common/extensions/extension_constants.h" 22 #include "chrome/common/pref_names.h" 23 #include "content/public/browser/notification_service.h" 24 #include "ui/gfx/image/image_skia.h" 25 26 using extensions::Extension; 27 28 namespace { 29 30 bool AppPrecedes(const ExtensionAppItem* app1, const ExtensionAppItem* app2) { 31 const syncer::StringOrdinal& page1 = app1->GetPageOrdinal(); 32 const syncer::StringOrdinal& page2 = app2->GetPageOrdinal(); 33 if (page1.LessThan(page2)) 34 return true; 35 36 if (page1.Equals(page2)) 37 return app1->GetAppLaunchOrdinal().LessThan(app2->GetAppLaunchOrdinal()); 38 39 return false; 40 } 41 42 bool ShouldDisplayInAppLauncher(Profile* profile, 43 scoped_refptr<const Extension> app) { 44 // If it's the web store, check the policy. 45 bool blocked_by_policy = 46 (app->id() == extension_misc::kWebStoreAppId || 47 app->id() == extension_misc::kEnterpriseWebStoreAppId) && 48 profile->GetPrefs()->GetBoolean(prefs::kHideWebStoreIcon); 49 return app->ShouldDisplayInAppLauncher() && !blocked_by_policy; 50 } 51 52 } // namespace 53 54 AppsModelBuilder::AppsModelBuilder(Profile* profile, 55 app_list::AppListModel::Apps* model, 56 AppListControllerDelegate* controller) 57 : profile_(profile), 58 controller_(controller), 59 model_(model), 60 highlighted_app_pending_(false), 61 ignore_changes_(false), 62 tracker_(extensions::InstallTrackerFactory::GetForProfile(profile_)) { 63 model_->AddObserver(this); 64 } 65 66 AppsModelBuilder::~AppsModelBuilder() { 67 OnShutdown(); 68 model_->RemoveObserver(this); 69 } 70 71 void AppsModelBuilder::Build() { 72 DCHECK(model_ && model_->item_count() == 0); 73 74 PopulateApps(); 75 UpdateHighlight(); 76 77 // Start observing after model is built. 78 tracker_->AddObserver(this); 79 } 80 81 void AppsModelBuilder::OnBeginExtensionInstall( 82 const std::string& extension_id, 83 const std::string& extension_name, 84 const gfx::ImageSkia& installing_icon, 85 bool is_app, 86 bool is_platform_app) { 87 if (!is_app) 88 return; 89 InsertApp(new ExtensionAppItem(profile_, 90 extension_id, 91 controller_, 92 extension_name, 93 installing_icon, 94 is_platform_app)); 95 SetHighlightedApp(extension_id); 96 } 97 98 void AppsModelBuilder::OnDownloadProgress(const std::string& extension_id, 99 int percent_downloaded) { 100 int i = FindApp(extension_id); 101 if (i == -1) 102 return; 103 GetAppAt(i)->SetPercentDownloaded(percent_downloaded); 104 } 105 106 void AppsModelBuilder::OnInstallFailure(const std::string& extension_id) { 107 int i = FindApp(extension_id); 108 if (i == -1) 109 return; 110 model_->DeleteAt(i); 111 } 112 113 void AppsModelBuilder::OnExtensionLoaded(const Extension* extension) { 114 if (!extension->ShouldDisplayInAppLauncher()) 115 return; 116 117 const int existing_index = FindApp(extension->id()); 118 if (existing_index != -1) { 119 GetAppAt(existing_index)->Reload(); 120 return; 121 } 122 123 InsertApp(new ExtensionAppItem(profile_, 124 extension->id(), 125 controller_, 126 "", 127 gfx::ImageSkia(), 128 extension->is_platform_app())); 129 UpdateHighlight(); 130 } 131 132 void AppsModelBuilder::OnExtensionUnloaded(const Extension* extension) { 133 int index = FindApp(extension->id()); 134 if (index < 0) 135 return; 136 GetAppAt(index)->UpdateIcon(); 137 } 138 139 void AppsModelBuilder::OnExtensionUninstalled(const Extension* extension) { 140 int index = FindApp(extension->id()); 141 if (index < 0) 142 return; 143 model_->DeleteAt(index); 144 } 145 146 void AppsModelBuilder::OnAppsReordered() { 147 ResortApps(); 148 } 149 150 void AppsModelBuilder::OnAppInstalledToAppList( 151 const std::string& extension_id) { 152 SetHighlightedApp(extension_id); 153 } 154 155 void AppsModelBuilder::OnShutdown() { 156 if (tracker_) { 157 tracker_->RemoveObserver(this); 158 tracker_ = NULL; 159 } 160 } 161 162 void AppsModelBuilder::AddApps(const ExtensionSet* extensions, Apps* apps) { 163 for (ExtensionSet::const_iterator app = extensions->begin(); 164 app != extensions->end(); ++app) { 165 if (ShouldDisplayInAppLauncher(profile_, *app)) 166 apps->push_back(new ExtensionAppItem(profile_, 167 (*app)->id(), 168 controller_, 169 "", 170 gfx::ImageSkia(), 171 (*app)->is_platform_app())); 172 } 173 } 174 175 void AppsModelBuilder::PopulateApps() { 176 ExtensionService* service = 177 extensions::ExtensionSystem::Get(profile_)->extension_service(); 178 if (!service) 179 return; 180 181 Apps apps; 182 AddApps(service->extensions(), &apps); 183 AddApps(service->disabled_extensions(), &apps); 184 AddApps(service->terminated_extensions(), &apps); 185 186 if (apps.empty()) 187 return; 188 189 service->extension_prefs()->extension_sorting()->FixNTPOrdinalCollisions(); 190 std::sort(apps.begin(), apps.end(), &AppPrecedes); 191 192 for (size_t i = 0; i < apps.size(); ++i) 193 model_->Add(apps[i]); 194 } 195 196 void AppsModelBuilder::ResortApps() { 197 // Scan app items in |model_| and put the apps that do not have valid ordinals 198 // into |invalid_ordinal_apps|. This is needed to handle uninstalling a 199 // terminated app case, where there is no unload notification and uninstall 200 // notification comes in after the app's ordinals are cleared. 201 // See http://crbug.com/256749. 202 Apps apps; 203 Apps invalid_ordinal_apps; 204 for (size_t i = 0; i < model_->item_count(); ++i) { 205 ExtensionAppItem* app = GetAppAt(i); 206 if (app->GetPageOrdinal().IsValid() && app->GetAppLaunchOrdinal().IsValid()) 207 apps.push_back(app); 208 else 209 invalid_ordinal_apps.push_back(app); 210 } 211 212 std::sort(apps.begin(), apps.end(), &AppPrecedes); 213 apps.insert( 214 apps.end(), invalid_ordinal_apps.begin(), invalid_ordinal_apps.end()); 215 216 base::AutoReset<bool> auto_reset(&ignore_changes_, true); 217 218 // Adjusts the order of apps as needed in |model_| based on |apps|. 219 for (size_t i = 0; i < apps.size(); ++i) { 220 ExtensionAppItem* app_item = apps[i]; 221 222 // Finds |app_item| in remaining unsorted part in |model_|. 223 for (size_t j = i; j < model_->item_count(); ++j) { 224 if (GetAppAt(j) == app_item) { 225 model_->Move(j, i); 226 break; 227 } 228 } 229 } 230 } 231 232 void AppsModelBuilder::InsertApp(ExtensionAppItem* app) { 233 DCHECK(model_); 234 235 size_t start = 0; 236 size_t end = model_->item_count(); 237 while (start < end) { 238 size_t mid = (start + end) / 2; 239 240 if (AppPrecedes(GetAppAt(mid), app)) 241 start = mid + 1; 242 else 243 end = mid; 244 } 245 model_->AddAt(start, app); 246 } 247 248 int AppsModelBuilder::FindApp(const std::string& app_id) { 249 DCHECK(model_); 250 251 for (size_t i = 0; i < model_->item_count(); ++i) { 252 if (GetAppAt(i)->extension_id() == app_id) 253 return i; 254 } 255 256 return -1; 257 } 258 259 void AppsModelBuilder::SetHighlightedApp(const std::string& extension_id) { 260 if (extension_id == highlight_app_id_) 261 return; 262 ExtensionAppItem* old_app = GetApp(highlight_app_id_); 263 if (old_app) 264 old_app->SetHighlighted(false); 265 highlight_app_id_ = extension_id; 266 ExtensionAppItem* new_app = GetApp(highlight_app_id_); 267 highlighted_app_pending_ = !new_app; 268 if (new_app) 269 new_app->SetHighlighted(true); 270 } 271 272 ExtensionAppItem* AppsModelBuilder::GetApp( 273 const std::string& extension_id) { 274 DCHECK(model_); 275 if (extension_id.empty()) 276 return NULL; 277 278 int index = FindApp(extension_id); 279 if (index == -1) 280 return NULL; 281 return GetAppAt(index); 282 } 283 284 void AppsModelBuilder::UpdateHighlight() { 285 DCHECK(model_); 286 if (!highlighted_app_pending_ || highlight_app_id_.empty()) 287 return; 288 289 int index = FindApp(highlight_app_id_); 290 if (index == -1) 291 return; 292 293 model_->GetItemAt(index)->SetHighlighted(true); 294 highlighted_app_pending_ = false; 295 } 296 297 ExtensionAppItem* AppsModelBuilder::GetAppAt(size_t index) { 298 DCHECK_LT(index, model_->item_count()); 299 ChromeAppListItem* item = 300 static_cast<ChromeAppListItem*>(model_->GetItemAt(index)); 301 DCHECK_EQ(item->type(), ChromeAppListItem::TYPE_APP); 302 303 return static_cast<ExtensionAppItem*>(item); 304 } 305 306 void AppsModelBuilder::ListItemsAdded(size_t start, size_t count) { 307 } 308 309 void AppsModelBuilder::ListItemsRemoved(size_t start, size_t count) { 310 } 311 312 void AppsModelBuilder::ListItemMoved(size_t index, size_t target_index) { 313 if (ignore_changes_) 314 return; 315 316 ExtensionAppItem* prev = target_index > 0 ? GetAppAt(target_index - 1) : NULL; 317 ExtensionAppItem* next = target_index + 1 < model_->item_count() ? 318 GetAppAt(target_index + 1) : NULL; 319 GetAppAt(target_index)->Move(prev, next); 320 } 321 322 void AppsModelBuilder::ListItemsChanged(size_t start, size_t count) { 323 NOTREACHED(); 324 } 325