root/chrome/browser/ui/metro_pin_tab_helper_win.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GenerateTileId
  2. GetTileImagesDir
  3. CreateSiteSpecificLogo
  4. GetPathToBackupLogo
  5. PinPageReportUmaCallback
  6. RunOnFileThread
  7. FaviconChooser
  8. best_candidate_
  9. UseChosenCandidate
  10. UpdateCandidate
  11. AddPendingRequest
  12. IsPinned
  13. TogglePinnedToStartScreen
  14. DidNavigateMainFrame
  15. DidUpdateFaviconURL
  16. DidDownloadFavicon
  17. UnPinPageFromStartScreen
  18. FaviconDownloaderFinished

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/metro_pin_tab_helper_win.h"

#include <set>

#include "base/base_paths.h"
#include "base/bind.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/metro.h"
#include "chrome/browser/favicon/favicon_tab_helper.h"
#include "chrome/browser/favicon/favicon_util.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "crypto/sha2.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"

DEFINE_WEB_CONTENTS_USER_DATA_KEY(MetroPinTabHelper);

namespace {

// Histogram name for site-specific tile pinning metrics.
const char kMetroPinMetric[] = "Metro.SecondaryTilePin";

// Generate an ID for the tile based on |url_str|. The ID is simply a hash of
// the URL.
base::string16 GenerateTileId(const base::string16& url_str) {
  uint8 hash[crypto::kSHA256Length];
  crypto::SHA256HashString(base::UTF16ToUTF8(url_str), hash, sizeof(hash));
  std::string hash_str = base::HexEncode(hash, sizeof(hash));
  return base::UTF8ToUTF16(hash_str);
}

// Get the path of the directory to store the tile logos in.
base::FilePath GetTileImagesDir() {
  base::FilePath tile_images_dir;
  if (!PathService::Get(chrome::DIR_USER_DATA, &tile_images_dir))
    return base::FilePath();

  tile_images_dir = tile_images_dir.Append(L"TileImages");
  if (!base::DirectoryExists(tile_images_dir) &&
      !base::CreateDirectory(tile_images_dir))
    return base::FilePath();

  return tile_images_dir;
}

// For the given |image| and |tile_id|, try to create a site specific logo in
// |logo_dir|. The path of any created logo is returned in |logo_path|. Return
// value indicates whether a site specific logo was created.
bool CreateSiteSpecificLogo(const SkBitmap& bitmap,
                            const base::string16& tile_id,
                            const base::FilePath& logo_dir,
                            base::FilePath* logo_path) {
  const int kLogoWidth = 120;
  const int kLogoHeight = 120;
  const int kBoxWidth = 40;
  const int kBoxHeight = 40;
  const int kCaptionHeight = 20;
  const double kBoxFade = 0.75;

  if (bitmap.isNull())
    return false;

  // Fill the tile logo with the dominant color of the favicon bitmap.
  SkColor dominant_color = color_utils::CalculateKMeanColorOfBitmap(bitmap);
  SkPaint paint;
  paint.setColor(dominant_color);
  gfx::Canvas canvas(gfx::Size(kLogoWidth, kLogoHeight), 1.0f,
                     true);
  canvas.DrawRect(gfx::Rect(0, 0, kLogoWidth, kLogoHeight), paint);

  // Now paint a faded square for the favicon to go in.
  color_utils::HSL shift = {-1, -1, kBoxFade};
  paint.setColor(color_utils::HSLShift(dominant_color, shift));
  int box_left = (kLogoWidth - kBoxWidth) / 2;
  int box_top = (kLogoHeight - kCaptionHeight - kBoxHeight) / 2;
  canvas.DrawRect(gfx::Rect(box_left, box_top, kBoxWidth, kBoxHeight), paint);

  // Now paint the favicon into the tile, leaving some room at the bottom for
  // the caption.
  int left = (kLogoWidth - bitmap.width()) / 2;
  int top = (kLogoHeight - kCaptionHeight - bitmap.height()) / 2;
  canvas.DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), left, top);

  SkBitmap logo_bitmap = canvas.ExtractImageRep().sk_bitmap();
  std::vector<unsigned char> logo_png;
  if (!gfx::PNGCodec::EncodeBGRASkBitmap(logo_bitmap, true, &logo_png))
    return false;

  *logo_path = logo_dir.Append(tile_id).ReplaceExtension(L".png");
  return base::WriteFile(*logo_path,
                         reinterpret_cast<char*>(&logo_png[0]),
                         logo_png.size()) > 0;
}

