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