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