// Get the path to the backup logo. If the backup logo already exists in
// |logo_dir|, it will be used, otherwise it will be copied out of the install
// folder. (The version in the install folder is not used as it may disappear
// after an upgrade, causing tiles to lose their images if Windows rebuilds
// its tile image cache.)
// The path to the logo is returned in |logo_path|, with the return value
// indicating success.
bool GetPathToBackupLogo(const base::FilePath& logo_dir,
                         base::FilePath* logo_path) {
  const wchar_t kDefaultLogoFileName[] = L"SecondaryTile.png";
  *logo_path = logo_dir.Append(kDefaultLogoFileName);
  if (base::PathExists(*logo_path))
    return true;

  base::FilePath default_logo_path;
  if (!PathService::Get(base::DIR_MODULE, &default_logo_path))
    return false;

  default_logo_path = default_logo_path.Append(kDefaultLogoFileName);
  return base::CopyFile(default_logo_path, *logo_path);
}

// UMA reporting callback for site-specific secondary tile creation.
void PinPageReportUmaCallback(
    base::win::MetroSecondaryTilePinUmaResult result) {
  UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
                            result,
                            base::win::METRO_PIN_STATE_LIMIT);
}

// The PinPageTaskRunner class performs the necessary FILE thread actions to
// pin a page, such as generating or copying the tile image file. When it
// has performed these actions it will send the tile creation request to the
// metro driver.
class PinPageTaskRunner : public base::RefCountedThreadSafe<PinPageTaskRunner> {
 public:
  // Creates a task runner for the pinning operation with the given details.
  // |favicon| can be a null image (i.e. favicon.isNull() can be true), in
  // which case the backup tile image will be used.
  PinPageTaskRunner(const base::string16& title,
                    const base::string16& url,
                    const SkBitmap& favicon);

  void Run();
  void RunOnFileThread();

 private:
  ~PinPageTaskRunner() {}

  // Details of the page being pinned.
  const base::string16 title_;
  const base::string16 url_;
  SkBitmap favicon_;

  friend class base::RefCountedThreadSafe<PinPageTaskRunner>;
  DISALLOW_COPY_AND_ASSIGN(PinPageTaskRunner);
};

PinPageTaskRunner::PinPageTaskRunner(const base::string16& title,
                                     const base::string16& url,
                                     const SkBitmap& favicon)
    : title_(title),
      url_(url),
      favicon_(favicon) {}

void PinPageTaskRunner::Run() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  content::BrowserThread::PostTask(
      content::BrowserThread::FILE,
      FROM_HERE,
      base::Bind(&PinPageTaskRunner::RunOnFileThread, this));
}

void PinPageTaskRunner::RunOnFileThread() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);

  base::string16 tile_id = GenerateTileId(url_);
  base::FilePath logo_dir = GetTileImagesDir();
  if (logo_dir.empty()) {
    LOG(ERROR) << "Could not create directory to store tile image.";
    return;
  }

  base::FilePath logo_path;
  if (!CreateSiteSpecificLogo(favicon_, tile_id, logo_dir, &logo_path) &&
      !GetPathToBackupLogo(logo_dir, &logo_path)) {
    LOG(ERROR) << "Count not get path to logo tile.";
    return;
  }

  UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
                            base::win::METRO_PIN_LOGO_READY,
                            base::win::METRO_PIN_STATE_LIMIT);

  HMODULE metro_module = base::win::GetMetroModule();
  if (!metro_module)
    return;

  base::win::MetroPinToStartScreen metro_pin_to_start_screen =
      reinterpret_cast<base::win::MetroPinToStartScreen>(
          ::GetProcAddress(metro_module, "MetroPinToStartScreen"));
  if (!metro_pin_to_start_screen) {
    NOTREACHED();
    return;
  }

  metro_pin_to_start_screen(tile_id,
                            title_,
                            url_,
                            logo_path,
                            base::Bind(&PinPageReportUmaCallback));
}

}  // namespace

