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