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/command_line.h"
      8 #include "base/prefs/pref_service.h"
      9 #include "chrome/browser/extensions/extension_service.h"
     10 #include "chrome/browser/extensions/extension_util.h"
     11 #include "chrome/browser/extensions/launch_util.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/app_list/app_list_service.h"
     16 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
     17 #include "chrome/browser/ui/host_desktop.h"
     18 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
     19 #include "chrome/common/chrome_switches.h"
     20 #include "chrome/common/extensions/extension_constants.h"
     21 #include "chrome/common/extensions/manifest_url_handler.h"
     22 #include "content/public/browser/user_metrics.h"
     23 #include "extensions/browser/app_sorting.h"
     24 #include "extensions/browser/extension_prefs.h"
     25 #include "extensions/browser/extension_system.h"
     26 #include "extensions/common/extension.h"
     27 #include "extensions/common/extension_icon_set.h"
     28 #include "extensions/common/manifest_handlers/icons_handler.h"
     29 #include "grit/theme_resources.h"
     30 #include "sync/api/string_ordinal.h"
     31 #include "ui/base/resource/resource_bundle.h"
     32 #include "ui/gfx/canvas.h"
     33 #include "ui/gfx/color_utils.h"
     34 #include "ui/gfx/image/canvas_image_source.h"
     35 #include "ui/gfx/image/image_skia_operations.h"
     36 #include "ui/gfx/rect.h"
     37 
     38 using extensions::Extension;
     39 
     40 namespace {
     41 
     42 // Overlays a shortcut icon over the bottom left corner of a given image.
     43 class ShortcutOverlayImageSource : public gfx::CanvasImageSource {
     44  public:
     45   explicit ShortcutOverlayImageSource(const gfx::ImageSkia& icon)
     46       : gfx::CanvasImageSource(icon.size(), false),
     47         icon_(icon) {
     48   }
     49   virtual ~ShortcutOverlayImageSource() {}
     50 
     51  private:
     52   // gfx::CanvasImageSource overrides:
     53   virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
     54     canvas->DrawImageInt(icon_, 0, 0);
     55 
     56     // Draw the overlay in the bottom left corner of the icon.
     57     const gfx::ImageSkia& overlay = *ui::ResourceBundle::GetSharedInstance().
     58         GetImageSkiaNamed(IDR_APP_LIST_TAB_OVERLAY);
     59     canvas->DrawImageInt(overlay, 0, icon_.height() - overlay.height());
     60   }
     61 
     62   gfx::ImageSkia icon_;
     63 
     64   DISALLOW_COPY_AND_ASSIGN(ShortcutOverlayImageSource);
     65 };
     66 
     67 // Rounds the corners of a given image.
     68 class RoundedCornersImageSource : public gfx::CanvasImageSource {
     69  public:
     70   explicit RoundedCornersImageSource(const gfx::ImageSkia& icon)
     71       : gfx::CanvasImageSource(icon.size(), false),
     72         icon_(icon) {
     73   }
     74   virtual ~RoundedCornersImageSource() {}
     75 
     76  private:
     77   // gfx::CanvasImageSource overrides:
     78   virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
     79     // The radius used to round the app icon.
     80     const size_t kRoundingRadius = 2;
     81 
     82     canvas->DrawImageInt(icon_, 0, 0);
     83 
     84     scoped_ptr<gfx::Canvas> masking_canvas(
     85         new gfx::Canvas(gfx::Size(icon_.width(), icon_.height()), 1.0f, false));
     86     DCHECK(masking_canvas);
     87 
     88     SkPaint opaque_paint;
     89     opaque_paint.setColor(SK_ColorWHITE);
     90     opaque_paint.setFlags(SkPaint::kAntiAlias_Flag);
     91     masking_canvas->DrawRoundRect(
     92         gfx::Rect(icon_.width(), icon_.height()),
     93         kRoundingRadius, opaque_paint);
     94 
     95     SkPaint masking_paint;
     96     masking_paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
     97     canvas->DrawImageInt(
     98         gfx::ImageSkia(masking_canvas->ExtractImageRep()), 0, 0, masking_paint);
     99   }
    100 
    101   gfx::ImageSkia icon_;
    102 
    103   DISALLOW_COPY_AND_ASSIGN(RoundedCornersImageSource);
    104 };
    105 
    106 extensions::AppSorting* GetAppSorting(Profile* profile) {
    107   return extensions::ExtensionPrefs::Get(profile)->app_sorting();
    108 }
    109 
    110 const color_utils::HSL shift = {-1, 0, 0.6};
    111 
    112 }  // namespace
    113 
    114 ExtensionAppItem::ExtensionAppItem(
    115     Profile* profile,
    116     const app_list::AppListSyncableService::SyncItem* sync_item,
    117     const std::string& extension_id,
    118     const std::string& extension_name,
    119     const gfx::ImageSkia& installing_icon,
    120     bool is_platform_app)
    121     : app_list::AppListItem(extension_id),
    122       profile_(profile),
    123       extension_id_(extension_id),
    124       extension_enable_flow_controller_(NULL),
    125       extension_name_(extension_name),
    126       installing_icon_(
    127           gfx::ImageSkiaOperations::CreateHSLShiftedImage(installing_icon,
    128                                                           shift)),
    129       is_platform_app_(is_platform_app),
    130       has_overlay_(false) {
    131   Reload();
    132   if (sync_item && sync_item->item_ordinal.IsValid()) {
    133     // An existing synced position exists, use that.
    134     set_position(sync_item->item_ordinal);
    135     // Only set the name from the sync item if it is empty.
    136     if (name().empty())
    137       SetName(sync_item->item_name);
    138     return;
    139   }
    140   GetAppSorting(profile_)->EnsureValidOrdinals(extension_id_,
    141                                                syncer::StringOrdinal());
    142   UpdatePositionFromExtensionOrdering();
    143 }
    144 
    145 ExtensionAppItem::~ExtensionAppItem() {
    146 }
    147 
    148 bool ExtensionAppItem::NeedsOverlay() const {
    149   // The overlay icon is disabled for hosted apps in windowed mode with
    150   // streamlined hosted apps.
    151   bool streamlined_hosted_apps = CommandLine::ForCurrentProcess()->
    152       HasSwitch(switches::kEnableStreamlinedHostedApps);
    153 #if defined(OS_CHROMEOS)
    154   if (!streamlined_hosted_apps)
    155     return false;
    156 #endif
    157   extensions::LaunchType launch_type =
    158       GetExtension()
    159           ? extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_),
    160                                       GetExtension())
    161           : extensions::LAUNCH_TYPE_WINDOW;
    162 
    163   return !is_platform_app_ && extension_id_ != extension_misc::kChromeAppId &&
    164       (!streamlined_hosted_apps ||
    165        launch_type != extensions::LAUNCH_TYPE_WINDOW);
    166 }
    167 
    168 void ExtensionAppItem::Reload() {
    169   const Extension* extension = GetExtension();
    170   bool is_installing = !extension;
    171   SetIsInstalling(is_installing);
    172   if (is_installing) {
    173     SetName(extension_name_);
    174     UpdateIcon();
    175     return;
    176   }
    177   SetNameAndShortName(extension->name(), extension->short_name());
    178   LoadImage(extension);
    179 }
    180 
    181 void ExtensionAppItem::UpdateIcon() {
    182   gfx::ImageSkia icon = installing_icon_;
    183 
    184   // Use the app icon if the app exists. Turn the image greyscale if the app is
    185   // not launchable.
    186   if (GetExtension()) {
    187     icon = icon_->image_skia();
    188     const bool enabled = extensions::util::IsAppLaunchable(extension_id_,
    189                                                            profile_);
    190     if (!enabled) {
    191       const color_utils::HSL shift = {-1, 0, 0.6};
    192       icon = gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon, shift);
    193     }
    194 
    195     if (GetExtension()->from_bookmark())
    196       icon = gfx::ImageSkia(new RoundedCornersImageSource(icon), icon.size());
    197   }
    198   // Paint the shortcut overlay if necessary.
    199   has_overlay_ = NeedsOverlay();
    200   if (has_overlay_)
    201     icon = gfx::ImageSkia(new ShortcutOverlayImageSource(icon), icon.size());
    202 
    203   SetIcon(icon, true);
    204 }
    205 
    206 void ExtensionAppItem::Move(const ExtensionAppItem* prev,
    207                             const ExtensionAppItem* next) {
    208   if (!prev && !next)
    209     return;  // No reordering necessary
    210 
    211   extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
    212   extensions::AppSorting* sorting = GetAppSorting(profile_);
    213 
    214   syncer::StringOrdinal page;
    215   std::string prev_id, next_id;
    216   if (!prev) {
    217     next_id = next->extension_id();
    218     page = sorting->GetPageOrdinal(next_id);
    219   } else if (!next) {
    220     prev_id = prev->extension_id();
    221     page = sorting->GetPageOrdinal(prev_id);
    222   } else {
    223     prev_id = prev->extension_id();
    224     page = sorting->GetPageOrdinal(prev_id);
    225     // Only set |next_id| if on the same page, otherwise just insert after prev.
    226     if (page.Equals(sorting->GetPageOrdinal(next->extension_id())))
    227       next_id = next->extension_id();
    228   }
    229   prefs->SetAppDraggedByUser(extension_id_);
    230   sorting->SetPageOrdinal(extension_id_, page);
    231   sorting->OnExtensionMoved(extension_id_, prev_id, next_id);
    232   UpdatePositionFromExtensionOrdering();
    233 }
    234 
    235 const Extension* ExtensionAppItem::GetExtension() const {
    236   const ExtensionService* service =
    237       extensions::ExtensionSystem::Get(profile_)->extension_service();
    238   const Extension* extension = service->GetInstalledExtension(extension_id_);
    239   return extension;
    240 }
    241 
    242 void ExtensionAppItem::LoadImage(const Extension* extension) {
    243   icon_.reset(new extensions::IconImage(
    244       profile_,
    245       extension,
    246       extensions::IconsInfo::GetIcons(extension),
    247       extension_misc::EXTENSION_ICON_MEDIUM,
    248       extensions::util::GetDefaultAppIcon(),
    249       this));
    250   UpdateIcon();
    251 }
    252 
    253 bool ExtensionAppItem::RunExtensionEnableFlow() {
    254   if (extensions::util::IsAppLaunchableWithoutEnabling(extension_id_, profile_))
    255     return false;
    256 
    257   if (!extension_enable_flow_) {
    258     extension_enable_flow_controller_ = GetController();
    259     extension_enable_flow_controller_->OnShowChildDialog();
    260 
    261     extension_enable_flow_.reset(new ExtensionEnableFlow(
    262         profile_, extension_id_, this));
    263     extension_enable_flow_->StartForNativeWindow(
    264         extension_enable_flow_controller_->GetAppListWindow());
    265   }
    266   return true;
    267 }
    268 
    269 void ExtensionAppItem::Launch(int event_flags) {
    270   // |extension| could be NULL when it is being unloaded for updating.
    271   const Extension* extension = GetExtension();
    272   if (!extension)
    273     return;
    274 
    275   if (RunExtensionEnableFlow())
    276     return;
    277 
    278   GetController()->LaunchApp(profile_,
    279                              extension,
    280                              AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
    281                              event_flags);
    282 }
    283 
    284 void ExtensionAppItem::OnExtensionIconImageChanged(
    285     extensions::IconImage* image) {
    286   DCHECK(icon_.get() == image);
    287   UpdateIcon();
    288 }
    289 
    290 void ExtensionAppItem::ExtensionEnableFlowFinished() {
    291   extension_enable_flow_.reset();
    292   extension_enable_flow_controller_->OnCloseChildDialog();
    293   extension_enable_flow_controller_ = NULL;
    294 
    295   // Automatically launch app after enabling.
    296   Launch(ui::EF_NONE);
    297 }
    298 
    299 void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated) {
    300   extension_enable_flow_.reset();
    301   extension_enable_flow_controller_->OnCloseChildDialog();
    302   extension_enable_flow_controller_ = NULL;
    303 }
    304 
    305 void ExtensionAppItem::Activate(int event_flags) {
    306   // |extension| could be NULL when it is being unloaded for updating.
    307   const Extension* extension = GetExtension();
    308   if (!extension)
    309     return;
    310 
    311   if (RunExtensionEnableFlow())
    312     return;
    313 
    314   content::RecordAction(base::UserMetricsAction("AppList_ClickOnApp"));
    315   CoreAppLauncherHandler::RecordAppListMainLaunch(extension);
    316   GetController()->ActivateApp(profile_,
    317                                extension,
    318                                AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
    319                                event_flags);
    320 }
    321 
    322 ui::MenuModel* ExtensionAppItem::GetContextMenuModel() {
    323   context_menu_.reset(new app_list::AppContextMenu(
    324       this, profile_, extension_id_, GetController()));
    325   context_menu_->set_is_platform_app(is_platform_app_);
    326   if (IsInFolder())
    327     context_menu_->set_is_in_folder(true);
    328   return context_menu_->GetMenuModel();
    329 }
    330 
    331 void ExtensionAppItem::OnExtensionPreferenceChanged() {
    332   if (has_overlay_ != NeedsOverlay())
    333     UpdateIcon();
    334 }
    335 
    336 // static
    337 const char ExtensionAppItem::kItemType[] = "ExtensionAppItem";
    338 
    339 const char* ExtensionAppItem::GetItemType() const {
    340   return ExtensionAppItem::kItemType;
    341 }
    342 
    343 void ExtensionAppItem::ExecuteLaunchCommand(int event_flags) {
    344   Launch(event_flags);
    345 }
    346 
    347 void ExtensionAppItem::UpdatePositionFromExtensionOrdering() {
    348   const syncer::StringOrdinal& page =
    349       GetAppSorting(profile_)->GetPageOrdinal(extension_id_);
    350   const syncer::StringOrdinal& launch =
    351      GetAppSorting(profile_)->GetAppLaunchOrdinal(extension_id_);
    352   set_position(syncer::StringOrdinal(
    353       page.ToInternalValue() + launch.ToInternalValue()));
    354 }
    355 
    356 AppListControllerDelegate* ExtensionAppItem::GetController() {
    357   return AppListService::Get(chrome::GetActiveDesktop())->
    358       GetControllerDelegate();
    359 }
    360