Home | History | Annotate | Download | only in app_list
      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.
      4 
      5 #include "chrome/browser/ui/app_list/extension_app_item.h"
      6 
      7 #include "base/prefs/pref_service.h"
      8 #include "chrome/browser/extensions/extension_prefs.h"
      9 #include "chrome/browser/extensions/extension_service.h"
     10 #include "chrome/browser/extensions/extension_sorting.h"
     11 #include "chrome/browser/extensions/extension_system.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/ui/app_list/app_context_menu.h"
     14 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
     15 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
     16 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
     17 #include "chrome/common/extensions/extension.h"
     18 #include "chrome/common/extensions/extension_constants.h"
     19 #include "chrome/common/extensions/extension_icon_set.h"
     20 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
     21 #include "chrome/common/extensions/manifest_url_handler.h"
     22 #include "grit/theme_resources.h"
     23 #include "ui/base/resource/resource_bundle.h"
     24 #include "ui/gfx/canvas.h"
     25 #include "ui/gfx/color_utils.h"
     26 #include "ui/gfx/image/canvas_image_source.h"
     27 #include "ui/gfx/image/image_skia_operations.h"
     28 
     29 using extensions::Extension;
     30 
     31 namespace {
     32 
     33 // Overlays a shortcut icon over the bottom left corner of a given image.
     34 class ShortcutOverlayImageSource : public gfx::CanvasImageSource {
     35  public:
     36   explicit ShortcutOverlayImageSource(const gfx::ImageSkia& icon)
     37       : gfx::CanvasImageSource(icon.size(), false),
     38         icon_(icon) {
     39   }
     40   virtual ~ShortcutOverlayImageSource() {}
     41 
     42  private:
     43   // gfx::CanvasImageSource overrides:
     44   virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
     45     canvas->DrawImageInt(icon_, 0, 0);
     46 
     47     // Draw the overlay in the bottom left corner of the icon.
     48     const gfx::ImageSkia& overlay = *ui::ResourceBundle::GetSharedInstance().
     49         GetImageSkiaNamed(IDR_APP_LIST_TAB_OVERLAY);
     50     canvas->DrawImageInt(overlay, 0, icon_.height() - overlay.height());
     51   }
     52 
     53   gfx::ImageSkia icon_;
     54 
     55   DISALLOW_COPY_AND_ASSIGN(ShortcutOverlayImageSource);
     56 };
     57 
     58 ExtensionSorting* GetExtensionSorting(Profile* profile) {
     59   ExtensionService* service =
     60       extensions::ExtensionSystem::Get(profile)->extension_service();
     61   return service->extension_prefs()->extension_sorting();
     62 }
     63 
     64 const color_utils::HSL shift = {-1, 0, 0.6};
     65 
     66 }  // namespace
     67 
     68 ExtensionAppItem::ExtensionAppItem(Profile* profile,
     69                                    const std::string& extension_id,
     70                                    AppListControllerDelegate* controller,
     71                                    const std::string& extension_name,
     72                                    const gfx::ImageSkia& installing_icon,
     73                                    bool is_platform_app)
     74     : ChromeAppListItem(TYPE_APP),
     75       profile_(profile),
     76       extension_id_(extension_id),
     77       controller_(controller),
     78       extension_name_(extension_name),
     79       installing_icon_(
     80           gfx::ImageSkiaOperations::CreateHSLShiftedImage(installing_icon,
     81                                                           shift)),
     82       is_platform_app_(is_platform_app) {
     83   Reload();
     84   GetExtensionSorting(profile_)->EnsureValidOrdinals(extension_id_,
     85                                                      syncer::StringOrdinal());
     86 }
     87 
     88 ExtensionAppItem::~ExtensionAppItem() {
     89 }
     90 
     91 bool ExtensionAppItem::HasOverlay() const {
     92 #if defined(OS_CHROMEOS)
     93   return false;
     94 #else
     95   return !is_platform_app_ && extension_id_ != extension_misc::kChromeAppId;
     96 #endif
     97 }
     98 
     99 void ExtensionAppItem::Reload() {
    100   const Extension* extension = GetExtension();
    101   bool is_installing = !extension;
    102   SetIsInstalling(is_installing);
    103   set_app_id(extension_id_);
    104   if (is_installing) {
    105     SetTitle(extension_name_);
    106     UpdateIcon();
    107     return;
    108   }
    109   SetTitle(extension->name());
    110   LoadImage(extension);
    111 }
    112 
    113 syncer::StringOrdinal ExtensionAppItem::GetPageOrdinal() const {
    114   return GetExtensionSorting(profile_)->GetPageOrdinal(extension_id_);
    115 }
    116 
    117 syncer::StringOrdinal ExtensionAppItem::GetAppLaunchOrdinal() const {
    118   return GetExtensionSorting(profile_)->GetAppLaunchOrdinal(extension_id_);
    119 }
    120 
    121 void ExtensionAppItem::Move(const ExtensionAppItem* prev,
    122                             const ExtensionAppItem* next) {
    123   // Does nothing if no predecessor nor successor.
    124   if (!prev && !next)
    125     return;
    126 
    127   ExtensionService* service =
    128       extensions::ExtensionSystem::Get(profile_)->extension_service();
    129   service->extension_prefs()->SetAppDraggedByUser(extension_id_);
    130 
    131   // Handles only predecessor or only successor case.
    132   if (!prev || !next) {
    133     syncer::StringOrdinal page = prev ? prev->GetPageOrdinal() :
    134                                         next->GetPageOrdinal();
    135     GetExtensionSorting(profile_)->SetPageOrdinal(extension_id_, page);
    136     service->OnExtensionMoved(extension_id_,
    137                               prev ? prev->extension_id() : std::string(),
    138                               next ? next->extension_id() : std::string());
    139     return;
    140   }
    141 
    142   // Handles both predecessor and successor are on the same page.
    143   syncer::StringOrdinal prev_page = prev->GetPageOrdinal();
    144   syncer::StringOrdinal next_page = next->GetPageOrdinal();
    145   if (prev_page.Equals(next_page)) {
    146     GetExtensionSorting(profile_)->SetPageOrdinal(extension_id_, prev_page);
    147     service->OnExtensionMoved(extension_id_,
    148                               prev->extension_id(),
    149                               next->extension_id());
    150     return;
    151   }
    152 
    153   // Otherwise, go with |next|. This is okay because app list does not split
    154   // page based ntp page ordinal.
    155   // TODO(xiyuan): Revisit this when implementing paging support.
    156   GetExtensionSorting(profile_)->SetPageOrdinal(extension_id_, prev_page);
    157   service->OnExtensionMoved(extension_id_,
    158                             prev->extension_id(),
    159                             std::string());
    160 }
    161 
    162 void ExtensionAppItem::UpdateIcon() {
    163   if (!GetExtension()) {
    164     gfx::ImageSkia icon = installing_icon_;
    165     if (HasOverlay())
    166       icon = gfx::ImageSkia(new ShortcutOverlayImageSource(icon), icon.size());
    167     SetIcon(icon, !HasOverlay());
    168     return;
    169   }
    170   gfx::ImageSkia icon = icon_->image_skia();
    171 
    172   const ExtensionService* service =
    173       extensions::ExtensionSystem::Get(profile_)->extension_service();
    174   const bool enabled = service->IsExtensionEnabledForLauncher(extension_id_);
    175   if (!enabled) {
    176     const color_utils::HSL shift = {-1, 0, 0.6};
    177     icon = gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon, shift);
    178   }
    179 
    180   if (HasOverlay())
    181     icon = gfx::ImageSkia(new ShortcutOverlayImageSource(icon), icon.size());
    182 
    183   SetIcon(icon, !HasOverlay());
    184 }
    185 
    186 const Extension* ExtensionAppItem::GetExtension() const {
    187   const ExtensionService* service =
    188       extensions::ExtensionSystem::Get(profile_)->extension_service();
    189   const Extension* extension = service->GetInstalledExtension(extension_id_);
    190   return extension;
    191 }
    192 
    193 void ExtensionAppItem::LoadImage(const Extension* extension) {
    194   icon_.reset(new extensions::IconImage(
    195       profile_,
    196       extension,
    197       extensions::IconsInfo::GetIcons(extension),
    198       extension_misc::EXTENSION_ICON_MEDIUM,
    199       extensions::IconsInfo::GetDefaultAppIcon(),
    200       this));
    201   UpdateIcon();
    202 }
    203 
    204 bool ExtensionAppItem::RunExtensionEnableFlow() {
    205   const ExtensionService* service =
    206       extensions::ExtensionSystem::Get(profile_)->extension_service();
    207   if (service->IsExtensionEnabledForLauncher(extension_id_))
    208     return false;
    209 
    210   if (!extension_enable_flow_) {
    211     controller_->OnShowExtensionPrompt();
    212 
    213     extension_enable_flow_.reset(new ExtensionEnableFlow(
    214         profile_, extension_id_, this));
    215     extension_enable_flow_->StartForNativeWindow(
    216         controller_->GetAppListWindow());
    217   }
    218   return true;
    219 }
    220 
    221 void ExtensionAppItem::Launch(int event_flags) {
    222   // |extension| could be NULL when it is being unloaded for updating.
    223   const Extension* extension = GetExtension();
    224   if (!extension)
    225     return;
    226 
    227   if (RunExtensionEnableFlow())
    228     return;
    229 
    230   controller_->LaunchApp(profile_, extension, event_flags);
    231 }
    232 
    233 void ExtensionAppItem::OnExtensionIconImageChanged(
    234     extensions::IconImage* image) {
    235   DCHECK(icon_.get() == image);
    236   UpdateIcon();
    237 }
    238 
    239 void ExtensionAppItem::ExtensionEnableFlowFinished() {
    240   extension_enable_flow_.reset();
    241   controller_->OnCloseExtensionPrompt();
    242 
    243   // Automatically launch app after enabling.
    244   Launch(ui::EF_NONE);
    245 }
    246 
    247 void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated) {
    248   extension_enable_flow_.reset();
    249   controller_->OnCloseExtensionPrompt();
    250 }
    251 
    252 void ExtensionAppItem::Activate(int event_flags) {
    253   // |extension| could be NULL when it is being unloaded for updating.
    254   const Extension* extension = GetExtension();
    255   if (!extension)
    256     return;
    257 
    258   if (RunExtensionEnableFlow())
    259     return;
    260 
    261   CoreAppLauncherHandler::RecordAppListMainLaunch(extension);
    262   controller_->ActivateApp(profile_, extension, event_flags);
    263 }
    264 
    265 ui::MenuModel* ExtensionAppItem::GetContextMenuModel() {
    266   if (!context_menu_) {
    267     context_menu_.reset(new app_list::AppContextMenu(
    268         this, profile_, extension_id_, controller_, is_platform_app_));
    269   }
    270 
    271   return context_menu_->GetMenuModel();
    272 }
    273 
    274 void ExtensionAppItem::ExecuteLaunchCommand(int event_flags) {
    275   Launch(event_flags);
    276 }
    277