1 // Copyright 2020 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/extensions/extension_installed_bubble_model.h"
6 
7 #include "base/strings/utf_string_conversions.h"
8 #include "chrome/browser/extensions/api/commands/command_service.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/sync/sync_promo_ui.h"
12 #include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
13 #include "chrome/common/extensions/command.h"
14 #include "chrome/common/extensions/sync_helper.h"
15 #include "chrome/grit/chromium_strings.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "extensions/common/api/extension_action/action_info.h"
18 #include "extensions/common/extension.h"
19 #include "ui/base/l10n/l10n_util.h"
20 #include "ui/gfx/image/image_skia_operations.h"
21 
22 namespace {
23 
CommandForExtensionAction(const extensions::Extension * extension,Profile * profile)24 base::Optional<extensions::Command> CommandForExtensionAction(
25     const extensions::Extension* extension,
26     Profile* profile) {
27   const auto* info = extensions::ActionInfo::GetExtensionActionInfo(extension);
28 
29   if (!info)
30     return base::nullopt;
31 
32   auto* service = extensions::CommandService::Get(profile);
33   extensions::Command command;
34 
35   if (service->GetExtensionActionCommand(extension->id(), info->type,
36                                          extensions::CommandService::ACTIVE,
37                                          &command, nullptr)) {
38     return command;
39   }
40 
41   return base::nullopt;
42 }
43 
MakeHowToUseText(const extensions::ActionInfo * action,base::Optional<extensions::Command> command,const std::string & keyword)44 base::string16 MakeHowToUseText(const extensions::ActionInfo* action,
45                                 base::Optional<extensions::Command> command,
46                                 const std::string& keyword) {
47   base::string16 extra;
48   if (command.has_value())
49     extra = command->accelerator().GetShortcutText();
50 
51   int message_id = 0;
52   if (action && action->type == extensions::ActionInfo::TYPE_BROWSER) {
53     message_id =
54         extra.empty()
55             ? IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO
56             : IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT;
57   } else if (action && action->type == extensions::ActionInfo::TYPE_PAGE) {
58     message_id = extra.empty()
59                      ? IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO
60                      : IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT;
61   } else if (!keyword.empty()) {
62     extra = base::UTF8ToUTF16(keyword);
63     message_id = IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO;
64   }
65 
66   if (!message_id)
67     return base::string16();
68 
69   return extra.empty() ? l10n_util::GetStringUTF16(message_id)
70                        : l10n_util::GetStringFUTF16(message_id, extra);
71 }
72 
73 }  // namespace
74 
ExtensionInstalledBubbleModel(Profile * profile,const extensions::Extension * extension,const SkBitmap & icon)75 ExtensionInstalledBubbleModel::ExtensionInstalledBubbleModel(
76     Profile* profile,
77     const extensions::Extension* extension,
78     const SkBitmap& icon)
79     : icon_(icon),
80       extension_id_(extension->id()),
81       extension_name_(extension->name()) {
82   const std::string& keyword = extensions::OmniboxInfo::GetKeyword(extension);
83   base::Optional<extensions::Command> command =
84       CommandForExtensionAction(extension, profile);
85   const auto* action_info =
86       extensions::ActionInfo::GetExtensionActionInfo(extension);
87 
88   // TODO(ellyjones): There is no logical reason why TYPE_ACTION should be
89   // different here, but the existing bubble behaves this way.
90   const bool toolbar_action =
91       action_info && action_info->type != extensions::ActionInfo::TYPE_ACTION;
92 
93   anchor_to_action_ = toolbar_action;
94   anchor_to_omnibox_ = !toolbar_action && !keyword.empty();
95 
96   show_how_to_use_ =
97       (toolbar_action && !action_info->synthesized) || !keyword.empty();
98   // If there's a shortcut, don't show the how-to-manage text because it
99   // clutters the bubble.
100   show_how_to_manage_ = !command.has_value() || anchor_to_omnibox_;
101   show_key_binding_ = command.has_value();
102 
103   show_sign_in_promo_ = extensions::sync_helper::IsSyncable(extension) &&
104                         SyncPromoUI::ShouldShowSyncPromo(profile);
105 
106   if (show_how_to_use_)
107     how_to_use_text_ = MakeHowToUseText(action_info, command, keyword);
108 }
109 
110 ExtensionInstalledBubbleModel::~ExtensionInstalledBubbleModel() = default;
111 
GetHowToUseText() const112 base::string16 ExtensionInstalledBubbleModel::GetHowToUseText() const {
113   DCHECK(show_how_to_use_);
114   return how_to_use_text_;
115 }
116 
MakeIconOfSize(const gfx::Size & wanted) const117 gfx::ImageSkia ExtensionInstalledBubbleModel::MakeIconOfSize(
118     const gfx::Size& wanted) const {
119   gfx::Size size(icon_.width(), icon_.height());
120   if (size.width() > wanted.width() || size.height() > wanted.height())
121     size.SetSize(wanted.width(), wanted.height());
122 
123   return gfx::ImageSkiaOperations::CreateResizedImage(
124       gfx::ImageSkia::CreateFrom1xBitmap(icon_),
125       skia::ImageOperations::RESIZE_BEST, size);
126 }
127