extension_app_model_builder.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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/extension_app_model_builder.h" 6 7#include <algorithm> 8 9#include "base/auto_reset.h" 10#include "base/bind.h" 11#include "base/callback.h" 12#include "base/command_line.h" 13#include "chrome/browser/extensions/extension_ui_util.h" 14#include "chrome/browser/extensions/install_tracker.h" 15#include "chrome/browser/extensions/install_tracker_factory.h" 16#include "chrome/browser/profiles/profile.h" 17#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" 18#include "chrome/browser/ui/app_list/app_list_syncable_service.h" 19#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h" 20#include "chrome/browser/ui/app_list/extension_app_item.h" 21#include "chrome/common/chrome_switches.h" 22#include "chrome/common/pref_names.h" 23#include "extensions/browser/extension_prefs.h" 24#include "extensions/browser/extension_registry.h" 25#include "extensions/browser/extension_system.h" 26#include "extensions/browser/extensions_browser_client.h" 27#include "extensions/browser/pref_names.h" 28#include "extensions/common/constants.h" 29#include "extensions/common/extension.h" 30#include "extensions/common/extension_set.h" 31#include "ui/gfx/image/image_skia.h" 32#include "ui/gfx/image/image_skia_operations.h" 33 34using extensions::Extension; 35 36ExtensionAppModelBuilder::ExtensionAppModelBuilder( 37 AppListControllerDelegate* controller) 38 : service_(NULL), 39 profile_(NULL), 40 controller_(controller), 41 model_(NULL), 42 highlighted_app_pending_(false), 43 tracker_(NULL), 44 extension_registry_(NULL) { 45} 46 47ExtensionAppModelBuilder::~ExtensionAppModelBuilder() { 48 OnShutdown(); 49 OnShutdown(extension_registry_); 50 if (!service_) 51 model_->top_level_item_list()->RemoveObserver(this); 52} 53 54void ExtensionAppModelBuilder::InitializeWithService( 55 app_list::AppListSyncableService* service) { 56 DCHECK(!service_ && !profile_); 57 model_ = service->model(); 58 service_ = service; 59 profile_ = service->profile(); 60 InitializePrefChangeRegistrars(); 61 62 BuildModel(); 63} 64 65void ExtensionAppModelBuilder::InitializeWithProfile( 66 Profile* profile, 67 app_list::AppListModel* model) { 68 DCHECK(!service_ && !profile_); 69 model_ = model; 70 model_->top_level_item_list()->AddObserver(this); 71 profile_ = profile; 72 InitializePrefChangeRegistrars(); 73 74 BuildModel(); 75} 76 77void ExtensionAppModelBuilder::InitializePrefChangeRegistrars() { 78 profile_pref_change_registrar_.Init(profile_->GetPrefs()); 79 profile_pref_change_registrar_.Add( 80 prefs::kHideWebStoreIcon, 81 base::Bind(&ExtensionAppModelBuilder::OnProfilePreferenceChanged, 82 base::Unretained(this))); 83 84 if (!CommandLine::ForCurrentProcess()->HasSwitch( 85 switches::kEnableStreamlinedHostedApps)) 86 return; 87 88 // TODO(calamity): analyze the performance impact of doing this every 89 // extension pref change. 90 extensions::ExtensionsBrowserClient* client = 91 extensions::ExtensionsBrowserClient::Get(); 92 extension_pref_change_registrar_.Init( 93 client->GetPrefServiceForContext(profile_)); 94 extension_pref_change_registrar_.Add( 95 extensions::pref_names::kExtensions, 96 base::Bind(&ExtensionAppModelBuilder::OnExtensionPreferenceChanged, 97 base::Unretained(this))); 98} 99 100void ExtensionAppModelBuilder::OnProfilePreferenceChanged() { 101 extensions::ExtensionSet extensions; 102 controller_->GetApps(profile_, &extensions); 103 104 for (extensions::ExtensionSet::const_iterator app = extensions.begin(); 105 app != extensions.end(); ++app) { 106 bool should_display = 107 extensions::ui_util::ShouldDisplayInAppLauncher(*app, profile_); 108 bool does_display = GetExtensionAppItem((*app)->id()) != NULL; 109 110 if (should_display == does_display) 111 continue; 112 113 if (should_display) { 114 InsertApp(CreateAppItem((*app)->id(), 115 "", 116 gfx::ImageSkia(), 117 (*app)->is_platform_app())); 118 } else { 119 if (service_) 120 service_->RemoveItem((*app)->id()); 121 else 122 model_->DeleteItem((*app)->id()); 123 } 124 } 125} 126 127void ExtensionAppModelBuilder::OnExtensionPreferenceChanged() { 128 model_->NotifyExtensionPreferenceChanged(); 129} 130 131void ExtensionAppModelBuilder::OnBeginExtensionInstall( 132 const ExtensionInstallParams& params) { 133 if (!params.is_app || params.is_ephemeral) 134 return; 135 136 DVLOG(2) << service_ << ": OnBeginExtensionInstall: " 137 << params.extension_id.substr(0, 8); 138 ExtensionAppItem* existing_item = GetExtensionAppItem(params.extension_id); 139 if (existing_item) { 140 existing_item->SetIsInstalling(true); 141 return; 142 } 143 144 // Icons from the webstore can be unusual sizes. Once installed, 145 // ExtensionAppItem uses extension_misc::EXTENSION_ICON_MEDIUM (48) to load 146 // it, so be consistent with that. 147 gfx::Size icon_size(extension_misc::EXTENSION_ICON_MEDIUM, 148 extension_misc::EXTENSION_ICON_MEDIUM); 149 gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage( 150 params.installing_icon, skia::ImageOperations::RESIZE_BEST, icon_size)); 151 152 InsertApp(CreateAppItem(params.extension_id, 153 params.extension_name, 154 resized, 155 params.is_platform_app)); 156 SetHighlightedApp(params.extension_id); 157} 158 159void ExtensionAppModelBuilder::OnDownloadProgress( 160 const std::string& extension_id, 161 int percent_downloaded) { 162 ExtensionAppItem* item = GetExtensionAppItem(extension_id); 163 if (!item) 164 return; 165 item->SetPercentDownloaded(percent_downloaded); 166} 167 168void ExtensionAppModelBuilder::OnInstallFailure( 169 const std::string& extension_id) { 170 model_->DeleteItem(extension_id); 171} 172 173void ExtensionAppModelBuilder::OnExtensionLoaded( 174 content::BrowserContext* browser_context, 175 const extensions::Extension* extension) { 176 if (!extensions::ui_util::ShouldDisplayInAppLauncher(extension, profile_)) 177 return; 178 179 DVLOG(2) << service_ << ": OnExtensionLoaded: " 180 << extension->id().substr(0, 8); 181 ExtensionAppItem* existing_item = GetExtensionAppItem(extension->id()); 182 if (existing_item) { 183 existing_item->Reload(); 184 if (service_) 185 service_->UpdateItem(existing_item); 186 return; 187 } 188 189 InsertApp(CreateAppItem(extension->id(), 190 "", 191 gfx::ImageSkia(), 192 extension->is_platform_app())); 193 UpdateHighlight(); 194} 195 196void ExtensionAppModelBuilder::OnExtensionUnloaded( 197 content::BrowserContext* browser_context, 198 const extensions::Extension* extension, 199 extensions::UnloadedExtensionInfo::Reason reason) { 200 ExtensionAppItem* item = GetExtensionAppItem(extension->id()); 201 if (!item) 202 return; 203 item->UpdateIcon(); 204} 205 206void ExtensionAppModelBuilder::OnExtensionUninstalled( 207 content::BrowserContext* browser_context, 208 const extensions::Extension* extension, 209 extensions::UninstallReason reason) { 210 if (service_) { 211 DVLOG(2) << service_ << ": OnExtensionUninstalled: " 212 << extension->id().substr(0, 8); 213 service_->RemoveItem(extension->id()); 214 return; 215 } 216 model_->DeleteItem(extension->id()); 217} 218 219void ExtensionAppModelBuilder::OnDisabledExtensionUpdated( 220 const Extension* extension) { 221 if (!extensions::ui_util::ShouldDisplayInAppLauncher(extension, profile_)) 222 return; 223 224 ExtensionAppItem* existing_item = GetExtensionAppItem(extension->id()); 225 if (existing_item) 226 existing_item->Reload(); 227} 228 229void ExtensionAppModelBuilder::OnAppInstalledToAppList( 230 const std::string& extension_id) { 231 SetHighlightedApp(extension_id); 232} 233 234void ExtensionAppModelBuilder::OnShutdown() { 235 if (tracker_) { 236 tracker_->RemoveObserver(this); 237 tracker_ = NULL; 238 } 239} 240 241void ExtensionAppModelBuilder::OnShutdown( 242 extensions::ExtensionRegistry* registry) { 243 if (!extension_registry_) 244 return; 245 246 DCHECK_EQ(extension_registry_, registry); 247 extension_registry_->RemoveObserver(this); 248 extension_registry_ = NULL; 249} 250 251scoped_ptr<ExtensionAppItem> ExtensionAppModelBuilder::CreateAppItem( 252 const std::string& extension_id, 253 const std::string& extension_name, 254 const gfx::ImageSkia& installing_icon, 255 bool is_platform_app) { 256 const app_list::AppListSyncableService::SyncItem* sync_item = 257 service_ ? service_->GetSyncItem(extension_id) : NULL; 258 return make_scoped_ptr(new ExtensionAppItem(profile_, 259 sync_item, 260 extension_id, 261 extension_name, 262 installing_icon, 263 is_platform_app)); 264} 265 266void ExtensionAppModelBuilder::BuildModel() { 267 DCHECK(!tracker_); 268 tracker_ = controller_->GetInstallTrackerFor(profile_); 269 extension_registry_ = extensions::ExtensionRegistry::Get(profile_); 270 271 PopulateApps(); 272 UpdateHighlight(); 273 274 // Start observing after model is built. 275 if (tracker_) 276 tracker_->AddObserver(this); 277 278 if (extension_registry_) 279 extension_registry_->AddObserver(this); 280} 281 282void ExtensionAppModelBuilder::PopulateApps() { 283 extensions::ExtensionSet extensions; 284 controller_->GetApps(profile_, &extensions); 285 286 for (extensions::ExtensionSet::const_iterator app = extensions.begin(); 287 app != extensions.end(); ++app) { 288 if (!extensions::ui_util::ShouldDisplayInAppLauncher(*app, profile_)) 289 continue; 290 InsertApp(CreateAppItem((*app)->id(), 291 "", 292 gfx::ImageSkia(), 293 (*app)->is_platform_app())); 294 } 295} 296 297void ExtensionAppModelBuilder::InsertApp(scoped_ptr<ExtensionAppItem> app) { 298 if (service_) { 299 service_->AddItem(app.PassAs<app_list::AppListItem>()); 300 return; 301 } 302 model_->AddItem(app.PassAs<app_list::AppListItem>()); 303} 304 305void ExtensionAppModelBuilder::SetHighlightedApp( 306 const std::string& extension_id) { 307 if (extension_id == highlight_app_id_) 308 return; 309 ExtensionAppItem* old_app = GetExtensionAppItem(highlight_app_id_); 310 if (old_app) 311 old_app->SetHighlighted(false); 312 highlight_app_id_ = extension_id; 313 ExtensionAppItem* new_app = GetExtensionAppItem(highlight_app_id_); 314 highlighted_app_pending_ = !new_app; 315 if (new_app) 316 new_app->SetHighlighted(true); 317} 318 319ExtensionAppItem* ExtensionAppModelBuilder::GetExtensionAppItem( 320 const std::string& extension_id) { 321 app_list::AppListItem* item = model_->FindItem(extension_id); 322 LOG_IF(ERROR, item && 323 item->GetItemType() != ExtensionAppItem::kItemType) 324 << "App Item matching id: " << extension_id 325 << " has incorrect type: '" << item->GetItemType() << "'"; 326 return static_cast<ExtensionAppItem*>(item); 327} 328 329void ExtensionAppModelBuilder::UpdateHighlight() { 330 DCHECK(model_); 331 if (!highlighted_app_pending_ || highlight_app_id_.empty()) 332 return; 333 ExtensionAppItem* item = GetExtensionAppItem(highlight_app_id_); 334 if (!item) 335 return; 336 item->SetHighlighted(true); 337 highlighted_app_pending_ = false; 338} 339 340void ExtensionAppModelBuilder::OnListItemMoved(size_t from_index, 341 size_t to_index, 342 app_list::AppListItem* item) { 343 DCHECK(!service_); 344 345 // This will get called from AppListItemList::ListItemMoved after 346 // set_position is called for the item. 347 if (item->GetItemType() != ExtensionAppItem::kItemType) 348 return; 349 350 app_list::AppListItemList* item_list = model_->top_level_item_list(); 351 ExtensionAppItem* prev = NULL; 352 for (size_t idx = to_index; idx > 0; --idx) { 353 app_list::AppListItem* item = item_list->item_at(idx - 1); 354 if (item->GetItemType() == ExtensionAppItem::kItemType) { 355 prev = static_cast<ExtensionAppItem*>(item); 356 break; 357 } 358 } 359 ExtensionAppItem* next = NULL; 360 for (size_t idx = to_index; idx < item_list->item_count() - 1; ++idx) { 361 app_list::AppListItem* item = item_list->item_at(idx + 1); 362 if (item->GetItemType() == ExtensionAppItem::kItemType) { 363 next = static_cast<ExtensionAppItem*>(item); 364 break; 365 } 366 } 367 // item->Move will call set_position, overriding the item's position. 368 if (prev || next) 369 static_cast<ExtensionAppItem*>(item)->Move(prev, next); 370} 371