app_info_summary_panel.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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.
4
5#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.h"
6
7#include <vector>
8
9#include "base/callback_forward.h"
10#include "base/command_line.h"
11#include "base/file_util.h"
12#include "base/i18n/time_formatting.h"
13#include "base/logging.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/task_runner_util.h"
16#include "chrome/browser/extensions/extension_service.h"
17#include "chrome/browser/extensions/launch_util.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/common/chrome_switches.h"
20#include "chrome/common/extensions/extension_constants.h"
21#include "chrome/grit/generated_resources.h"
22#include "content/public/browser/browser_thread.h"
23#include "extensions/browser/extension_prefs.h"
24#include "extensions/browser/extension_system.h"
25#include "extensions/common/extension.h"
26#include "extensions/common/manifest.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/layout/box_layout.h"
33#include "ui/views/layout/layout_constants.h"
34#include "ui/views/view.h"
35#include "ui/views/widget/widget.h"
36
37// A model for a combobox selecting the launch options for a hosted app.
38// Displays different options depending on the host OS.
39class LaunchOptionsComboboxModel : public ui::ComboboxModel {
40 public:
41  LaunchOptionsComboboxModel();
42  virtual ~LaunchOptionsComboboxModel();
43
44  extensions::LaunchType GetLaunchTypeAtIndex(int index) const;
45  int GetIndexForLaunchType(extensions::LaunchType launch_type) const;
46
47  // Overridden from ui::ComboboxModel:
48  virtual int GetItemCount() const OVERRIDE;
49  virtual base::string16 GetItemAt(int index) OVERRIDE;
50
51 private:
52  // A list of the launch types available in the combobox, in order.
53  std::vector<extensions::LaunchType> launch_types_;
54
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_;
58};
59
60LaunchOptionsComboboxModel::LaunchOptionsComboboxModel() {
61  if (CommandLine::ForCurrentProcess()->HasSwitch(
62          switches::kEnableStreamlinedHostedApps)) {
63    // Streamlined hosted apps can only toggle between LAUNCH_TYPE_WINDOW and
64    // LAUNCH_TYPE_REGULAR.
65    // TODO(sashab): Use a checkbox for this choice instead of combobox.
66    launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR);
67    launch_type_messages_.push_back(
68        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_TAB));
69
70    // Although LAUNCH_TYPE_WINDOW doesn't work on Mac, the streamlined hosted
71    // apps flag isn't available on Mac, so we must be on a non-Mac OS.
72    launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW);
73    launch_type_messages_.push_back(
74        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW));
75  } else {
76    launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR);
77    launch_type_messages_.push_back(
78        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_REGULAR));
79
80    launch_types_.push_back(extensions::LAUNCH_TYPE_PINNED);
81    launch_type_messages_.push_back(
82        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_PINNED));
83
84#if defined(OS_MACOSX)
85    // Mac does not support standalone web app browser windows or maximize.
86    launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN);
87    launch_type_messages_.push_back(
88        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN));
89#else
90    launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW);
91    launch_type_messages_.push_back(
92        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW));
93
94    // Even though the launch type is Full Screen, it is more accurately
95    // described as Maximized in non-Mac OSs.
96    launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN);
97    launch_type_messages_.push_back(
98        l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED));
99#endif
100  }
101}
102
103LaunchOptionsComboboxModel::~LaunchOptionsComboboxModel() {
104}
105
106extensions::LaunchType LaunchOptionsComboboxModel::GetLaunchTypeAtIndex(
107    int index) const {
108  return launch_types_[index];
109}
110
111int LaunchOptionsComboboxModel::GetIndexForLaunchType(
112    extensions::LaunchType launch_type) const {
113  for (size_t i = 0; i < launch_types_.size(); i++) {
114    if (launch_types_[i] == launch_type) {
115      return i;
116    }
117  }
118  // If the requested launch type is not available, just select the first one.
119  LOG(WARNING) << "Unavailable launch type " << launch_type << " selected.";
120  return 0;
121}
122
123int LaunchOptionsComboboxModel::GetItemCount() const {
124  return launch_types_.size();
125}
126
127base::string16 LaunchOptionsComboboxModel::GetItemAt(int index) {
128  return launch_type_messages_[index];
129}
130
131AppInfoSummaryPanel::AppInfoSummaryPanel(Profile* profile,
132                                         const extensions::Extension* app)
133    : AppInfoPanel(profile, app),
134      description_heading_(NULL),
135      description_label_(NULL),
136      details_heading_(NULL),
137      size_title_(NULL),
138      size_value_(NULL),
139      version_title_(NULL),
140      version_value_(NULL),
141      installed_time_title_(NULL),
142      installed_time_value_(NULL),
143      last_run_time_title_(NULL),
144      last_run_time_value_(NULL),
145      launch_options_combobox_(NULL),
146      weak_ptr_factory_(this) {
147  // Create UI elements.
148  CreateDescriptionControl();
149  CreateDetailsControl();
150  CreateLaunchOptionControl();
151
152  // Layout elements.
153  SetLayoutManager(
154      new views::BoxLayout(views::BoxLayout::kVertical,
155                           0,
156                           0,
157                           views::kUnrelatedControlVerticalSpacing));
158
159  LayoutDescriptionControl();
160  LayoutDetailsControl();
161
162  if (launch_options_combobox_)
163    AddChildView(launch_options_combobox_);
164}
165
166AppInfoSummaryPanel::~AppInfoSummaryPanel() {
167  // Destroy view children before their models.
168  RemoveAllChildViews(true);
169}
170
171void AppInfoSummaryPanel::CreateDescriptionControl() {
172  if (!app_->description().empty()) {
173    const size_t kMaxLength = 400;
174
175    base::string16 text = base::UTF8ToUTF16(app_->description());
176    if (text.length() > kMaxLength) {
177      text = text.substr(0, kMaxLength);
178      text += base::ASCIIToUTF16(" ... ");
179    }
180
181    description_heading_ = CreateHeading(
182        l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_DESCRIPTION_TITLE));
183    description_label_ = new views::Label(text);
184    description_label_->SetMultiLine(true);
185    description_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
186  }
187}
188
189void AppInfoSummaryPanel::CreateDetailsControl() {
190  // The size doesn't make sense for component apps.
191  if (app_->location() != extensions::Manifest::COMPONENT) {
192    size_title_ = new views::Label(
193        l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_SIZE_LABEL));
194    size_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
195
196    size_value_ = new views::Label(
197        l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_SIZE_LOADING_LABEL));
198    size_value_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
199
200    StartCalculatingAppSize();
201  }
202
203  // The version doesn't make sense for bookmark apps.
204  if (!app_->from_bookmark()) {
205    // Display 'Version: Built-in' for component apps.
206    base::string16 version_str = base::ASCIIToUTF16(app_->VersionString());
207    if (app_->location() == extensions::Manifest::COMPONENT)
208      version_str = l10n_util::GetStringUTF16(
209          IDS_APPLICATION_INFO_VERSION_BUILT_IN_LABEL);
210
211    version_title_ = new views::Label(
212        l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_VERSION_LABEL));
213    version_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
214
215    version_value_ = new views::Label(version_str);
216    version_value_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
217  }
218
219  // The install date doesn't make sense for component apps.
220  if (app_->location() != extensions::Manifest::COMPONENT) {
221    installed_time_title_ = new views::Label(
222        l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_INSTALLED_LABEL));
223    installed_time_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
224
225    installed_time_value_ =
226        new views::Label(base::TimeFormatShortDate(GetInstalledTime()));
227    installed_time_value_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
228  }
229
230  // The last run time is currently incorrect for component and hosted apps,
231  // since it is not updated when they are accessed outside of their shortcuts.
232  // TODO(sashab): Update the run time for these correctly: crbug.com/398716
233  if (app_->location() != extensions::Manifest::COMPONENT &&
234      !app_->is_hosted_app()) {
235    last_run_time_title_ = new views::Label(
236        l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_LAST_RUN_LABEL));
237    last_run_time_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
238
239    // Display 'Never' if the app has never been run.
240    base::string16 last_run_value_str =
241        l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_LAST_RUN_NEVER_LABEL);
242    if (GetLastLaunchedTime() != base::Time())
243      last_run_value_str = base::TimeFormatShortDate(GetLastLaunchedTime());
244
245    last_run_time_value_ = new views::Label(last_run_value_str);
246    last_run_time_value_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
247  }
248
249  // Only generate the heading if we have at least one field to display.
250  if (version_title_ || installed_time_title_ || last_run_time_title_) {
251    details_heading_ = CreateHeading(
252        l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_DETAILS_TITLE));
253  }
254}
255
256void AppInfoSummaryPanel::CreateLaunchOptionControl() {
257  if (CanSetLaunchType()) {
258    launch_options_combobox_model_.reset(new LaunchOptionsComboboxModel());
259    launch_options_combobox_ =
260        new views::Combobox(launch_options_combobox_model_.get());
261
262    launch_options_combobox_->set_listener(this);
263    launch_options_combobox_->SetSelectedIndex(
264        launch_options_combobox_model_->GetIndexForLaunchType(GetLaunchType()));
265  }
266}
267
268void AppInfoSummaryPanel::LayoutDescriptionControl() {
269  if (description_label_) {
270    DCHECK(description_heading_);
271    views::View* vertical_stack = CreateVerticalStack();
272    vertical_stack->AddChildView(description_heading_);
273    vertical_stack->AddChildView(description_label_);
274    AddChildView(vertical_stack);
275  }
276}
277
278void AppInfoSummaryPanel::LayoutDetailsControl() {
279  if (details_heading_) {
280    views::View* details_stack =
281        CreateVerticalStack(views::kRelatedControlSmallVerticalSpacing);
282
283    if (version_title_ && version_value_) {
284      details_stack->AddChildView(
285          CreateKeyValueField(version_title_, version_value_));
286    }
287
288    if (installed_time_title_ && installed_time_value_) {
289      details_stack->AddChildView(
290          CreateKeyValueField(installed_time_title_, installed_time_value_));
291    }
292
293    if (last_run_time_title_ && last_run_time_value_) {
294      details_stack->AddChildView(
295          CreateKeyValueField(last_run_time_title_, last_run_time_value_));
296    }
297
298    if (size_title_ && size_value_) {
299      details_stack->AddChildView(
300          CreateKeyValueField(size_title_, size_value_));
301    }
302
303    views::View* vertical_stack = CreateVerticalStack();
304    vertical_stack->AddChildView(details_heading_);
305    vertical_stack->AddChildView(details_stack);
306    AddChildView(vertical_stack);
307  }
308}
309
310void AppInfoSummaryPanel::OnPerformAction(views::Combobox* combobox) {
311  if (combobox == launch_options_combobox_) {
312    SetLaunchType(launch_options_combobox_model_->GetLaunchTypeAtIndex(
313        launch_options_combobox_->selected_index()));
314  } else {
315    NOTREACHED();
316  }
317}
318
319void AppInfoSummaryPanel::StartCalculatingAppSize() {
320  base::PostTaskAndReplyWithResult(
321      content::BrowserThread::GetBlockingPool(),
322      FROM_HERE,
323      base::Bind(&base::ComputeDirectorySize, app_->path()),
324      base::Bind(&AppInfoSummaryPanel::OnAppSizeCalculated, AsWeakPtr()));
325}
326
327void AppInfoSummaryPanel::OnAppSizeCalculated(int64 app_size_in_bytes) {
328  size_value_->SetText(ui::FormatBytes(app_size_in_bytes));
329}
330
331base::Time AppInfoSummaryPanel::GetInstalledTime() const {
332  return extensions::ExtensionPrefs::Get(profile_)->GetInstallTime(app_->id());
333}
334
335base::Time AppInfoSummaryPanel::GetLastLaunchedTime() const {
336  return extensions::ExtensionPrefs::Get(profile_)
337      ->GetLastLaunchTime(app_->id());
338}
339
340extensions::LaunchType AppInfoSummaryPanel::GetLaunchType() const {
341  return extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_),
342                                   app_);
343}
344
345void AppInfoSummaryPanel::SetLaunchType(
346    extensions::LaunchType launch_type) const {
347  DCHECK(CanSetLaunchType());
348  ExtensionService* service =
349      extensions::ExtensionSystem::Get(profile_)->extension_service();
350  extensions::SetLaunchType(service, app_->id(), launch_type);
351}
352
353bool AppInfoSummaryPanel::CanSetLaunchType() const {
354  // V2 apps don't have a launch type, and neither does the Chrome app.
355  return app_->id() != extension_misc::kChromeAppId && !app_->is_platform_app();
356}
357