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