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