extension_app_model_builder.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
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    if (service_)
153      service_->UpdateItem(existing_item);
154    return;
155  }
156
157  InsertApp(CreateAppItem(extension->id(),
158                          "",
159                          gfx::ImageSkia(),
160                          extension->is_platform_app()));
161  UpdateHighlight();
162}
163
164void ExtensionAppModelBuilder::OnExtensionUnloaded(const Extension* extension) {
165  ExtensionAppItem* item = GetExtensionAppItem(extension->id());
166  if (!item)
167    return;
168  item->UpdateIcon();
169}
170
171void ExtensionAppModelBuilder::OnExtensionUninstalled(
172    const Extension* extension) {
173  if (service_) {
174    DVLOG(2) << service_ << ": OnExtensionUninstalled: "
175             << extension->id().substr(0, 8);
176    service_->RemoveItem(extension->id());
177    return;
178  }
179  model_->DeleteItem(extension->id());
180}
181
182void ExtensionAppModelBuilder::OnDisabledExtensionUpdated(
183    const Extension* extension) {
184  if (!extension->ShouldDisplayInAppLauncher())
185    return;
186
187  ExtensionAppItem* existing_item = GetExtensionAppItem(extension->id());
188  if (existing_item)
189    existing_item->Reload();
190}
191
192void ExtensionAppModelBuilder::OnAppInstalledToAppList(
193    const std::string& extension_id) {
194  SetHighlightedApp(extension_id);
195}
196
197void ExtensionAppModelBuilder::OnShutdown() {
198  if (tracker_) {
199    tracker_->RemoveObserver(this);
200    tracker_ = NULL;
201  }
202}
203
204scoped_ptr<ExtensionAppItem> ExtensionAppModelBuilder::CreateAppItem(
205    const std::string& extension_id,
206    const std::string& extension_name,
207    const gfx::ImageSkia& installing_icon,
208    bool is_platform_app) {
209  const app_list::AppListSyncableService::SyncItem* sync_item =
210      service_ ? service_->GetSyncItem(extension_id) : NULL;
211  return make_scoped_ptr(new ExtensionAppItem(profile_,
212                                              sync_item,
213                                              extension_id,
214                                              extension_name,
215                                              installing_icon,
216                                              is_platform_app));
217}
218
219void ExtensionAppModelBuilder::BuildModel() {
220  DCHECK(!tracker_);
221  tracker_ = controller_->GetInstallTrackerFor(profile_);
222
223  PopulateApps();
224  UpdateHighlight();
225
226  // Start observing after model is built.
227  if (tracker_)
228    tracker_->AddObserver(this);
229}
230
231void ExtensionAppModelBuilder::PopulateApps() {
232  extensions::ExtensionSet extensions;
233  controller_->GetApps(profile_, &extensions);
234
235  for (extensions::ExtensionSet::const_iterator app = extensions.begin();
236       app != extensions.end(); ++app) {
237    if (!ShouldDisplayInAppLauncher(profile_, *app))
238      continue;
239    InsertApp(CreateAppItem((*app)->id(),
240                            "",
241                            gfx::ImageSkia(),
242                            (*app)->is_platform_app()));
243  }
244}
245
246void ExtensionAppModelBuilder::InsertApp(scoped_ptr<ExtensionAppItem> app) {
247  if (service_) {
248    service_->AddItem(app.PassAs<app_list::AppListItem>());
249    return;
250  }
251  model_->AddItem(app.PassAs<app_list::AppListItem>());
252}
253
254void ExtensionAppModelBuilder::SetHighlightedApp(
255    const std::string& extension_id) {
256  if (extension_id == highlight_app_id_)
257    return;
258  ExtensionAppItem* old_app = GetExtensionAppItem(highlight_app_id_);
259  if (old_app)
260    old_app->SetHighlighted(false);
261  highlight_app_id_ = extension_id;
262  ExtensionAppItem* new_app = GetExtensionAppItem(highlight_app_id_);
263  highlighted_app_pending_ = !new_app;
264  if (new_app)
265    new_app->SetHighlighted(true);
266}
267
268ExtensionAppItem* ExtensionAppModelBuilder::GetExtensionAppItem(
269    const std::string& extension_id) {
270  app_list::AppListItem* item = model_->FindItem(extension_id);
271  LOG_IF(ERROR, item &&
272         item->GetItemType() != ExtensionAppItem::kItemType)
273      << "App Item matching id: " << extension_id
274      << " has incorrect type: '" << item->GetItemType() << "'";
275  return static_cast<ExtensionAppItem*>(item);
276}
277
278void ExtensionAppModelBuilder::UpdateHighlight() {
279  DCHECK(model_);
280  if (!highlighted_app_pending_ || highlight_app_id_.empty())
281    return;
282  ExtensionAppItem* item = GetExtensionAppItem(highlight_app_id_);
283  if (!item)
284    return;
285  item->SetHighlighted(true);
286  highlighted_app_pending_ = false;
287}
288
289void ExtensionAppModelBuilder::OnListItemMoved(size_t from_index,
290                                               size_t to_index,
291                                               app_list::AppListItem* item) {
292  DCHECK(!service_);
293
294  // This will get called from AppListItemList::ListItemMoved after
295  // set_position is called for the item.
296  if (item->GetItemType() != ExtensionAppItem::kItemType)
297    return;
298
299  app_list::AppListItemList* item_list = model_->top_level_item_list();
300  ExtensionAppItem* prev = NULL;
301  for (size_t idx = to_index; idx > 0; --idx) {
302    app_list::AppListItem* item = item_list->item_at(idx - 1);
303    if (item->GetItemType() == ExtensionAppItem::kItemType) {
304      prev = static_cast<ExtensionAppItem*>(item);
305      break;
306    }
307  }
308  ExtensionAppItem* next = NULL;
309  for (size_t idx = to_index; idx < item_list->item_count() - 1; ++idx) {
310    app_list::AppListItem* item = item_list->item_at(idx + 1);
311    if (item->GetItemType() == ExtensionAppItem::kItemType) {
312      next = static_cast<ExtensionAppItem*>(item);
313      break;
314    }
315  }
316  // item->Move will call set_position, overriding the item's position.
317  if (prev || next)
318    static_cast<ExtensionAppItem*>(item)->Move(prev, next);
319}
320