webstore_result.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright 2013 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/ui/app_list/search/webstore/webstore_result.h" 6 7#include <vector> 8 9#include "base/bind.h" 10#include "base/memory/ref_counted.h" 11#include "base/strings/utf_string_conversions.h" 12#include "chrome/browser/apps/ephemeral_app_launcher.h" 13#include "chrome/browser/extensions/extension_service.h" 14#include "chrome/browser/extensions/install_tracker.h" 15#include "chrome/browser/extensions/install_tracker_factory.h" 16#include "chrome/browser/profiles/profile.h" 17#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" 18#include "chrome/browser/ui/app_list/search/common/url_icon_source.h" 19#include "chrome/browser/ui/app_list/search/webstore/webstore_installer.h" 20#include "chrome/browser/ui/browser_navigator.h" 21#include "chrome/browser/ui/extensions/application_launch.h" 22#include "chrome/grit/chromium_strings.h" 23#include "chrome/grit/generated_resources.h" 24#include "extensions/browser/extension_registry.h" 25#include "extensions/browser/extension_system.h" 26#include "extensions/browser/extension_util.h" 27#include "extensions/common/extension.h" 28#include "extensions/common/extension_urls.h" 29#include "grit/theme_resources.h" 30#include "net/base/url_util.h" 31#include "ui/base/l10n/l10n_util.h" 32#include "ui/base/resource/resource_bundle.h" 33#include "ui/gfx/canvas.h" 34#include "ui/gfx/image/canvas_image_source.h" 35 36namespace { 37 38const int kLaunchEphemeralAppAction = 1; 39 40// BadgedImageSource adds a webstore badge to a webstore app icon. 41class BadgedIconSource : public gfx::CanvasImageSource { 42 public: 43 BadgedIconSource(const gfx::ImageSkia& icon, const gfx::Size& icon_size) 44 : CanvasImageSource(icon_size, false), icon_(icon) {} 45 46 virtual void Draw(gfx::Canvas* canvas) OVERRIDE { 47 canvas->DrawImageInt(icon_, 0, 0); 48 const gfx::ImageSkia& badge = *ui::ResourceBundle::GetSharedInstance(). 49 GetImageSkiaNamed(IDR_WEBSTORE_ICON_16); 50 canvas->DrawImageInt( 51 badge, icon_.width() - badge.width(), icon_.height() - badge.height()); 52 } 53 54 private: 55 gfx::ImageSkia icon_; 56 57 DISALLOW_COPY_AND_ASSIGN(BadgedIconSource); 58}; 59 60} // namespace 61 62namespace app_list { 63 64WebstoreResult::WebstoreResult(Profile* profile, 65 const std::string& app_id, 66 const std::string& localized_name, 67 const GURL& icon_url, 68 bool is_paid, 69 extensions::Manifest::Type item_type, 70 AppListControllerDelegate* controller) 71 : profile_(profile), 72 app_id_(app_id), 73 localized_name_(localized_name), 74 icon_url_(icon_url), 75 is_paid_(is_paid), 76 item_type_(item_type), 77 controller_(controller), 78 install_tracker_(NULL), 79 extension_registry_(NULL), 80 weak_factory_(this) { 81 set_id(extensions::Extension::GetBaseURLFromExtensionId(app_id_).spec()); 82 set_relevance(0.0); // What is the right value to use? 83 84 set_title(base::UTF8ToUTF16(localized_name_)); 85 SetDefaultDetails(); 86 87 InitAndStartObserving(); 88 UpdateActions(); 89 90 int icon_dimension = GetPreferredIconDimension(); 91 icon_ = gfx::ImageSkia( 92 new UrlIconSource( 93 base::Bind(&WebstoreResult::OnIconLoaded, weak_factory_.GetWeakPtr()), 94 profile_->GetRequestContext(), 95 icon_url_, 96 icon_dimension, 97 IDR_WEBSTORE_ICON_32), 98 gfx::Size(icon_dimension, icon_dimension)); 99 SetIcon(icon_); 100} 101 102WebstoreResult::~WebstoreResult() { 103 StopObservingInstall(); 104 StopObservingRegistry(); 105} 106 107void WebstoreResult::Open(int event_flags) { 108 const GURL store_url = net::AppendQueryParameter( 109 GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + app_id_), 110 extension_urls::kWebstoreSourceField, 111 extension_urls::kLaunchSourceAppListSearch); 112 113 chrome::NavigateParams params(profile_, 114 store_url, 115 ui::PAGE_TRANSITION_LINK); 116 params.disposition = ui::DispositionFromEventFlags(event_flags); 117 chrome::Navigate(¶ms); 118} 119 120void WebstoreResult::InvokeAction(int action_index, int event_flags) { 121 if (is_paid_) { 122 // Paid apps cannot be installed directly from the launcher. Instead, open 123 // the webstore page for the app. 124 Open(event_flags); 125 return; 126 } 127 128 StartInstall(action_index == kLaunchEphemeralAppAction); 129} 130 131scoped_ptr<ChromeSearchResult> WebstoreResult::Duplicate() { 132 return scoped_ptr<ChromeSearchResult>(new WebstoreResult(profile_, 133 app_id_, 134 localized_name_, 135 icon_url_, 136 is_paid_, 137 item_type_, 138 controller_)).Pass(); 139} 140 141void WebstoreResult::InitAndStartObserving() { 142 DCHECK(!install_tracker_ && !extension_registry_); 143 144 install_tracker_ = 145 extensions::InstallTrackerFactory::GetForBrowserContext(profile_); 146 extension_registry_ = extensions::ExtensionRegistry::Get(profile_); 147 148 const extensions::ActiveInstallData* install_data = 149 install_tracker_->GetActiveInstall(app_id_); 150 if (install_data) { 151 SetPercentDownloaded(install_data->percent_downloaded); 152 SetIsInstalling(true); 153 } 154 155 install_tracker_->AddObserver(this); 156 extension_registry_->AddObserver(this); 157} 158 159void WebstoreResult::UpdateActions() { 160 Actions actions; 161 162 const bool is_otr = profile_->IsOffTheRecord(); 163 const bool is_installed = 164 extensions::util::IsExtensionInstalledPermanently(app_id_, profile_); 165 166 if (!is_otr && !is_installed && !is_installing()) { 167 if (EphemeralAppLauncher::IsFeatureEnabled()) { 168 actions.push_back(Action( 169 l10n_util::GetStringUTF16(IDS_WEBSTORE_RESULT_INSTALL), 170 l10n_util::GetStringUTF16( 171 IDS_EXTENSION_INLINE_INSTALL_PROMPT_TITLE))); 172 if ((item_type_ == extensions::Manifest::TYPE_PLATFORM_APP || 173 item_type_ == extensions::Manifest::TYPE_HOSTED_APP) && 174 !is_paid_) { 175 actions.push_back(Action( 176 l10n_util::GetStringUTF16(IDS_WEBSTORE_RESULT_LAUNCH), 177 l10n_util::GetStringUTF16(IDS_WEBSTORE_RESULT_LAUNCH_APP_TOOLTIP))); 178 } 179 } else { 180 actions.push_back(Action( 181 l10n_util::GetStringUTF16(IDS_EXTENSION_INLINE_INSTALL_PROMPT_TITLE), 182 base::string16())); 183 } 184 } 185 186 SetActions(actions); 187} 188 189void WebstoreResult::SetDefaultDetails() { 190 const base::string16 details = 191 l10n_util::GetStringUTF16(IDS_EXTENSION_WEB_STORE_TITLE); 192 Tags details_tags; 193 details_tags.push_back(Tag(SearchResult::Tag::DIM, 0, details.length())); 194 195 set_details(details); 196 set_details_tags(details_tags); 197} 198 199void WebstoreResult::OnIconLoaded() { 200 // Remove the existing image reps since the icon data is loaded and they 201 // need to be re-created. 202 const std::vector<gfx::ImageSkiaRep>& image_reps = icon_.image_reps(); 203 for (size_t i = 0; i < image_reps.size(); ++i) 204 icon_.RemoveRepresentation(image_reps[i].scale()); 205 int icon_dimension = GetPreferredIconDimension(); 206 gfx::Size icon_size(icon_dimension, icon_dimension); 207 icon_ = gfx::ImageSkia(new BadgedIconSource(icon_, icon_size), icon_size); 208 209 SetIcon(icon_); 210} 211 212void WebstoreResult::StartInstall(bool launch_ephemeral_app) { 213 SetPercentDownloaded(0); 214 SetIsInstalling(true); 215 216 if (launch_ephemeral_app) { 217 scoped_refptr<EphemeralAppLauncher> installer = 218 EphemeralAppLauncher::CreateForLauncher( 219 app_id_, 220 profile_, 221 controller_->GetAppListWindow(), 222 base::Bind(&WebstoreResult::LaunchCallback, 223 weak_factory_.GetWeakPtr())); 224 installer->Start(); 225 return; 226 } 227 228 scoped_refptr<WebstoreInstaller> installer = 229 new WebstoreInstaller( 230 app_id_, 231 profile_, 232 controller_->GetAppListWindow(), 233 base::Bind(&WebstoreResult::InstallCallback, 234 weak_factory_.GetWeakPtr())); 235 installer->BeginInstall(); 236} 237 238void WebstoreResult::InstallCallback( 239 bool success, 240 const std::string& error, 241 extensions::webstore_install::Result result) { 242 if (!success) { 243 LOG(ERROR) << "Failed to install app, error=" << error; 244 SetIsInstalling(false); 245 return; 246 } 247 248 // Success handling is continued in OnExtensionInstalled. 249 SetPercentDownloaded(100); 250} 251 252void WebstoreResult::LaunchCallback(extensions::webstore_install::Result result, 253 const std::string& error) { 254 if (result != extensions::webstore_install::SUCCESS) 255 LOG(ERROR) << "Failed to launch app, error=" << error; 256 257 SetIsInstalling(false); 258} 259 260void WebstoreResult::StopObservingInstall() { 261 if (install_tracker_) 262 install_tracker_->RemoveObserver(this); 263 install_tracker_ = NULL; 264} 265 266void WebstoreResult::StopObservingRegistry() { 267 if (extension_registry_) 268 extension_registry_->RemoveObserver(this); 269 extension_registry_ = NULL; 270} 271 272void WebstoreResult::OnDownloadProgress(const std::string& extension_id, 273 int percent_downloaded) { 274 if (extension_id != app_id_ || percent_downloaded < 0) 275 return; 276 277 SetPercentDownloaded(percent_downloaded); 278} 279 280void WebstoreResult::OnExtensionInstalled( 281 content::BrowserContext* browser_context, 282 const extensions::Extension* extension, 283 bool is_update) { 284 if (extension->id() != app_id_) 285 return; 286 287 SetIsInstalling(false); 288 UpdateActions(); 289 290 if (extensions::util::IsExtensionInstalledPermanently(extension->id(), 291 profile_)) { 292 NotifyItemInstalled(); 293 } 294} 295 296void WebstoreResult::OnShutdown() { 297 StopObservingInstall(); 298} 299 300void WebstoreResult::OnShutdown(extensions::ExtensionRegistry* registry) { 301 StopObservingRegistry(); 302} 303 304ChromeSearchResultType WebstoreResult::GetType() { 305 return SEARCH_WEBSTORE_SEARCH_RESULT; 306} 307 308} // namespace app_list 309