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/apps/drive/drive_app_converter.h" 6 7 #include <algorithm> 8 #include <set> 9 10 #include "base/logging.h" 11 #include "base/memory/ref_counted.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "chrome/browser/extensions/crx_installer.h" 14 #include "chrome/browser/extensions/install_tracker.h" 15 #include "chrome/browser/image_decoder.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "content/public/browser/browser_thread.h" 18 #include "extensions/browser/extension_system.h" 19 #include "extensions/common/constants.h" 20 #include "net/base/load_flags.h" 21 #include "net/http/http_status_code.h" 22 #include "net/url_request/url_fetcher.h" 23 #include "net/url_request/url_fetcher_delegate.h" 24 #include "net/url_request/url_request_status.h" 25 #include "third_party/skia/include/core/SkBitmap.h" 26 27 using content::BrowserThread; 28 29 // IconFetcher downloads |icon_url| using |converter|'s profile. The icon 30 // url is passed from a DriveAppInfo and should follow icon url definition 31 // in Drive API: 32 // https://developers.google.com/drive/v2/reference/apps#resource 33 // Each icon url represents a single image associated with a certain size. 34 class DriveAppConverter::IconFetcher : public net::URLFetcherDelegate, 35 public ImageDecoder::Delegate { 36 public: 37 IconFetcher(DriveAppConverter* converter, 38 const GURL& icon_url, 39 int expected_size) 40 : converter_(converter), 41 icon_url_(icon_url), 42 expected_size_(expected_size) {} 43 virtual ~IconFetcher() { 44 if (image_decoder_.get()) 45 image_decoder_->set_delegate(NULL); 46 } 47 48 void Start() { 49 fetcher_.reset( 50 net::URLFetcher::Create(icon_url_, net::URLFetcher::GET, this)); 51 fetcher_->SetRequestContext(converter_->profile_->GetRequestContext()); 52 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); 53 fetcher_->Start(); 54 } 55 56 const GURL& icon_url() const { return icon_url_; } 57 const SkBitmap& icon() const { return icon_; } 58 59 private: 60 // net::URLFetcherDelegate overrides: 61 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE { 62 CHECK_EQ(fetcher_.get(), source); 63 scoped_ptr<net::URLFetcher> fetcher(fetcher_.Pass()); 64 65 if (!fetcher->GetStatus().is_success() || 66 fetcher->GetResponseCode() != net::HTTP_OK) { 67 converter_->OnIconFetchComplete(this); 68 return; 69 } 70 71 std::string unsafe_icon_data; 72 fetcher->GetResponseAsString(&unsafe_icon_data); 73 74 image_decoder_ = 75 new ImageDecoder(this, unsafe_icon_data, ImageDecoder::DEFAULT_CODEC); 76 image_decoder_->Start( 77 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI)); 78 } 79 80 // ImageDecoder::Delegate overrides: 81 virtual void OnImageDecoded(const ImageDecoder* decoder, 82 const SkBitmap& decoded_image) OVERRIDE { 83 if (decoded_image.width() == expected_size_) 84 icon_ = decoded_image; 85 converter_->OnIconFetchComplete(this); 86 } 87 88 virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE { 89 converter_->OnIconFetchComplete(this); 90 } 91 92 DriveAppConverter* converter_; 93 const GURL icon_url_; 94 const int expected_size_; 95 96 scoped_ptr<net::URLFetcher> fetcher_; 97 scoped_refptr<ImageDecoder> image_decoder_; 98 SkBitmap icon_; 99 100 DISALLOW_COPY_AND_ASSIGN(IconFetcher); 101 }; 102 103 DriveAppConverter::DriveAppConverter(Profile* profile, 104 const drive::DriveAppInfo& drive_app_info, 105 const FinishedCallback& finished_callback) 106 : profile_(profile), 107 drive_app_info_(drive_app_info), 108 extension_(NULL), 109 is_new_install_(false), 110 finished_callback_(finished_callback) { 111 DCHECK(profile_); 112 } 113 114 DriveAppConverter::~DriveAppConverter() { 115 PostInstallCleanUp(); 116 } 117 118 void DriveAppConverter::Start() { 119 DCHECK(!IsStarted()); 120 121 if (drive_app_info_.app_name.empty() || 122 !drive_app_info_.create_url.is_valid()) { 123 finished_callback_.Run(this, false); 124 return; 125 } 126 127 web_app_.title = base::UTF8ToUTF16(drive_app_info_.app_name); 128 web_app_.app_url = drive_app_info_.create_url; 129 130 const std::set<int> allowed_sizes(extension_misc::kExtensionIconSizes, 131 extension_misc::kExtensionIconSizes + 132 extension_misc::kNumExtensionIconSizes); 133 std::set<int> pending_sizes; 134 for (size_t i = 0; i < drive_app_info_.app_icons.size(); ++i) { 135 const int icon_size = drive_app_info_.app_icons[i].first; 136 if (allowed_sizes.find(icon_size) == allowed_sizes.end() || 137 pending_sizes.find(icon_size) != pending_sizes.end()) { 138 continue; 139 } 140 141 pending_sizes.insert(icon_size); 142 const GURL& icon_url = drive_app_info_.app_icons[i].second; 143 IconFetcher* fetcher = new IconFetcher(this, icon_url, icon_size); 144 fetchers_.push_back(fetcher); // Pass ownership to |fetchers|. 145 fetcher->Start(); 146 } 147 148 if (fetchers_.empty()) 149 StartInstall(); 150 } 151 152 bool DriveAppConverter::IsStarted() const { 153 return !fetchers_.empty() || crx_installer_.get(); 154 } 155 156 bool DriveAppConverter::IsInstalling(const std::string& app_id) const { 157 return crx_installer_.get() && crx_installer_->extension() && 158 crx_installer_->extension()->id() == app_id; 159 } 160 161 void DriveAppConverter::OnIconFetchComplete(const IconFetcher* fetcher) { 162 const SkBitmap& icon = fetcher->icon(); 163 if (!icon.empty() && icon.width() != 0) { 164 WebApplicationInfo::IconInfo icon_info; 165 icon_info.url = fetcher->icon_url(); 166 icon_info.data = icon; 167 icon_info.width = icon.width(); 168 icon_info.height = icon.height(); 169 web_app_.icons.push_back(icon_info); 170 } 171 172 fetchers_.erase(std::find(fetchers_.begin(), fetchers_.end(), fetcher)); 173 174 if (fetchers_.empty()) 175 StartInstall(); 176 } 177 178 void DriveAppConverter::StartInstall() { 179 DCHECK(!crx_installer_.get()); 180 crx_installer_ = extensions::CrxInstaller::CreateSilent( 181 extensions::ExtensionSystem::Get(profile_)->extension_service()); 182 // The converted url app should not be syncable. Drive apps go with the user's 183 // account and url apps will be created when needed. Syncing those apps could 184 // hit an edge case where the synced url apps become orphans when the user has 185 // corresponding chrome apps. 186 crx_installer_->set_do_not_sync(true); 187 188 extensions::InstallTracker::Get(profile_)->AddObserver(this); 189 crx_installer_->InstallWebApp(web_app_); 190 } 191 192 void DriveAppConverter::PostInstallCleanUp() { 193 if (!crx_installer_.get()) 194 return; 195 196 extensions::InstallTracker::Get(profile_)->RemoveObserver(this); 197 crx_installer_ = NULL; 198 } 199 200 void DriveAppConverter::OnFinishCrxInstall(const std::string& extension_id, 201 bool success) { 202 if (!crx_installer_->extension() || 203 crx_installer_->extension()->id() != extension_id) { 204 return; 205 } 206 207 extension_ = crx_installer_->extension(); 208 is_new_install_ = success && crx_installer_->current_version().empty(); 209 PostInstallCleanUp(); 210 211 finished_callback_.Run(this, success); 212 // |finished_callback_| could delete this. 213 } 214