class MetroPinTabHelper::FaviconChooser {
 public:
  FaviconChooser(MetroPinTabHelper* helper,
                 const base::string16& title,
                 const base::string16& url,
                 const SkBitmap& history_bitmap);

  ~FaviconChooser() {}

  // Pin the page on the FILE thread using the current |best_candidate_| and
  // delete the FaviconChooser.
  void UseChosenCandidate();

  // Update the |best_candidate_| with the newly downloaded favicons provided.
  void UpdateCandidate(int id,
                       const GURL& image_url,
                       const std::vector<SkBitmap>& bitmaps);

  void AddPendingRequest(int request_id);

 private:
  // The tab helper that this chooser is operating for.
  MetroPinTabHelper* helper_;

  // Title and URL of the page being pinned.
  const base::string16 title_;
  const base::string16 url_;

  // The best candidate we have so far for the current pin operation.
  SkBitmap best_candidate_;

  // Outstanding favicon download requests.
  std::set<int> in_progress_requests_;

  DISALLOW_COPY_AND_ASSIGN(FaviconChooser);
};

MetroPinTabHelper::FaviconChooser::FaviconChooser(
    MetroPinTabHelper* helper,
    const base::string16& title,
    const base::string16& url,
    const SkBitmap& history_bitmap)
        : helper_(helper),
          title_(title),
          url_(url),
          best_candidate_(history_bitmap) {}

void MetroPinTabHelper::FaviconChooser::UseChosenCandidate() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  scoped_refptr<PinPageTaskRunner> runner(
      new PinPageTaskRunner(title_, url_, best_candidate_));
  runner->Run();
  helper_->FaviconDownloaderFinished();
}

void MetroPinTabHelper::FaviconChooser::UpdateCandidate(
    int id,
    const GURL& image_url,
    const std::vector<SkBitmap>& bitmaps) {
  const int kMaxIconSize = 32;

  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::set<int>::iterator iter = in_progress_requests_.find(id);
  // Check that this request is one of ours.
  if (iter == in_progress_requests_.end())
    return;

  in_progress_requests_.erase(iter);

  // Process the bitmaps, keeping the one that is best so far.
  for (std::vector<SkBitmap>::const_iterator iter = bitmaps.begin();
       iter != bitmaps.end();
       ++iter) {

    // If the new bitmap is too big, ignore it.
    if (iter->height() > kMaxIconSize || iter->width() > kMaxIconSize)
      continue;

    // If we don't have a best candidate yet, this is better so just grab it.
    if (best_candidate_.isNull()) {
      best_candidate_ = *iter;
      continue;
    }

    // If it is smaller than our best one so far, ignore it.
    if (iter->height() <= best_candidate_.height() ||
        iter->width() <= best_candidate_.width()) {
      continue;
    }

    // Othewise it is our new best candidate.
    best_candidate_ = *iter;
  }

  // If there are no more outstanding requests, pin the page on the FILE thread.
  // Once this happens this downloader has done its job, so delete it.
  if (in_progress_requests_.empty())
    UseChosenCandidate();
}

void MetroPinTabHelper::FaviconChooser::AddPendingRequest(int request_id) {
  in_progress_requests_.insert(request_id);
}

MetroPinTabHelper::MetroPinTabHelper(content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents) {
}

MetroPinTabHelper::~MetroPinTabHelper() {}

bool MetroPinTabHelper::IsPinned() const {
  HMODULE metro_module = base::win::GetMetroModule();
  if (!metro_module)
    return false;

  typedef BOOL (*MetroIsPinnedToStartScreen)(const base::string16&);
  MetroIsPinnedToStartScreen metro_is_pinned_to_start_screen =
      reinterpret_cast<MetroIsPinnedToStartScreen>(
          ::GetProcAddress(metro_module, "MetroIsPinnedToStartScreen"));
  if (!metro_is_pinned_to_start_screen) {
    NOTREACHED();
    return false;
  }

  GURL url = web_contents()->GetURL();
  base::string16 tile_id = GenerateTileId(base::UTF8ToUTF16(url.spec()));
  return metro_is_pinned_to_start_screen(tile_id) != 0;
}

