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