extension_app_model_builder.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
13c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// Copyright (c) 2012 The Chromium Authors. All rights reserved. 23c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// Use of this source code is governed by a BSD-style license that can be 33c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// found in the LICENSE file. 43c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 53c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/app_list/extension_app_model_builder.h" 63c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 73c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include <algorithm> 83c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 93c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "base/auto_reset.h" 103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "base/callback.h" 113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "base/command_line.h" 123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "base/prefs/pref_service.h" 133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/chrome_notification_types.h" 143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/extension_service.h" 153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/install_tracker.h" 163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/install_tracker_factory.h" 173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/profiles/profile.h" 183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" 193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/app_list/app_list_syncable_service.h" 203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h" 213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/app_list/extension_app_item.h" 223c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/common/chrome_switches.h" 233c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/common/extensions/extension_constants.h" 243c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/common/pref_names.h" 253c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "content/public/browser/notification_service.h" 263c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/extension_prefs.h" 273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/extension_system.h" 283c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/pref_names.h" 293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/common/extension.h" 303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/common/extension_set.h" 313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/gfx/image/image_skia.h" 323c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 333c827367444ee418f129b2c238299f49d3264554Jarkko Poyryusing extensions::Extension; 343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 353c827367444ee418f129b2c238299f49d3264554Jarkko Poyrynamespace { 363c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 373c827367444ee418f129b2c238299f49d3264554Jarkko Poyrybool ShouldDisplayInAppLauncher(Profile* profile, 383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry scoped_refptr<const Extension> app) { 393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry // If it's the web store, check the policy. 403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry bool blocked_by_policy = 413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry (app->id() == extension_misc::kWebStoreAppId || 423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry app->id() == extension_misc::kEnterpriseWebStoreAppId) && 433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry profile->GetPrefs()->GetBoolean(prefs::kHideWebStoreIcon); 443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry return app->ShouldDisplayInAppLauncher() && !blocked_by_policy; 453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry} 463c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry} // namespace 483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 493c827367444ee418f129b2c238299f49d3264554Jarkko PoyryExtensionAppModelBuilder::ExtensionAppModelBuilder( 503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry AppListControllerDelegate* controller) 513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry : service_(NULL), 523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry profile_(NULL), 533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry controller_(controller), 54 model_(NULL), 55 highlighted_app_pending_(false), 56 tracker_(NULL) { 57} 58 59ExtensionAppModelBuilder::~ExtensionAppModelBuilder() { 60 OnShutdown(); 61 model_->item_list()->RemoveObserver(this); 62} 63 64void ExtensionAppModelBuilder::InitializeWithService( 65 app_list::AppListSyncableService* service) { 66 DCHECK(!service_ && !profile_); 67 model_ = service->model(); 68 model_->item_list()->AddObserver(this); 69 service_ = service; 70 profile_ = service->profile(); 71 InitializePrefChangeRegistrar(); 72 73 BuildModel(); 74} 75 76void ExtensionAppModelBuilder::InitializeWithProfile( 77 Profile* profile, 78 app_list::AppListModel* model) { 79 DCHECK(!service_ && !profile_); 80 model_ = model; 81 model_->item_list()->AddObserver(this); 82 profile_ = profile; 83 InitializePrefChangeRegistrar(); 84 85 BuildModel(); 86} 87 88void ExtensionAppModelBuilder::InitializePrefChangeRegistrar() { 89 if (!CommandLine::ForCurrentProcess()->HasSwitch( 90 switches::kEnableStreamlinedHostedApps)) 91 return; 92 93 const ExtensionService* extension_service = 94 extensions::ExtensionSystem::Get(profile_)->extension_service(); 95 if (!extension_service) 96 return; 97 98 extension_pref_change_registrar_.Init( 99 extension_service->extension_prefs()->pref_service()); 100 extension_pref_change_registrar_.Add( 101 extensions::pref_names::kExtensions, 102 base::Bind(&ExtensionAppModelBuilder::OnExtensionPreferenceChanged, 103 base::Unretained(this))); 104} 105 106void ExtensionAppModelBuilder::OnExtensionPreferenceChanged() { 107 // TODO(calamity): analyze the performance impact of doing this every 108 // extension pref change. 109 app_list::AppListItemList* item_list = model_->item_list(); 110 for (size_t i = 0; i < item_list->item_count(); ++i) { 111 app_list::AppListItem* item = item_list->item_at(i); 112 if (item->GetItemType() != ExtensionAppItem::kItemType) 113 continue; 114 115 static_cast<ExtensionAppItem*>(item)->UpdateIconOverlay(); 116 } 117} 118 119void ExtensionAppModelBuilder::OnBeginExtensionInstall( 120 const ExtensionInstallParams& params) { 121 if (!params.is_app || params.is_ephemeral) 122 return; 123 124 DVLOG(2) << service_ << ": OnBeginExtensionInstall: " 125 << params.extension_id.substr(0, 8); 126 ExtensionAppItem* existing_item = GetExtensionAppItem(params.extension_id); 127 if (existing_item) { 128 existing_item->SetIsInstalling(true); 129 return; 130 } 131 InsertApp(CreateAppItem(params.extension_id, 132 params.extension_name, 133 params.installing_icon, 134 params.is_platform_app)); 135 SetHighlightedApp(params.extension_id); 136} 137 138void ExtensionAppModelBuilder::OnDownloadProgress( 139 const std::string& extension_id, 140 int percent_downloaded) { 141 ExtensionAppItem* item = GetExtensionAppItem(extension_id); 142 if (!item) 143 return; 144 item->SetPercentDownloaded(percent_downloaded); 145} 146 147void ExtensionAppModelBuilder::OnInstallFailure( 148 const std::string& extension_id) { 149 model_->DeleteItem(extension_id); 150} 151 152void ExtensionAppModelBuilder::OnExtensionLoaded(const Extension* extension) { 153 if (!extension->ShouldDisplayInAppLauncher()) 154 return; 155 156 DVLOG(2) << service_ << ": OnExtensionLoaded: " 157 << extension->id().substr(0, 8); 158 ExtensionAppItem* existing_item = GetExtensionAppItem(extension->id()); 159 if (existing_item) { 160 existing_item->Reload(); 161 return; 162 } 163 164 InsertApp(CreateAppItem(extension->id(), 165 "", 166 gfx::ImageSkia(), 167 extension->is_platform_app())); 168 UpdateHighlight(); 169} 170 171void ExtensionAppModelBuilder::OnExtensionUnloaded(const Extension* extension) { 172 ExtensionAppItem* item = GetExtensionAppItem(extension->id()); 173 if (!item) 174 return; 175 item->UpdateIcon(); 176} 177 178void ExtensionAppModelBuilder::OnExtensionUninstalled( 179 const Extension* extension) { 180 if (service_) { 181 DVLOG(2) << service_ << ": OnExtensionUninstalled: " 182 << extension->id().substr(0, 8); 183 service_->RemoveItem(extension->id()); 184 return; 185 } 186 model_->DeleteItem(extension->id()); 187} 188 189void ExtensionAppModelBuilder::OnAppsReordered() { 190 // Do nothing; App List order does not track extensions order. 191} 192 193void ExtensionAppModelBuilder::OnAppInstalledToAppList( 194 const std::string& extension_id) { 195 SetHighlightedApp(extension_id); 196} 197 198void ExtensionAppModelBuilder::OnShutdown() { 199 if (tracker_) { 200 tracker_->RemoveObserver(this); 201 tracker_ = NULL; 202 } 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 224 PopulateApps(); 225 UpdateHighlight(); 226 227 // Start observing after model is built. 228 if (tracker_) 229 tracker_->AddObserver(this); 230} 231 232void ExtensionAppModelBuilder::PopulateApps() { 233 extensions::ExtensionSet extensions; 234 controller_->GetApps(profile_, &extensions); 235 236 for (extensions::ExtensionSet::const_iterator app = extensions.begin(); 237 app != extensions.end(); ++app) { 238 if (!ShouldDisplayInAppLauncher(profile_, *app)) 239 continue; 240 InsertApp(CreateAppItem((*app)->id(), 241 "", 242 gfx::ImageSkia(), 243 (*app)->is_platform_app())); 244 } 245} 246 247void ExtensionAppModelBuilder::InsertApp(scoped_ptr<ExtensionAppItem> app) { 248 if (service_) { 249 service_->AddItem(app.PassAs<app_list::AppListItem>()); 250 return; 251 } 252 model_->AddItem(app.PassAs<app_list::AppListItem>()); 253} 254 255void ExtensionAppModelBuilder::SetHighlightedApp( 256 const std::string& extension_id) { 257 if (extension_id == highlight_app_id_) 258 return; 259 ExtensionAppItem* old_app = GetExtensionAppItem(highlight_app_id_); 260 if (old_app) 261 old_app->SetHighlighted(false); 262 highlight_app_id_ = extension_id; 263 ExtensionAppItem* new_app = GetExtensionAppItem(highlight_app_id_); 264 highlighted_app_pending_ = !new_app; 265 if (new_app) 266 new_app->SetHighlighted(true); 267} 268 269ExtensionAppItem* ExtensionAppModelBuilder::GetExtensionAppItem( 270 const std::string& extension_id) { 271 app_list::AppListItem* item = 272 model_->item_list()->FindItem(extension_id); 273 LOG_IF(ERROR, item && 274 item->GetItemType() != ExtensionAppItem::kItemType) 275 << "App Item matching id: " << extension_id 276 << " has incorrect type: '" << item->GetItemType() << "'"; 277 return static_cast<ExtensionAppItem*>(item); 278} 279 280void ExtensionAppModelBuilder::UpdateHighlight() { 281 DCHECK(model_); 282 if (!highlighted_app_pending_ || highlight_app_id_.empty()) 283 return; 284 ExtensionAppItem* item = GetExtensionAppItem(highlight_app_id_); 285 if (!item) 286 return; 287 item->SetHighlighted(true); 288 highlighted_app_pending_ = false; 289} 290 291void ExtensionAppModelBuilder::OnListItemMoved(size_t from_index, 292 size_t to_index, 293 app_list::AppListItem* item) { 294 // This will get called from AppListItemList::ListItemMoved after 295 // set_position is called for the item. 296 app_list::AppListItemList* item_list = model_->item_list(); 297 if (item->GetItemType() != ExtensionAppItem::kItemType) 298 return; 299 300 if (service_) 301 return; 302 303 ExtensionAppItem* prev = NULL; 304 for (size_t idx = to_index; idx > 0; --idx) { 305 app_list::AppListItem* item = item_list->item_at(idx - 1); 306 if (item->GetItemType() == ExtensionAppItem::kItemType) { 307 prev = static_cast<ExtensionAppItem*>(item); 308 break; 309 } 310 } 311 ExtensionAppItem* next = NULL; 312 for (size_t idx = to_index; idx < item_list->item_count() - 1; ++idx) { 313 app_list::AppListItem* item = item_list->item_at(idx + 1); 314 if (item->GetItemType() == ExtensionAppItem::kItemType) { 315 next = static_cast<ExtensionAppItem*>(item); 316 break; 317 } 318 } 319 // item->Move will call set_position, overriding the item's position. 320 if (prev || next) 321 static_cast<ExtensionAppItem*>(item)->Move(prev, next); 322} 323