bookmark_app_helper.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
1// Copyright 2014 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/extensions/bookmark_app_helper.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/browser/chrome_notification_types.h"
9#include "chrome/browser/extensions/crx_installer.h"
10#include "chrome/browser/extensions/extension_service.h"
11#include "chrome/browser/extensions/favicon_downloader.h"
12#include "chrome/browser/extensions/tab_helper.h"
13#include "chrome/common/extensions/extension_constants.h"
14#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
15#include "content/public/browser/notification_service.h"
16#include "content/public/browser/notification_source.h"
17#include "content/public/browser/web_contents.h"
18#include "extensions/common/extension.h"
19#include "skia/ext/image_operations.h"
20#include "skia/ext/platform_canvas.h"
21#include "third_party/skia/include/core/SkBitmap.h"
22#include "ui/gfx/color_analysis.h"
23#include "ui/gfx/image/image.h"
24
25namespace extensions {
26
27// static
28std::map<int, SkBitmap> BookmarkAppHelper::ConstrainBitmapsToSizes(
29    const std::vector<SkBitmap>& bitmaps,
30    const std::set<int>& sizes) {
31  std::map<int, SkBitmap> output_bitmaps;
32  std::map<int, SkBitmap> ordered_bitmaps;
33  for (std::vector<SkBitmap>::const_iterator it = bitmaps.begin();
34       it != bitmaps.end();
35       ++it) {
36    DCHECK(it->width() == it->height());
37    ordered_bitmaps[it->width()] = *it;
38  }
39
40  std::set<int>::const_iterator sizes_it = sizes.begin();
41  std::map<int, SkBitmap>::const_iterator bitmaps_it = ordered_bitmaps.begin();
42  while (sizes_it != sizes.end() && bitmaps_it != ordered_bitmaps.end()) {
43    int size = *sizes_it;
44    // Find the closest not-smaller bitmap.
45    bitmaps_it = ordered_bitmaps.lower_bound(size);
46    ++sizes_it;
47    // Ensure the bitmap is valid and smaller than the next allowed size.
48    if (bitmaps_it != ordered_bitmaps.end() &&
49        (sizes_it == sizes.end() || bitmaps_it->second.width() < *sizes_it)) {
50      // Resize the bitmap if it does not exactly match the desired size.
51      output_bitmaps[size] = bitmaps_it->second.width() == size
52                                 ? bitmaps_it->second
53                                 : skia::ImageOperations::Resize(
54                                       bitmaps_it->second,
55                                       skia::ImageOperations::RESIZE_LANCZOS3,
56                                       size,
57                                       size);
58    }
59  }
60  return output_bitmaps;
61}
62
63// static
64void BookmarkAppHelper::GenerateContainerIcon(std::map<int, SkBitmap>* bitmaps,
65                                              int output_size) {
66  std::map<int, SkBitmap>::const_iterator it =
67      bitmaps->lower_bound(output_size);
68  // Do nothing if there is no icon smaller than the desired size or there is
69  // already an icon of |output_size|.
70  if (it == bitmaps->begin() || bitmaps->count(output_size))
71    return;
72
73  --it;
74  // This is the biggest icon smaller than |output_size|.
75  const SkBitmap& base_icon = it->second;
76
77  const size_t kBorderRadius = 5;
78  const size_t kColorStripHeight = 3;
79  const SkColor kBorderColor = 0xFFD5D5D5;
80  const SkColor kBackgroundColor = 0xFFFFFFFF;
81
82  // Create a separate canvas for the color strip.
83  scoped_ptr<SkCanvas> color_strip_canvas(
84      skia::CreateBitmapCanvas(output_size, output_size, false));
85  DCHECK(color_strip_canvas);
86
87  // Draw a rounded rect of the |base_icon|'s dominant color.
88  SkPaint color_strip_paint;
89  color_utils::GridSampler sampler;
90  color_strip_paint.setFlags(SkPaint::kAntiAlias_Flag);
91  color_strip_paint.setColor(color_utils::CalculateKMeanColorOfPNG(
92      gfx::Image::CreateFrom1xBitmap(base_icon).As1xPNGBytes(),
93      100,
94      665,
95      &sampler));
96  color_strip_canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size),
97                                    kBorderRadius,
98                                    kBorderRadius,
99                                    color_strip_paint);
100
101  // Erase the top of the rounded rect to leave a color strip.
102  SkPaint clear_paint;
103  clear_paint.setColor(SK_ColorTRANSPARENT);
104  clear_paint.setXfermodeMode(SkXfermode::kSrc_Mode);
105  color_strip_canvas->drawRect(
106      SkRect::MakeWH(output_size, output_size - kColorStripHeight),
107      clear_paint);
108
109  // Draw each element to an output canvas.
110  scoped_ptr<SkCanvas> canvas(
111      skia::CreateBitmapCanvas(output_size, output_size, false));
112  DCHECK(canvas);
113
114  // Draw the background.
115  SkPaint background_paint;
116  background_paint.setColor(kBackgroundColor);
117  background_paint.setFlags(SkPaint::kAntiAlias_Flag);
118  canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size),
119                        kBorderRadius,
120                        kBorderRadius,
121                        background_paint);
122
123  // Draw the color strip.
124  canvas->drawBitmap(
125      color_strip_canvas->getDevice()->accessBitmap(false), 0, 0);
126
127  // Draw the border.
128  SkPaint border_paint;
129  border_paint.setColor(kBorderColor);
130  border_paint.setStyle(SkPaint::kStroke_Style);
131  border_paint.setFlags(SkPaint::kAntiAlias_Flag);
132  canvas->drawRoundRect(SkRect::MakeWH(output_size, output_size),
133                        kBorderRadius,
134                        kBorderRadius,
135                        border_paint);
136
137  // Draw the centered base icon to the output canvas.
138  canvas->drawBitmap(base_icon,
139                     (output_size - base_icon.width()) / 2,
140                     (output_size - base_icon.height()) / 2);
141
142  const SkBitmap& generated_icon = canvas->getDevice()->accessBitmap(false);
143  generated_icon.deepCopyTo(&(*bitmaps)[output_size]);
144}
145
146BookmarkAppHelper::BookmarkAppHelper(ExtensionService* service,
147                                     WebApplicationInfo web_app_info,
148                                     content::WebContents* contents)
149    : web_app_info_(web_app_info),
150      crx_installer_(extensions::CrxInstaller::CreateSilent(service)) {
151  registrar_.Add(this,
152                 chrome::NOTIFICATION_CRX_INSTALLER_DONE,
153                 content::Source<CrxInstaller>(crx_installer_.get()));
154
155  registrar_.Add(this,
156                 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
157                 content::Source<CrxInstaller>(crx_installer_.get()));
158
159  crx_installer_->set_error_on_unsupported_requirements(true);
160
161  // Add urls from the WebApplicationInfo.
162  std::vector<GURL> web_app_info_icon_urls;
163  for (std::vector<WebApplicationInfo::IconInfo>::const_iterator it =
164           web_app_info_.icons.begin();
165       it != web_app_info_.icons.end();
166       ++it) {
167    if (it->url.is_valid())
168      web_app_info_icon_urls.push_back(it->url);
169  }
170
171  favicon_downloader_.reset(
172      new FaviconDownloader(contents,
173                            web_app_info_icon_urls,
174                            base::Bind(&BookmarkAppHelper::OnIconsDownloaded,
175                                       base::Unretained(this))));
176}
177
178BookmarkAppHelper::~BookmarkAppHelper() {}
179
180void BookmarkAppHelper::Create(const CreateBookmarkAppCallback& callback) {
181  callback_ = callback;
182  favicon_downloader_->Start();
183}
184
185void BookmarkAppHelper::OnIconsDownloaded(
186    bool success,
187    const std::map<GURL, std::vector<SkBitmap> >& bitmaps) {
188  // The tab has navigated away during the icon download. Cancel the bookmark
189  // app creation.
190  if (!success) {
191    favicon_downloader_.reset();
192    callback_.Run(NULL, web_app_info_);
193    return;
194  }
195
196  // Add the downloaded icons. Extensions only allow certain icon sizes. First
197  // populate icons that match the allowed sizes exactly and then downscale
198  // remaining icons to the closest allowed size that doesn't yet have an icon.
199  std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes,
200                              extension_misc::kExtensionIconSizes +
201                                  extension_misc::kNumExtensionIconSizes);
202  std::vector<SkBitmap> downloaded_icons;
203  for (FaviconDownloader::FaviconMap::const_iterator map_it = bitmaps.begin();
204       map_it != bitmaps.end();
205       ++map_it) {
206    for (std::vector<SkBitmap>::const_iterator bitmap_it =
207             map_it->second.begin();
208         bitmap_it != map_it->second.end();
209         ++bitmap_it) {
210      if (bitmap_it->empty() || bitmap_it->width() != bitmap_it->height())
211        continue;
212
213      downloaded_icons.push_back(*bitmap_it);
214    }
215  }
216
217  // If there are icons that don't match the accepted icon sizes, find the
218  // closest bigger icon to the accepted sizes and resize the icon to it. An
219  // icon will be resized and used for at most one size.
220  std::map<int, SkBitmap> resized_bitmaps(
221      ConstrainBitmapsToSizes(downloaded_icons, allowed_sizes));
222
223  // Generate container icons from smaller icons.
224  const int kIconSizesToGenerate[] = {extension_misc::EXTENSION_ICON_SMALL,
225                                      extension_misc::EXTENSION_ICON_MEDIUM, };
226  const std::set<int> generate_sizes(
227      kIconSizesToGenerate,
228      kIconSizesToGenerate + arraysize(kIconSizesToGenerate));
229
230  // Only generate icons if larger icons don't exist. This means the app
231  // launcher and the taskbar will do their best downsizing large icons and
232  // these container icons are only generated as a last resort against upscaling
233  // a smaller icon.
234  if (resized_bitmaps.lower_bound(*generate_sizes.rbegin()) ==
235      resized_bitmaps.end()) {
236    // Generate these from biggest to smallest so we don't end up with
237    // concentric container icons.
238    for (std::set<int>::const_reverse_iterator it = generate_sizes.rbegin();
239         it != generate_sizes.rend();
240         ++it) {
241      GenerateContainerIcon(&resized_bitmaps, *it);
242    }
243  }
244
245  // Populate the icon data into the WebApplicationInfo we are using to
246  // install the bookmark app.
247  for (std::map<int, SkBitmap>::const_iterator resized_bitmaps_it =
248           resized_bitmaps.begin();
249       resized_bitmaps_it != resized_bitmaps.end();
250       ++resized_bitmaps_it) {
251    WebApplicationInfo::IconInfo icon_info;
252    icon_info.data = resized_bitmaps_it->second;
253    icon_info.width = icon_info.data.width();
254    icon_info.height = icon_info.data.height();
255    web_app_info_.icons.push_back(icon_info);
256  }
257
258  // Install the app.
259  crx_installer_->InstallWebApp(web_app_info_);
260  favicon_downloader_.reset();
261}
262
263void BookmarkAppHelper::Observe(int type,
264                                const content::NotificationSource& source,
265                                const content::NotificationDetails& details) {
266  switch (type) {
267    case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
268      const Extension* extension =
269          content::Details<const Extension>(details).ptr();
270      DCHECK(extension);
271      DCHECK_EQ(AppLaunchInfo::GetLaunchWebURL(extension),
272                web_app_info_.app_url);
273      callback_.Run(extension, web_app_info_);
274      break;
275    }
276    case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR:
277      callback_.Run(NULL, web_app_info_);
278      break;
279    default:
280      NOTREACHED();
281      break;
282  }
283}
284
285}  // namespace extensions
286