Home | History | Annotate | Download | only in webstore
      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 
     36 namespace {
     37 
     38 const int kLaunchEphemeralAppAction = 1;
     39 
     40 // BadgedImageSource adds a webstore badge to a webstore app icon.
     41 class 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 
     62 namespace app_list {
     63 
     64 WebstoreResult::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 
    102 WebstoreResult::~WebstoreResult() {
    103   StopObservingInstall();
    104   StopObservingRegistry();
    105 }
    106 
    107 void 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(&params);
    118 }
    119 
    120 void 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 
    131 scoped_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 
    141 void 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 
    159 void 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 
    189 void 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 
    199 void 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 
    212 void 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 
    238 void 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 
    252 void 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 
    260 void WebstoreResult::StopObservingInstall() {
    261   if (install_tracker_)
    262     install_tracker_->RemoveObserver(this);
    263   install_tracker_ = NULL;
    264 }
    265 
    266 void WebstoreResult::StopObservingRegistry() {
    267   if (extension_registry_)
    268     extension_registry_->RemoveObserver(this);
    269   extension_registry_ = NULL;
    270 }
    271 
    272 void 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 
    280 void 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 
    296 void WebstoreResult::OnShutdown() {
    297   StopObservingInstall();
    298 }
    299 
    300 void WebstoreResult::OnShutdown(extensions::ExtensionRegistry* registry) {
    301   StopObservingRegistry();
    302 }
    303 
    304 ChromeSearchResultType WebstoreResult::GetType() {
    305   return SEARCH_WEBSTORE_SEARCH_RESULT;
    306 }
    307 
    308 }  // namespace app_list
    309