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