void MetroPinTabHelper::TogglePinnedToStartScreen() {
  if (IsPinned()) {
    UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
                              base::win::METRO_UNPIN_INITIATED,
                              base::win::METRO_PIN_STATE_LIMIT);
    UnPinPageFromStartScreen();
    return;
  }

  UMA_HISTOGRAM_ENUMERATION(kMetroPinMetric,
                            base::win::METRO_PIN_INITIATED,
                            base::win::METRO_PIN_STATE_LIMIT);
  GURL url = web_contents()->GetURL();
  base::string16 url_str = base::UTF8ToUTF16(url.spec());
  base::string16 title = web_contents()->GetTitle();
  // TODO(oshima): Use scoped_ptr::Pass to pass it to other thread.
  SkBitmap favicon;
  FaviconTabHelper* favicon_tab_helper = FaviconTabHelper::FromWebContents(
      web_contents());
  if (favicon_tab_helper->FaviconIsValid()) {
    // Only the 1x bitmap data is needed.
    favicon = favicon_tab_helper->GetFavicon().AsImageSkia().GetRepresentation(
        1.0f).sk_bitmap();
  }

  favicon_chooser_.reset(new FaviconChooser(this, title, url_str, favicon));

  if (favicon_url_candidates_.empty()) {
    favicon_chooser_->UseChosenCandidate();
    return;
  }

  // Request all the candidates.
  int max_image_size = 0;  // Do not resize images.
  for (std::vector<content::FaviconURL>::const_iterator iter =
           favicon_url_candidates_.begin();
       iter != favicon_url_candidates_.end();
       ++iter) {
    favicon_chooser_->AddPendingRequest(
        web_contents()->DownloadImage(iter->icon_url,
            true,
            max_image_size,
            base::Bind(&MetroPinTabHelper::DidDownloadFavicon,
                       base::Unretained(this))));
  }

}

void MetroPinTabHelper::DidNavigateMainFrame(
    const content::LoadCommittedDetails& /*details*/,
    const content::FrameNavigateParams& /*params*/) {
  // Cancel any outstanding pin operations once the user navigates away from
  // the page.
  if (favicon_chooser_.get())
    favicon_chooser_.reset();
  // Any candidate favicons we have are now out of date so clear them.
  favicon_url_candidates_.clear();
}

void MetroPinTabHelper::DidUpdateFaviconURL(
    int32 page_id,
    const std::vector<content::FaviconURL>& candidates) {
  favicon_url_candidates_ = candidates;
}

void MetroPinTabHelper::DidDownloadFavicon(
    int id,
    int http_status_code,
    const GURL& image_url,
    const std::vector<SkBitmap>& bitmaps,
    const std::vector<gfx::Size>& original_bitmap_sizes) {
  if (favicon_chooser_.get()) {
    favicon_chooser_->UpdateCandidate(id, image_url, bitmaps);
  }
}

void MetroPinTabHelper::UnPinPageFromStartScreen() {
  HMODULE metro_module = base::win::GetMetroModule();
  if (!metro_module)
    return;

  base::win::MetroUnPinFromStartScreen metro_un_pin_from_start_screen =
      reinterpret_cast<base::win::MetroUnPinFromStartScreen>(
          ::GetProcAddress(metro_module, "MetroUnPinFromStartScreen"));
  if (!metro_un_pin_from_start_screen) {
    NOTREACHED();
    return;
  }

  GURL url = web_contents()->GetURL();
  base::string16 tile_id = GenerateTileId(base::UTF8ToUTF16(url.spec()));
  metro_un_pin_from_start_screen(tile_id,
                                 base::Bind(&PinPageReportUmaCallback));
}

void MetroPinTabHelper::FaviconDownloaderFinished() {
  favicon_chooser_.reset();
}

/* [<][>][^][v][top][bottom][index][help] */