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