Add ability to gather metrics to BubbleManager.
[chromium-blink-merge.git] / chrome / browser / ui / metro_pin_tab_helper_win.cc
blob65645716e44c33732e486eb0dd37324272e30eff
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.
5 #include "chrome/browser/ui/metro_pin_tab_helper_win.h"
7 #include <set>
9 #include "base/base_paths.h"
10 #include "base/bind.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/logging.h"
14 #include "base/memory/ref_counted.h"
15 #include "base/memory/ref_counted_memory.h"
16 #include "base/metrics/histogram.h"
17 #include "base/path_service.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/win/metro.h"
21 #include "chrome/common/chrome_paths.h"
22 #include "components/favicon/content/content_favicon_driver.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/web_contents.h"
25 #include "crypto/sha2.h"
26 #include "third_party/skia/include/core/SkCanvas.h"
27 #include "third_party/skia/include/core/SkColor.h"
28 #include "ui/gfx/canvas.h"
29 #include "ui/gfx/codec/png_codec.h"
30 #include "ui/gfx/color_analysis.h"
31 #include "ui/gfx/color_utils.h"
32 #include "ui/gfx/geometry/rect.h"
33 #include "ui/gfx/geometry/size.h"
34 #include "ui/gfx/image/image.h"
36 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MetroPinTabHelper);
38 namespace {
40 // Histogram name for site-specific tile pinning metrics.
41 const char kMetroPinMetric[] = "Metro.SecondaryTilePin";
43 // Generate an ID for the tile based on |url_str|. The ID is simply a hash of
44 // the URL.
45 base::string16 GenerateTileId(const base::string16& url_str) {
46 uint8 hash[crypto::kSHA256Length];
47 crypto::SHA256HashString(base::UTF16ToUTF8(url_str), hash, sizeof(hash));
48 std::string hash_str = base::HexEncode(hash, sizeof(hash));
49 return base::UTF8ToUTF16(hash_str);
52 // Get the path of the directory to store the tile logos in.
53 base::FilePath GetTileImagesDir() {
54 base::FilePath tile_images_dir;
55 if (!PathService::Get(chrome::DIR_USER_DATA, &tile_images_dir))
56 return base::FilePath();
58 tile_images_dir = tile_images_dir.Append(L"TileImages");
59 if (!base::DirectoryExists(tile_images_dir) &&
60 !base::CreateDirectory(tile_images_dir))
61 return base::FilePath();
63 return tile_images_dir;
66 // For the given |image| and |tile_id|, try to create a site specific logo in
67 // |logo_dir|. The path of any created logo is returned in |logo_path|. Return
68 // value indicates whether a site specific logo was created.
69 bool CreateSiteSpecificLogo(const SkBitmap& bitmap,
70 const base::string16& tile_id,
71 const base::FilePath& logo_dir,
72 base::FilePath* logo_path) {
73 const int kLogoWidth = 120;
74 const int kLogoHeight = 120;
75 const int kBoxWidth = 40;
76 const int kBoxHeight = 40;
77 const int kCaptionHeight = 20;
78 const double kBoxFade = 0.75;
80 if (bitmap.isNull())
81 return false;
83 // Fill the tile logo with the dominant color of the favicon bitmap.
84 SkColor dominant_color = color_utils::CalculateKMeanColorOfBitmap(bitmap);
85 SkPaint paint;
86 paint.setColor(dominant_color);
87 gfx::Canvas canvas(gfx::Size(kLogoWidth, kLogoHeight), 1.0f,
88 true);
89 canvas.DrawRect(gfx::Rect(0, 0, kLogoWidth, kLogoHeight), paint);
91 // Now paint a faded square for the favicon to go in.
92 color_utils::HSL shift = {-1, -1, kBoxFade};
93 paint.setColor(color_utils::HSLShift(dominant_color, shift));
94 int box_left = (kLogoWidth - kBoxWidth) / 2;
95 int box_top = (kLogoHeight - kCaptionHeight - kBoxHeight) / 2;
96 canvas.DrawRect(gfx::Rect(box_left, box_top, kBoxWidth, kBoxHeight), paint);
98 // Now paint the favicon into the tile, leaving some room at the bottom for
99 // the caption.
100 int left = (kLogoWidth - bitmap.width()) / 2;
101 int top = (kLogoHeight - kCaptionHeight - bitmap.height()) / 2;
102 canvas.DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), left, top);
104 SkBitmap logo_bitmap = canvas.ExtractImageRep().sk_bitmap();
105 std::vector<unsigned char> logo_png;
106 if (!gfx::PNGCodec::EncodeBGRASkBitmap(logo_bitmap, true, &logo_png))
107 return false;
109 *logo_path = logo_dir.Append(tile_id).ReplaceExtension(L".png");
110 return base::WriteFile(*logo_path,
111 reinterpret_cast<char*>(&logo_png[0]),
112 logo_png.size()) > 0;
115 // Get the path to the backup logo. If the backup logo already exists in
116 // |logo_dir|, it will be used, otherwise it will be copied out of the install
117 // folder. (The version in the install folder is not used as it may disappear
118 // after an upgrade, causing tiles to lose their images if Windows rebuilds
119 // its tile image cache.)
120 // The path to the logo is returned in |logo_path|, with the return value
121 // indicating success.
122 bool GetPathToBackupLogo(const base::FilePath& logo_dir,
123 base::FilePath* logo_path) {
124 const wchar_t kDefaultLogoFileName[] = L"SecondaryTile.png";
125 *logo_path = logo_dir.Append(kDefaultLogoFileName);
126 if (base::PathExists(*logo_path))
127 return true;
129 base::FilePath default_logo_path;
130 if (!PathService::Get(base::DIR_MODULE, &default_logo_path))
131 return false;
133 default_logo_path = default_logo_path.Append(kDefaultLogoFileName);
134 return base::CopyFile(default_logo_path, *logo_path);
137 // UMA reporting callback for site-specific secondary tile creation.
138 void PinPageReportUmaCallback(
139 base::win::MetroSecondaryTilePinUmaResult result) {
140 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
141 result,
142 base::win::METRO_PIN_STATE_LIMIT);
145 // The PinPageTaskRunner class performs the necessary FILE thread actions to
146 // pin a page, such as generating or copying the tile image file. When it
147 // has performed these actions it will send the tile creation request to the
148 // metro driver.
149 class PinPageTaskRunner : public base::RefCountedThreadSafe<PinPageTaskRunner> {
150 public:
151 // Creates a task runner for the pinning operation with the given details.
152 // |favicon| can be a null image (i.e. favicon.isNull() can be true), in
153 // which case the backup tile image will be used.
154 PinPageTaskRunner(const base::string16& title,
155 const base::string16& url,
156 const SkBitmap& favicon);
158 void Run();
159 void RunOnFileThread();
161 private:
162 ~PinPageTaskRunner() {}
164 // Details of the page being pinned.
165 const base::string16 title_;
166 const base::string16 url_;
167 SkBitmap favicon_;
169 friend class base::RefCountedThreadSafe<PinPageTaskRunner>;
170 DISALLOW_COPY_AND_ASSIGN(PinPageTaskRunner);
173 PinPageTaskRunner::PinPageTaskRunner(const base::string16& title,
174 const base::string16& url,
175 const SkBitmap& favicon)
176 : title_(title),
177 url_(url),
178 favicon_(favicon) {}
180 void PinPageTaskRunner::Run() {
181 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
183 content::BrowserThread::PostTask(
184 content::BrowserThread::FILE,
185 FROM_HERE,
186 base::Bind(&PinPageTaskRunner::RunOnFileThread, this));
189 void PinPageTaskRunner::RunOnFileThread() {
190 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
192 base::string16 tile_id = GenerateTileId(url_);
193 base::FilePath logo_dir = GetTileImagesDir();
194 if (logo_dir.empty()) {
195 LOG(ERROR) << "Could not create directory to store tile image.";
196 return;
199 base::FilePath logo_path;
200 if (!CreateSiteSpecificLogo(favicon_, tile_id, logo_dir, &logo_path) &&
201 !GetPathToBackupLogo(logo_dir, &logo_path)) {
202 LOG(ERROR) << "Count not get path to logo tile.";
203 return;
206 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
207 base::win::METRO_PIN_LOGO_READY,
208 base::win::METRO_PIN_STATE_LIMIT);
210 HMODULE metro_module = base::win::GetMetroModule();
211 if (!metro_module)
212 return;
214 base::win::MetroPinToStartScreen metro_pin_to_start_screen =
215 reinterpret_cast<base::win::MetroPinToStartScreen>(
216 ::GetProcAddress(metro_module, "MetroPinToStartScreen"));
217 if (!metro_pin_to_start_screen) {
218 NOTREACHED();
219 return;
222 metro_pin_to_start_screen(tile_id,
223 title_,
224 url_,
225 logo_path,
226 base::Bind(&PinPageReportUmaCallback));
229 } // namespace
231 class MetroPinTabHelper::FaviconChooser {
232 public:
233 FaviconChooser(MetroPinTabHelper* helper,
234 const base::string16& title,
235 const base::string16& url,
236 const SkBitmap& history_bitmap);
238 ~FaviconChooser() {}
240 // Pin the page on the FILE thread using the current |best_candidate_| and
241 // delete the FaviconChooser.
242 void UseChosenCandidate();
244 // Update the |best_candidate_| with the newly downloaded favicons provided.
245 void UpdateCandidate(int id,
246 const GURL& image_url,
247 const std::vector<SkBitmap>& bitmaps);
249 void AddPendingRequest(int request_id);
251 private:
252 // The tab helper that this chooser is operating for.
253 MetroPinTabHelper* helper_;
255 // Title and URL of the page being pinned.
256 const base::string16 title_;
257 const base::string16 url_;
259 // The best candidate we have so far for the current pin operation.
260 SkBitmap best_candidate_;
262 // Outstanding favicon download requests.
263 std::set<int> in_progress_requests_;
265 DISALLOW_COPY_AND_ASSIGN(FaviconChooser);
268 MetroPinTabHelper::FaviconChooser::FaviconChooser(
269 MetroPinTabHelper* helper,
270 const base::string16& title,
271 const base::string16& url,
272 const SkBitmap& history_bitmap)
273 : helper_(helper),
274 title_(title),
275 url_(url),
276 best_candidate_(history_bitmap) {}
278 void MetroPinTabHelper::FaviconChooser::UseChosenCandidate() {
279 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
280 scoped_refptr<PinPageTaskRunner> runner(
281 new PinPageTaskRunner(title_, url_, best_candidate_));
282 runner->Run();
283 helper_->FaviconDownloaderFinished();
286 void MetroPinTabHelper::FaviconChooser::UpdateCandidate(
287 int id,
288 const GURL& image_url,
289 const std::vector<SkBitmap>& bitmaps) {
290 const int kMaxIconSize = 32;
292 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
294 std::set<int>::iterator iter = in_progress_requests_.find(id);
295 // Check that this request is one of ours.
296 if (iter == in_progress_requests_.end())
297 return;
299 in_progress_requests_.erase(iter);
301 // Process the bitmaps, keeping the one that is best so far.
302 for (std::vector<SkBitmap>::const_iterator iter = bitmaps.begin();
303 iter != bitmaps.end();
304 ++iter) {
306 // If the new bitmap is too big, ignore it.
307 if (iter->height() > kMaxIconSize || iter->width() > kMaxIconSize)
308 continue;
310 // If we don't have a best candidate yet, this is better so just grab it.
311 if (best_candidate_.isNull()) {
312 best_candidate_ = *iter;
313 continue;
316 // If it is smaller than our best one so far, ignore it.
317 if (iter->height() <= best_candidate_.height() ||
318 iter->width() <= best_candidate_.width()) {
319 continue;
322 // Othewise it is our new best candidate.
323 best_candidate_ = *iter;
326 // If there are no more outstanding requests, pin the page on the FILE thread.
327 // Once this happens this downloader has done its job, so delete it.
328 if (in_progress_requests_.empty())
329 UseChosenCandidate();
332 void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id) {
333 in_progress_requests_.insert(request_id);
336 MetroPinTabHelper::MetroPinTabHelper(content::WebContents* web_contents)
337 : content::WebContentsObserver(web_contents) {
340 MetroPinTabHelper::~MetroPinTabHelper() {}
342 bool MetroPinTabHelper::IsPinned() const {
343 HMODULE metro_module = base::win::GetMetroModule();
344 if (!metro_module)
345 return false;
347 typedef BOOL (*MetroIsPinnedToStartScreen)(const base::string16&);
348 MetroIsPinnedToStartScreen metro_is_pinned_to_start_screen =
349 reinterpret_cast<MetroIsPinnedToStartScreen>(
350 ::GetProcAddress(metro_module, "MetroIsPinnedToStartScreen"));
351 if (!metro_is_pinned_to_start_screen) {
352 NOTREACHED();
353 return false;
356 GURL url = web_contents()->GetURL();
357 base::string16 tile_id = GenerateTileId(base::UTF8ToUTF16(url.spec()));
358 return metro_is_pinned_to_start_screen(tile_id) != 0;
361 void MetroPinTabHelper::TogglePinnedToStartScreen() {
362 if (IsPinned()) {
363 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
364 base::win::METRO_UNPIN_INITIATED,
365 base::win::METRO_PIN_STATE_LIMIT);
366 UnPinPageFromStartScreen();
367 return;
370 UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
371 base::win::METRO_PIN_INITIATED,
372 base::win::METRO_PIN_STATE_LIMIT);
373 GURL url = web_contents()->GetURL();
374 base::string16 url_str = base::UTF8ToUTF16(url.spec());
375 base::string16 title = web_contents()->GetTitle();
376 // TODO(oshima): Use scoped_ptr::Pass to pass it to other thread.
377 SkBitmap favicon;
378 favicon::FaviconDriver* favicon_driver =
379 favicon::ContentFaviconDriver::FromWebContents(web_contents());
380 if (favicon_driver->FaviconIsValid()) {
381 // Only the 1x bitmap data is needed.
382 favicon = favicon_driver->GetFavicon()
383 .AsImageSkia()
384 .GetRepresentation(1.0f)
385 .sk_bitmap();
388 favicon_chooser_.reset(new FaviconChooser(this, title, url_str, favicon));
390 if (favicon_url_candidates_.empty()) {
391 favicon_chooser_->UseChosenCandidate();
392 return;
395 // Request all the candidates.
396 int max_image_size = 0; // Do not resize images.
397 for (std::vector<content::FaviconURL>::const_iterator iter =
398 favicon_url_candidates_.begin();
399 iter != favicon_url_candidates_.end();
400 ++iter) {
401 favicon_chooser_->AddPendingRequest(
402 web_contents()->DownloadImage(iter->icon_url,
403 true,
404 max_image_size,
405 false,
406 base::Bind(&MetroPinTabHelper::DidDownloadFavicon,
407 base::Unretained(this))));
412 void MetroPinTabHelper::DidNavigateMainFrame(
413 const content::LoadCommittedDetails& /*details*/,
414 const content::FrameNavigateParams& /*params*/) {
415 // Cancel any outstanding pin operations once the user navigates away from
416 // the page.
417 if (favicon_chooser_.get())
418 favicon_chooser_.reset();
419 // Any candidate favicons we have are now out of date so clear them.
420 favicon_url_candidates_.clear();
423 void MetroPinTabHelper::DidUpdateFaviconURL(
424 const std::vector<content::FaviconURL>& candidates) {
425 favicon_url_candidates_ = candidates;
428 void MetroPinTabHelper::DidDownloadFavicon(
429 int id,
430 int http_status_code,
431 const GURL& image_url,
432 const std::vector<SkBitmap>& bitmaps,
433 const std::vector<gfx::Size>& original_bitmap_sizes) {
434 if (favicon_chooser_.get()) {
435 favicon_chooser_->UpdateCandidate(id, image_url, bitmaps);
439 void MetroPinTabHelper::UnPinPageFromStartScreen() {
440 HMODULE metro_module = base::win::GetMetroModule();
441 if (!metro_module)
442 return;
444 base::win::MetroUnPinFromStartScreen metro_un_pin_from_start_screen =
445 reinterpret_cast<base::win::MetroUnPinFromStartScreen>(
446 ::GetProcAddress(metro_module, "MetroUnPinFromStartScreen"));
447 if (!metro_un_pin_from_start_screen) {
448 NOTREACHED();
449 return;
452 GURL url = web_contents()->GetURL();
453 base::string16 tile_id = GenerateTileId(base::UTF8ToUTF16(url.spec()));
454 metro_un_pin_from_start_screen(tile_id,
455 base::Bind(&PinPageReportUmaCallback));
458 void MetroPinTabHelper::FaviconDownloaderFinished() {
459 favicon_chooser_.reset();