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