1 // Copyright 2014 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.
5 #include "chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.h"
9 #include "base/callback_forward.h"
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/task_runner_util.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/extension_util.h"
16 #include "chrome/browser/extensions/launch_util.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/extensions/extension_constants.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "extensions/browser/extension_prefs.h"
22 #include "extensions/browser/extension_system.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/manifest.h"
25 #include "extensions/common/manifest_handlers/shared_module_info.h"
26 #include "extensions/common/manifest_url_handlers.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/models/combobox_model.h"
29 #include "ui/base/text/bytes_formatting.h"
30 #include "ui/views/controls/combobox/combobox.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/controls/link.h"
33 #include "ui/views/layout/box_layout.h"
34 #include "ui/views/layout/layout_constants.h"
35 #include "ui/views/view.h"
37 // A model for a combobox selecting the launch options for a hosted app.
38 // Displays different options depending on the host OS.
39 class LaunchOptionsComboboxModel : public ui::ComboboxModel {
41 LaunchOptionsComboboxModel();
42 ~LaunchOptionsComboboxModel() override;
44 extensions::LaunchType GetLaunchTypeAtIndex(int index) const;
45 int GetIndexForLaunchType(extensions::LaunchType launch_type) const;
47 // Overridden from ui::ComboboxModel:
48 int GetItemCount() const override;
49 base::string16 GetItemAt(int index) override;
52 // A list of the launch types available in the combobox, in order.
53 std::vector<extensions::LaunchType> launch_types_;
55 // A list of the messages to display in the combobox, in order. The indexes in
56 // this list correspond to the indexes in launch_types_.
57 std::vector<base::string16> launch_type_messages_;
60 LaunchOptionsComboboxModel::LaunchOptionsComboboxModel() {
61 if (extensions::util::IsStreamlinedHostedAppsEnabled()) {
62 // Streamlined hosted apps can only toggle between LAUNCH_TYPE_WINDOW and
63 // LAUNCH_TYPE_REGULAR.
64 // TODO(sashab): Use a checkbox for this choice instead of combobox.
65 launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR);
66 launch_type_messages_.push_back(
67 l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_TAB));
69 // Although LAUNCH_TYPE_WINDOW doesn't work on Mac, the streamlined hosted
70 // apps flag isn't available on Mac, so we must be on a non-Mac OS.
71 launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW);
72 launch_type_messages_.push_back(
73 l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW));
75 launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR);
76 launch_type_messages_.push_back(
77 l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_REGULAR));
79 launch_types_.push_back(extensions::LAUNCH_TYPE_PINNED);
80 launch_type_messages_.push_back(
81 l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_PINNED));
83 #if defined(OS_MACOSX)
84 // Mac does not support standalone web app browser windows or maximize.
85 launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN);
86 launch_type_messages_.push_back(
87 l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN));
89 launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW);
90 launch_type_messages_.push_back(
91 l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW));
93 // Even though the launch type is Full Screen, it is more accurately
94 // described as Maximized in non-Mac OSs.
95 launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN);
96 launch_type_messages_.push_back(
97 l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED));
102 LaunchOptionsComboboxModel::~LaunchOptionsComboboxModel() {
105 extensions::LaunchType LaunchOptionsComboboxModel::GetLaunchTypeAtIndex(
107 return launch_types_[index];
110 int LaunchOptionsComboboxModel::GetIndexForLaunchType(
111 extensions::LaunchType launch_type) const {
112 for (size_t i = 0; i < launch_types_.size(); i++) {
113 if (launch_types_[i] == launch_type) {
117 // If the requested launch type is not available, just select the first one.
118 LOG(WARNING) << "Unavailable launch type " << launch_type << " selected.";
122 int LaunchOptionsComboboxModel::GetItemCount() const {
123 return launch_types_.size();
126 base::string16 LaunchOptionsComboboxModel::GetItemAt(int index) {
127 return launch_type_messages_[index];
130 AppInfoSummaryPanel::AppInfoSummaryPanel(Profile* profile,
131 const extensions::Extension* app)
132 : AppInfoPanel(profile, app),
134 homepage_link_(NULL),
135 licenses_link_(NULL),
136 launch_options_combobox_(NULL),
137 weak_ptr_factory_(this) {
138 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical,
141 views::kRelatedControlVerticalSpacing));
146 AppInfoSummaryPanel::~AppInfoSummaryPanel() {
147 // Destroy view children before their models.
148 RemoveAllChildViews(true);
151 void AppInfoSummaryPanel::AddDescriptionAndLinksControl(
152 views::View* vertical_stack) {
153 views::View* description_and_labels_stack = new views::View();
154 description_and_labels_stack->SetLayoutManager(
155 new views::BoxLayout(views::BoxLayout::kVertical,
158 views::kRelatedControlSmallVerticalSpacing));
160 if (!app_->description().empty()) {
161 // TODO(sashab): Clip the app's description to 4 lines, and use Label's
162 // built-in elide behavior to add ellipses at the end: crbug.com/358053
163 const size_t max_length = 400;
164 base::string16 text = base::UTF8ToUTF16(app_->description());
165 if (text.length() > max_length) {
166 text = text.substr(0, max_length);
167 text += base::ASCIIToUTF16(" ... ");
170 views::Label* description_label = new views::Label(text);
171 description_label->SetMultiLine(true);
172 description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
173 description_and_labels_stack->AddChildView(description_label);
176 if (CanShowAppHomePage()) {
177 homepage_link_ = new views::Link(
178 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_HOMEPAGE_LINK));
179 homepage_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
180 homepage_link_->set_listener(this);
181 description_and_labels_stack->AddChildView(homepage_link_);
184 if (CanDisplayLicenses()) {
185 licenses_link_ = new views::Link(
186 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_LICENSES_BUTTON_TEXT));
187 licenses_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
188 licenses_link_->set_listener(this);
189 description_and_labels_stack->AddChildView(licenses_link_);
192 vertical_stack->AddChildView(description_and_labels_stack);
195 void AppInfoSummaryPanel::AddDetailsControl(views::View* vertical_stack) {
196 // Component apps have no details.
197 if (app_->location() == extensions::Manifest::COMPONENT)
200 views::View* details_list =
201 CreateVerticalStack(views::kRelatedControlSmallVerticalSpacing);
204 views::Label* size_title = new views::Label(
205 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_SIZE_LABEL));
206 size_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
208 size_value_ = new views::Label(
209 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_SIZE_LOADING_LABEL));
210 size_value_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
211 StartCalculatingAppSize();
213 details_list->AddChildView(CreateKeyValueField(size_title, size_value_));
215 // The version doesn't make sense for bookmark apps.
216 if (!app_->from_bookmark()) {
217 views::Label* version_title = new views::Label(
218 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_VERSION_LABEL));
219 version_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
221 views::Label* version_value =
222 new views::Label(base::UTF8ToUTF16(app_->VersionString()));
223 version_value->SetHorizontalAlignment(gfx::ALIGN_LEFT);
225 details_list->AddChildView(
226 CreateKeyValueField(version_title, version_value));
229 vertical_stack->AddChildView(details_list);
232 void AppInfoSummaryPanel::AddLaunchOptionControl(views::View* vertical_stack) {
233 if (!CanSetLaunchType())
236 launch_options_combobox_model_.reset(new LaunchOptionsComboboxModel());
237 launch_options_combobox_ =
238 new views::Combobox(launch_options_combobox_model_.get());
239 launch_options_combobox_->set_listener(this);
240 launch_options_combobox_->SetSelectedIndex(
241 launch_options_combobox_model_->GetIndexForLaunchType(GetLaunchType()));
243 vertical_stack->AddChildView(launch_options_combobox_);
246 void AppInfoSummaryPanel::AddSubviews() {
247 AddChildView(CreateHeading(
248 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_APP_OVERVIEW_TITLE)));
250 views::View* vertical_stack =
251 CreateVerticalStack(views::kUnrelatedControlVerticalSpacing);
252 AddChildView(vertical_stack);
254 AddDescriptionAndLinksControl(vertical_stack);
255 AddDetailsControl(vertical_stack);
256 AddLaunchOptionControl(vertical_stack);
259 void AppInfoSummaryPanel::OnPerformAction(views::Combobox* combobox) {
260 if (combobox == launch_options_combobox_) {
261 SetLaunchType(launch_options_combobox_model_->GetLaunchTypeAtIndex(
262 launch_options_combobox_->selected_index()));
268 void AppInfoSummaryPanel::LinkClicked(views::Link* source, int event_flags) {
269 if (source == homepage_link_) {
271 } else if (source == licenses_link_) {
278 void AppInfoSummaryPanel::StartCalculatingAppSize() {
279 base::PostTaskAndReplyWithResult(
280 content::BrowserThread::GetBlockingPool(),
282 base::Bind(&base::ComputeDirectorySize, app_->path()),
283 base::Bind(&AppInfoSummaryPanel::OnAppSizeCalculated, AsWeakPtr()));
286 void AppInfoSummaryPanel::OnAppSizeCalculated(int64 app_size_in_bytes) {
287 const int one_mebibyte_in_bytes = 1024 * 1024;
288 if (app_size_in_bytes < one_mebibyte_in_bytes) {
289 size_value_->SetText(
290 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_SIZE_SMALL_LABEL));
292 size_value_->SetText(ui::FormatBytesWithUnits(
293 app_size_in_bytes, ui::DATA_UNITS_MEBIBYTE, true));
297 extensions::LaunchType AppInfoSummaryPanel::GetLaunchType() const {
298 return extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_),
302 void AppInfoSummaryPanel::SetLaunchType(
303 extensions::LaunchType launch_type) const {
304 DCHECK(CanSetLaunchType());
305 ExtensionService* service =
306 extensions::ExtensionSystem::Get(profile_)->extension_service();
307 extensions::SetLaunchType(service, app_->id(), launch_type);
310 bool AppInfoSummaryPanel::CanSetLaunchType() const {
311 // V2 apps and extensions don't have a launch type, and neither does the
313 return !app_->is_platform_app() && !app_->is_extension() &&
314 app_->id() != extension_misc::kChromeAppId;
316 void AppInfoSummaryPanel::ShowAppHomePage() {
317 DCHECK(CanShowAppHomePage());
318 OpenLink(extensions::ManifestURL::GetHomepageURL(app_));
322 bool AppInfoSummaryPanel::CanShowAppHomePage() const {
323 return extensions::ManifestURL::SpecifiedHomepageURL(app_);
326 void AppInfoSummaryPanel::DisplayLicenses() {
327 DCHECK(CanDisplayLicenses());
328 for (const auto& license_url : GetLicenseUrls())
329 OpenLink(license_url);
333 bool AppInfoSummaryPanel::CanDisplayLicenses() const {
334 return !GetLicenseUrls().empty();
337 const std::vector<GURL> AppInfoSummaryPanel::GetLicenseUrls() const {
338 if (!extensions::SharedModuleInfo::ImportsModules(app_))
339 return std::vector<GURL>();
341 std::vector<GURL> license_urls;
342 ExtensionService* service =
343 extensions::ExtensionSystem::Get(profile_)->extension_service();
345 const std::vector<extensions::SharedModuleInfo::ImportInfo>& imports =
346 extensions::SharedModuleInfo::GetImports(app_);
348 for (const auto& shared_module : imports) {
349 const extensions::Extension* imported_module =
350 service->GetExtensionById(shared_module.extension_id, true);
351 DCHECK(imported_module);
353 GURL about_page = extensions::ManifestURL::GetAboutPage(imported_module);
354 if (about_page != GURL::EmptyGURL())
355 license_urls.push_back(about_page);