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