blob: 198df6688b4a354ea28040ab8f300372889d2c7a [file] [log] [blame]
[email protected]c82da8c42012-06-08 19:49:111// Copyright (c) 2012 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/extensions/extension_install_prompt.h"
6
7#include <map>
8
9#include "base/command_line.h"
[email protected]b19fe572013-07-18 04:54:2610#include "base/message_loop/message_loop.h"
[email protected]3853a4c2013-02-11 17:15:5711#include "base/prefs/pref_service.h"
[email protected]3ea1b182013-02-08 22:38:4112#include "base/strings/string_number_conversions.h"
[email protected]00e7bef2013-06-10 20:35:1713#include "base/strings/string_util.h"
14#include "base/strings/stringprintf.h"
[email protected]112158af2013-06-07 23:46:1815#include "base/strings/utf_string_conversions.h"
[email protected]c82da8c42012-06-08 19:49:1116#include "chrome/browser/extensions/bundle_installer.h"
[email protected]c82da8c42012-06-08 19:49:1117#include "chrome/browser/extensions/extension_install_ui.h"
[email protected]ec7de0c5a2012-11-16 07:40:4718#include "chrome/browser/extensions/image_loader.h"
[email protected]c82da8c42012-06-08 19:49:1119#include "chrome/browser/profiles/profile.h"
[email protected]4f3fb352013-07-17 04:07:0120#include "chrome/browser/signin/profile_oauth2_token_service.h"
21#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
[email protected]32fc4ff72012-06-15 21:50:0122#include "chrome/browser/ui/browser.h"
[email protected]619f86182012-07-03 21:30:1823#include "chrome/browser/ui/browser_window.h"
[email protected]c82da8c42012-06-08 19:49:1124#include "chrome/common/chrome_switches.h"
[email protected]29e0c4e72013-02-01 04:42:5625#include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h"
[email protected]faf87192012-08-17 00:07:5926#include "chrome/common/extensions/extension_constants.h"
[email protected]c82da8c42012-06-08 19:49:1127#include "chrome/common/extensions/extension_icon_set.h"
[email protected]abe720c02013-04-12 04:00:1028#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
[email protected]fc5077942012-08-15 21:37:5929#include "chrome/common/pref_names.h"
[email protected]91e51d612012-10-21 23:03:0530#include "content/public/browser/web_contents.h"
[email protected]5db2e882012-12-20 10:17:2631#include "content/public/browser/web_contents_view.h"
[email protected]e4452d32013-11-15 23:07:4132#include "extensions/common/extension.h"
[email protected]993da5e2013-03-23 21:25:1633#include "extensions/common/extension_resource.h"
[email protected]5ef835a2013-11-08 20:42:5734#include "extensions/common/feature_switch.h"
[email protected]d42c11152013-08-22 19:36:3235#include "extensions/common/manifest.h"
[email protected]6bf90612013-08-15 00:36:2736#include "extensions/common/manifest_constants.h"
[email protected]c41003472013-10-19 15:37:2537#include "extensions/common/permissions/permission_message_provider.h"
[email protected]5a55f3f2013-10-29 01:08:2938#include "extensions/common/permissions/permission_set.h"
[email protected]e4452d32013-11-15 23:07:4139#include "extensions/common/permissions/permissions_data.h"
[email protected]885c0e92012-11-13 20:27:4240#include "extensions/common/url_pattern.h"
[email protected]c82da8c42012-06-08 19:49:1141#include "grit/chromium_strings.h"
42#include "grit/generated_resources.h"
[email protected]2a281332012-07-11 22:20:2343#include "grit/theme_resources.h"
[email protected]c82da8c42012-06-08 19:49:1144#include "ui/base/l10n/l10n_util.h"
45#include "ui/base/resource/resource_bundle.h"
46#include "ui/gfx/image/image.h"
47
[email protected]c82da8c42012-06-08 19:49:1148using extensions::BundleInstaller;
49using extensions::Extension;
[email protected]1d5e58b2013-01-31 08:41:4050using extensions::Manifest;
[email protected]c2e66e12012-06-27 06:27:0651using extensions::PermissionSet;
[email protected]c82da8c42012-06-08 19:49:1152
[email protected]612a1cb12012-10-17 13:18:0353namespace {
54
[email protected]c82da8c42012-06-08 19:49:1155static const int kTitleIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
56 0, // The regular install prompt depends on what's being installed.
57 IDS_EXTENSION_INLINE_INSTALL_PROMPT_TITLE,
58 IDS_EXTENSION_INSTALL_PROMPT_TITLE,
59 IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE,
[email protected]612a1cb12012-10-17 13:18:0360 IDS_EXTENSION_PERMISSIONS_PROMPT_TITLE,
61 IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_TITLE,
[email protected]15d267b42013-02-14 23:43:3262 IDS_EXTENSION_POST_INSTALL_PERMISSIONS_PROMPT_TITLE,
[email protected]1a93d8d2013-10-27 23:09:2063 IDS_EXTENSION_LAUNCH_APP_PROMPT_TITLE,
[email protected]c82da8c42012-06-08 19:49:1164};
65static const int kHeadingIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
66 IDS_EXTENSION_INSTALL_PROMPT_HEADING,
67 0, // Inline installs use the extension name.
68 0, // Heading for bundle installs depends on the bundle contents.
69 IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING,
[email protected]612a1cb12012-10-17 13:18:0370 IDS_EXTENSION_PERMISSIONS_PROMPT_HEADING,
[email protected]846606012012-10-19 18:42:2571 0, // External installs use different strings for extensions/apps.
[email protected]15d267b42013-02-14 23:43:3272 IDS_EXTENSION_POST_INSTALL_PERMISSIONS_PROMPT_HEADING,
[email protected]1a93d8d2013-10-27 23:09:2073 IDS_EXTENSION_LAUNCH_APP_PROMPT_HEADING,
[email protected]15d267b42013-02-14 23:43:3274};
75static const int kButtons[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
76 ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
77 ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
78 ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
79 ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
80 ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
81 ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
82 ui::DIALOG_BUTTON_CANCEL,
[email protected]aa5a5472013-10-04 03:41:0483 ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
[email protected]c82da8c42012-06-08 19:49:1184};
85static const int kAcceptButtonIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
86 IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
87 IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
88 IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
89 IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON,
[email protected]612a1cb12012-10-17 13:18:0390 IDS_EXTENSION_PROMPT_PERMISSIONS_BUTTON,
[email protected]846606012012-10-19 18:42:2591 0, // External installs use different strings for extensions/apps.
[email protected]a2886e8b2013-06-08 05:15:0292 IDS_EXTENSION_PROMPT_PERMISSIONS_CLEAR_RETAINED_FILES_BUTTON,
[email protected]1a93d8d2013-10-27 23:09:2093 IDS_EXTENSION_PROMPT_LAUNCH_BUTTON,
[email protected]c82da8c42012-06-08 19:49:1194};
95static const int kAbortButtonIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
96 0, // These all use the platform's default cancel label.
97 0,
98 0,
99 0,
[email protected]612a1cb12012-10-17 13:18:03100 IDS_EXTENSION_PROMPT_PERMISSIONS_ABORT_BUTTON,
101 IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_ABORT_BUTTON,
[email protected]15d267b42013-02-14 23:43:32102 IDS_CLOSE,
[email protected]bc123c02013-10-15 06:41:13103 0, // Platform dependent cancel button.
[email protected]c82da8c42012-06-08 19:49:11104};
105static const int kPermissionsHeaderIds[
106 ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
107 IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
108 IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
109 IDS_EXTENSION_PROMPT_THESE_WILL_HAVE_ACCESS_TO,
110 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO,
111 IDS_EXTENSION_PROMPT_WANTS_ACCESS_TO,
[email protected]612a1cb12012-10-17 13:18:03112 IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
[email protected]15d267b42013-02-14 23:43:32113 IDS_EXTENSION_PROMPT_CAN_ACCESS,
[email protected]1a93d8d2013-10-27 23:09:20114 IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
[email protected]c82da8c42012-06-08 19:49:11115};
[email protected]612a1cb12012-10-17 13:18:03116static const int kOAuthHeaderIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
[email protected]fc5077942012-08-15 21:37:59117 IDS_EXTENSION_PROMPT_OAUTH_HEADER,
118 0, // Inline installs don't show OAuth permissions.
119 0, // Bundle installs don't show OAuth permissions.
120 IDS_EXTENSION_PROMPT_OAUTH_REENABLE_HEADER,
121 IDS_EXTENSION_PROMPT_OAUTH_PERMISSIONS_HEADER,
[email protected]612a1cb12012-10-17 13:18:03122 0,
[email protected]15d267b42013-02-14 23:43:32123 0,
[email protected]aa5a5472013-10-04 03:41:04124 IDS_EXTENSION_PROMPT_OAUTH_HEADER,
[email protected]fc5077942012-08-15 21:37:59125};
[email protected]c82da8c42012-06-08 19:49:11126
[email protected]c82da8c42012-06-08 19:49:11127// Size of extension icon in top left of dialog.
128const int kIconSize = 69;
129
[email protected]dd46a4ce2012-09-15 10:50:50130// Returns pixel size under maximal scale factor for the icon whose device
131// independent size is |size_in_dip|
132int GetSizeForMaxScaleFactor(int size_in_dip) {
[email protected]50b66262013-09-24 03:25:48133 return static_cast<int>(size_in_dip * gfx::ImageSkia::GetMaxSupportedScale());
[email protected]dd46a4ce2012-09-15 10:50:50134}
135
136// Returns bitmap for the default icon with size equal to the default icon's
137// pixel size under maximal supported scale factor.
138SkBitmap GetDefaultIconBitmapForMaxScaleFactor(bool is_app) {
[email protected]702d8b42013-02-27 20:55:50139 const gfx::ImageSkia& image = is_app ?
140 extensions::IconsInfo::GetDefaultAppIcon() :
141 extensions::IconsInfo::GetDefaultExtensionIcon();
[email protected]50b66262013-09-24 03:25:48142 return image.GetRepresentation(
143 gfx::ImageSkia::GetMaxSupportedScale()).sk_bitmap();
[email protected]dd46a4ce2012-09-15 10:50:50144}
145
[email protected]af6efb22012-10-12 02:23:05146// If auto confirm is enabled then posts a task to proceed with or cancel the
147// install and returns true. Otherwise returns false.
148bool AutoConfirmPrompt(ExtensionInstallPrompt::Delegate* delegate) {
149 const CommandLine* cmdline = CommandLine::ForCurrentProcess();
150 if (!cmdline->HasSwitch(switches::kAppsGalleryInstallAutoConfirmForTests))
151 return false;
152 std::string value = cmdline->GetSwitchValueASCII(
153 switches::kAppsGalleryInstallAutoConfirmForTests);
154
155 // We use PostTask instead of calling the delegate directly here, because in
156 // the real implementations it's highly likely the message loop will be
157 // pumping a few times before the user clicks accept or cancel.
158 if (value == "accept") {
[email protected]b3a25092013-05-28 22:08:16159 base::MessageLoop::current()->PostTask(
[email protected]af6efb22012-10-12 02:23:05160 FROM_HERE,
161 base::Bind(&ExtensionInstallPrompt::Delegate::InstallUIProceed,
162 base::Unretained(delegate)));
163 return true;
164 }
165
166 if (value == "cancel") {
[email protected]b3a25092013-05-28 22:08:16167 base::MessageLoop::current()->PostTask(
[email protected]af6efb22012-10-12 02:23:05168 FROM_HERE,
169 base::Bind(&ExtensionInstallPrompt::Delegate::InstallUIAbort,
170 base::Unretained(delegate),
171 true));
172 return true;
173 }
174
175 NOTREACHED();
176 return false;
177}
178
[email protected]91e51d612012-10-21 23:03:05179Profile* ProfileForWebContents(content::WebContents* web_contents) {
180 if (!web_contents)
181 return NULL;
182 return Profile::FromBrowserContext(web_contents->GetBrowserContext());
183}
184
[email protected]5db2e882012-12-20 10:17:26185gfx::NativeWindow NativeWindowForWebContents(content::WebContents* contents) {
186 if (!contents)
187 return NULL;
188
189 return contents->GetView()->GetTopLevelNativeWindow();
190}
191
[email protected]c82da8c42012-06-08 19:49:11192} // namespace
193
[email protected]5db2e882012-12-20 10:17:26194ExtensionInstallPrompt::Prompt::Prompt(PromptType type)
[email protected]c82da8c42012-06-08 19:49:11195 : type_(type),
[email protected]79a6f99a2013-08-29 00:32:58196 is_showing_details_for_retained_files_(false),
[email protected]c82da8c42012-06-08 19:49:11197 extension_(NULL),
198 bundle_(NULL),
199 average_rating_(0.0),
[email protected]dcde34b32013-07-31 02:28:45200 rating_count_(0),
201 show_user_count_(false) {
[email protected]c82da8c42012-06-08 19:49:11202}
203
204ExtensionInstallPrompt::Prompt::~Prompt() {
205}
206
207void ExtensionInstallPrompt::Prompt::SetPermissions(
208 const std::vector<string16>& permissions) {
209 permissions_ = permissions;
210}
211
[email protected]8ab7e6c2013-07-11 21:15:03212void ExtensionInstallPrompt::Prompt::SetPermissionsDetails(
213 const std::vector<string16>& details) {
214 details_ = details;
[email protected]79a6f99a2013-08-29 00:32:58215 is_showing_details_for_permissions_.clear();
216 for (size_t i = 0; i < details.size(); ++i)
217 is_showing_details_for_permissions_.push_back(false);
218}
219
220void ExtensionInstallPrompt::Prompt::SetIsShowingDetails(
221 DetailsType type,
222 size_t index,
223 bool is_showing_details) {
224 switch (type) {
225 case PERMISSIONS_DETAILS:
226 is_showing_details_for_permissions_[index] = is_showing_details;
227 break;
228 case OAUTH_DETAILS:
229 is_showing_details_for_oauth_[index] = is_showing_details;
230 break;
231 case RETAINED_FILES_DETAILS:
232 is_showing_details_for_retained_files_ = is_showing_details;
233 break;
234 }
[email protected]8ab7e6c2013-07-11 21:15:03235}
236
[email protected]b70a2d92012-06-28 19:51:21237void ExtensionInstallPrompt::Prompt::SetOAuthIssueAdvice(
238 const IssueAdviceInfo& issue_advice) {
[email protected]79a6f99a2013-08-29 00:32:58239 is_showing_details_for_oauth_.clear();
240 for (size_t i = 0; i < issue_advice.size(); ++i)
241 is_showing_details_for_oauth_.push_back(false);
242
[email protected]b70a2d92012-06-28 19:51:21243 oauth_issue_advice_ = issue_advice;
244}
245
[email protected]5db2e882012-12-20 10:17:26246void ExtensionInstallPrompt::Prompt::SetUserNameFromProfile(Profile* profile) {
247 // |profile| can be NULL in unit tests.
248 if (profile) {
249 oauth_user_name_ = UTF8ToUTF16(profile->GetPrefs()->GetString(
250 prefs::kGoogleServicesUsername));
251 } else {
252 oauth_user_name_.clear();
253 }
254}
255
[email protected]c82da8c42012-06-08 19:49:11256void ExtensionInstallPrompt::Prompt::SetInlineInstallWebstoreData(
257 const std::string& localized_user_count,
[email protected]dcde34b32013-07-31 02:28:45258 bool show_user_count,
[email protected]c82da8c42012-06-08 19:49:11259 double average_rating,
260 int rating_count) {
261 CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
262 localized_user_count_ = localized_user_count;
[email protected]dcde34b32013-07-31 02:28:45263 show_user_count_ = show_user_count;
[email protected]c82da8c42012-06-08 19:49:11264 average_rating_ = average_rating;
265 rating_count_ = rating_count;
266}
267
268string16 ExtensionInstallPrompt::Prompt::GetDialogTitle() const {
[email protected]c82da8c42012-06-08 19:49:11269 int resource_id = kTitleIds[type_];
270
271 if (type_ == INSTALL_PROMPT) {
272 if (extension_->is_app())
273 resource_id = IDS_EXTENSION_INSTALL_APP_PROMPT_TITLE;
274 else if (extension_->is_theme())
275 resource_id = IDS_EXTENSION_INSTALL_THEME_PROMPT_TITLE;
276 else
277 resource_id = IDS_EXTENSION_INSTALL_EXTENSION_PROMPT_TITLE;
[email protected]612a1cb12012-10-17 13:18:03278 } else if (type_ == EXTERNAL_INSTALL_PROMPT) {
279 return l10n_util::GetStringFUTF16(
280 resource_id, UTF8ToUTF16(extension_->name()));
[email protected]c82da8c42012-06-08 19:49:11281 }
282
283 return l10n_util::GetStringUTF16(resource_id);
284}
285
286string16 ExtensionInstallPrompt::Prompt::GetHeading() const {
287 if (type_ == INLINE_INSTALL_PROMPT) {
288 return UTF8ToUTF16(extension_->name());
289 } else if (type_ == BUNDLE_INSTALL_PROMPT) {
290 return bundle_->GetHeadingTextFor(BundleInstaller::Item::STATE_PENDING);
[email protected]612a1cb12012-10-17 13:18:03291 } else if (type_ == EXTERNAL_INSTALL_PROMPT) {
[email protected]846606012012-10-19 18:42:25292 int resource_id = -1;
293 if (extension_->is_app())
294 resource_id = IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_HEADING_APP;
295 else if (extension_->is_theme())
296 resource_id = IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_HEADING_THEME;
297 else
298 resource_id = IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_HEADING_EXTENSION;
299 return l10n_util::GetStringUTF16(resource_id);
[email protected]c82da8c42012-06-08 19:49:11300 } else {
301 return l10n_util::GetStringFUTF16(
302 kHeadingIds[type_], UTF8ToUTF16(extension_->name()));
303 }
304}
305
[email protected]15d267b42013-02-14 23:43:32306int ExtensionInstallPrompt::Prompt::GetDialogButtons() const {
[email protected]a2886e8b2013-06-08 05:15:02307 if (type_ == POST_INSTALL_PERMISSIONS_PROMPT &&
308 ShouldDisplayRevokeFilesButton()) {
309 return kButtons[type_] | ui::DIALOG_BUTTON_OK;
310 }
311
[email protected]15d267b42013-02-14 23:43:32312 return kButtons[type_];
313}
314
315bool ExtensionInstallPrompt::Prompt::HasAcceptButtonLabel() const {
[email protected]a2886e8b2013-06-08 05:15:02316 if (kAcceptButtonIds[type_] == 0)
317 return false;
318
319 if (type_ == POST_INSTALL_PERMISSIONS_PROMPT)
320 return ShouldDisplayRevokeFilesButton();
321
322 return true;
[email protected]15d267b42013-02-14 23:43:32323}
324
[email protected]c82da8c42012-06-08 19:49:11325string16 ExtensionInstallPrompt::Prompt::GetAcceptButtonLabel() const {
[email protected]846606012012-10-19 18:42:25326 if (type_ == EXTERNAL_INSTALL_PROMPT) {
327 int id = -1;
328 if (extension_->is_app())
329 id = IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_ACCEPT_BUTTON_APP;
330 else if (extension_->is_theme())
331 id = IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_ACCEPT_BUTTON_THEME;
332 else
333 id = IDS_EXTENSION_EXTERNAL_INSTALL_PROMPT_ACCEPT_BUTTON_EXTENSION;
334 return l10n_util::GetStringUTF16(id);
335 }
[email protected]c82da8c42012-06-08 19:49:11336 return l10n_util::GetStringUTF16(kAcceptButtonIds[type_]);
337}
338
339bool ExtensionInstallPrompt::Prompt::HasAbortButtonLabel() const {
340 return kAbortButtonIds[type_] > 0;
341}
342
343string16 ExtensionInstallPrompt::Prompt::GetAbortButtonLabel() const {
344 CHECK(HasAbortButtonLabel());
345 return l10n_util::GetStringUTF16(kAbortButtonIds[type_]);
346}
347
348string16 ExtensionInstallPrompt::Prompt::GetPermissionsHeading() const {
349 return l10n_util::GetStringUTF16(kPermissionsHeaderIds[type_]);
350}
351
[email protected]813475e2012-07-02 23:54:18352string16 ExtensionInstallPrompt::Prompt::GetOAuthHeading() const {
[email protected]5db2e882012-12-20 10:17:26353 return l10n_util::GetStringFUTF16(kOAuthHeaderIds[type_], oauth_user_name_);
[email protected]813475e2012-07-02 23:54:18354}
355
[email protected]a2886e8b2013-06-08 05:15:02356string16 ExtensionInstallPrompt::Prompt::GetRetainedFilesHeading() const {
[email protected]4f1e1082013-09-23 10:30:53357 const int kRetainedFilesMessageIDs[6] = {
358 IDS_EXTENSION_PROMPT_RETAINED_FILES_DEFAULT,
359 IDS_EXTENSION_PROMPT_RETAINED_FILE_SINGULAR,
360 IDS_EXTENSION_PROMPT_RETAINED_FILES_ZERO,
361 IDS_EXTENSION_PROMPT_RETAINED_FILES_TWO,
362 IDS_EXTENSION_PROMPT_RETAINED_FILES_FEW,
363 IDS_EXTENSION_PROMPT_RETAINED_FILES_MANY,
364 };
365 std::vector<int> message_ids;
366 for (size_t i = 0; i < arraysize(kRetainedFilesMessageIDs); i++) {
367 message_ids.push_back(kRetainedFilesMessageIDs[i]);
368 }
369 return l10n_util::GetPluralStringFUTF16(message_ids, GetRetainedFileCount());
[email protected]8ab7e6c2013-07-11 21:15:03370}
371
[email protected]f2cd8992013-06-11 17:30:18372bool ExtensionInstallPrompt::Prompt::ShouldShowPermissions() const {
373 return GetPermissionCount() > 0 || type_ == POST_INSTALL_PERMISSIONS_PROMPT;
374}
375
[email protected]c82da8c42012-06-08 19:49:11376void ExtensionInstallPrompt::Prompt::AppendRatingStars(
377 StarAppender appender, void* data) const {
378 CHECK(appender);
379 CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
380 int rating_integer = floor(average_rating_);
381 double rating_fractional = average_rating_ - rating_integer;
382
383 if (rating_fractional > 0.66) {
384 rating_integer++;
385 }
386
387 if (rating_fractional < 0.33 || rating_fractional > 0.66) {
388 rating_fractional = 0;
389 }
390
391 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
392 int i;
393 for (i = 0; i < rating_integer; i++) {
394 appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_ON), data);
395 }
396 if (rating_fractional) {
397 appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_HALF_LEFT), data);
398 i++;
399 }
400 for (; i < kMaxExtensionRating; i++) {
401 appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_OFF), data);
402 }
403}
404
405string16 ExtensionInstallPrompt::Prompt::GetRatingCount() const {
406 CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
[email protected]6725048e2013-10-24 21:47:14407 return l10n_util::GetStringFUTF16(IDS_EXTENSION_RATING_COUNT,
408 base::IntToString16(rating_count_));
[email protected]c82da8c42012-06-08 19:49:11409}
410
411string16 ExtensionInstallPrompt::Prompt::GetUserCount() const {
412 CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
[email protected]dcde34b32013-07-31 02:28:45413
414 if (show_user_count_) {
[email protected]6725048e2013-10-24 21:47:14415 return l10n_util::GetStringFUTF16(IDS_EXTENSION_USER_COUNT,
416 base::UTF8ToUTF16(localized_user_count_));
[email protected]dcde34b32013-07-31 02:28:45417 }
[email protected]6725048e2013-10-24 21:47:14418 return base::string16();
[email protected]c82da8c42012-06-08 19:49:11419}
420
421size_t ExtensionInstallPrompt::Prompt::GetPermissionCount() const {
422 return permissions_.size();
423}
424
[email protected]8ab7e6c2013-07-11 21:15:03425size_t ExtensionInstallPrompt::Prompt::GetPermissionsDetailsCount() const {
426 return details_.size();
427}
428
[email protected]c82da8c42012-06-08 19:49:11429string16 ExtensionInstallPrompt::Prompt::GetPermission(size_t index) const {
430 CHECK_LT(index, permissions_.size());
[email protected]ef2654e42012-08-11 03:57:56431 return permissions_[index];
[email protected]c82da8c42012-06-08 19:49:11432}
433
[email protected]8ab7e6c2013-07-11 21:15:03434string16 ExtensionInstallPrompt::Prompt::GetPermissionsDetails(
435 size_t index) const {
436 CHECK_LT(index, details_.size());
437 return details_[index];
438}
439
[email protected]79a6f99a2013-08-29 00:32:58440bool ExtensionInstallPrompt::Prompt::GetIsShowingDetails(
441 DetailsType type, size_t index) const {
442 switch (type) {
443 case PERMISSIONS_DETAILS:
444 CHECK_LT(index, is_showing_details_for_permissions_.size());
445 return is_showing_details_for_permissions_[index];
446 case OAUTH_DETAILS:
447 CHECK_LT(index, is_showing_details_for_oauth_.size());
448 return is_showing_details_for_oauth_[index];
449 case RETAINED_FILES_DETAILS:
450 return is_showing_details_for_retained_files_;
451 }
452 return false;
453}
454
[email protected]b70a2d92012-06-28 19:51:21455size_t ExtensionInstallPrompt::Prompt::GetOAuthIssueCount() const {
456 return oauth_issue_advice_.size();
457}
458
459const IssueAdviceInfoEntry& ExtensionInstallPrompt::Prompt::GetOAuthIssue(
460 size_t index) const {
461 CHECK_LT(index, oauth_issue_advice_.size());
462 return oauth_issue_advice_[index];
463}
464
[email protected]a2886e8b2013-06-08 05:15:02465size_t ExtensionInstallPrompt::Prompt::GetRetainedFileCount() const {
466 return retained_files_.size();
467}
468
469string16 ExtensionInstallPrompt::Prompt::GetRetainedFile(size_t index) const {
470 CHECK_LT(index, retained_files_.size());
[email protected]6725048e2013-10-24 21:47:14471 return retained_files_[index].AsUTF16Unsafe();
[email protected]a2886e8b2013-06-08 05:15:02472}
473
474bool ExtensionInstallPrompt::Prompt::ShouldDisplayRevokeFilesButton() const {
475 return !retained_files_.empty();
476}
477
[email protected]5db2e882012-12-20 10:17:26478ExtensionInstallPrompt::ShowParams::ShowParams(content::WebContents* contents)
479 : parent_web_contents(contents),
480 parent_window(NativeWindowForWebContents(contents)),
481 navigator(contents) {
482}
483
484ExtensionInstallPrompt::ShowParams::ShowParams(
485 gfx::NativeWindow window,
486 content::PageNavigator* navigator)
487 : parent_web_contents(NULL),
488 parent_window(window),
489 navigator(navigator) {
490}
491
[email protected]c82da8c42012-06-08 19:49:11492// static
493scoped_refptr<Extension>
494 ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
495 const DictionaryValue* manifest,
[email protected]c422a862012-07-31 15:46:13496 int flags,
[email protected]c82da8c42012-06-08 19:49:11497 const std::string& id,
498 const std::string& localized_name,
499 const std::string& localized_description,
500 std::string* error) {
501 scoped_ptr<DictionaryValue> localized_manifest;
502 if (!localized_name.empty() || !localized_description.empty()) {
503 localized_manifest.reset(manifest->DeepCopy());
504 if (!localized_name.empty()) {
[email protected]6bf90612013-08-15 00:36:27505 localized_manifest->SetString(extensions::manifest_keys::kName,
[email protected]c82da8c42012-06-08 19:49:11506 localized_name);
507 }
508 if (!localized_description.empty()) {
[email protected]6bf90612013-08-15 00:36:27509 localized_manifest->SetString(extensions::manifest_keys::kDescription,
[email protected]c82da8c42012-06-08 19:49:11510 localized_description);
511 }
512 }
513
514 return Extension::Create(
[email protected]650b2d52013-02-10 03:41:45515 base::FilePath(),
[email protected]1d5e58b2013-01-31 08:41:40516 Manifest::INTERNAL,
[email protected]c82da8c42012-06-08 19:49:11517 localized_manifest.get() ? *localized_manifest.get() : *manifest,
[email protected]c422a862012-07-31 15:46:13518 flags,
[email protected]c82da8c42012-06-08 19:49:11519 id,
520 error);
521}
522
[email protected]619f86182012-07-03 21:30:18523ExtensionInstallPrompt::ExtensionInstallPrompt(
[email protected]91e51d612012-10-21 23:03:05524 content::WebContents* contents)
[email protected]16798da832012-08-30 20:46:04525 : record_oauth2_grant_(false),
[email protected]b3a25092013-05-28 22:08:16526 ui_loop_(base::MessageLoop::current()),
[email protected]c82da8c42012-06-08 19:49:11527 extension_(NULL),
[email protected]91e51d612012-10-21 23:03:05528 install_ui_(ExtensionInstallUI::Create(ProfileForWebContents(contents))),
[email protected]5db2e882012-12-20 10:17:26529 show_params_(contents),
[email protected]c82da8c42012-06-08 19:49:11530 delegate_(NULL),
[email protected]5db2e882012-12-20 10:17:26531 prompt_(UNSET_PROMPT_TYPE) {
532 prompt_.SetUserNameFromProfile(install_ui_->profile());
533}
534
535ExtensionInstallPrompt::ExtensionInstallPrompt(
536 Profile* profile,
537 gfx::NativeWindow native_window,
538 content::PageNavigator* navigator)
539 : record_oauth2_grant_(false),
[email protected]b3a25092013-05-28 22:08:16540 ui_loop_(base::MessageLoop::current()),
[email protected]5db2e882012-12-20 10:17:26541 extension_(NULL),
542 install_ui_(ExtensionInstallUI::Create(profile)),
543 show_params_(native_window, navigator),
544 delegate_(NULL),
545 prompt_(UNSET_PROMPT_TYPE) {
546 prompt_.SetUserNameFromProfile(install_ui_->profile());
[email protected]c82da8c42012-06-08 19:49:11547}
548
549ExtensionInstallPrompt::~ExtensionInstallPrompt() {
550}
551
552void ExtensionInstallPrompt::ConfirmBundleInstall(
553 extensions::BundleInstaller* bundle,
[email protected]c2e66e12012-06-27 06:27:06554 const PermissionSet* permissions) {
[email protected]b3a25092013-05-28 22:08:16555 DCHECK(ui_loop_ == base::MessageLoop::current());
[email protected]c82da8c42012-06-08 19:49:11556 bundle_ = bundle;
557 permissions_ = permissions;
558 delegate_ = bundle;
[email protected]5db2e882012-12-20 10:17:26559 prompt_.set_type(BUNDLE_INSTALL_PROMPT);
[email protected]c82da8c42012-06-08 19:49:11560
[email protected]4e494872013-09-06 09:13:54561 ShowConfirmation();
[email protected]c82da8c42012-06-08 19:49:11562}
563
[email protected]734bcec2012-10-08 20:29:05564void ExtensionInstallPrompt::ConfirmStandaloneInstall(
[email protected]c82da8c42012-06-08 19:49:11565 Delegate* delegate,
566 const Extension* extension,
567 SkBitmap* icon,
568 const ExtensionInstallPrompt::Prompt& prompt) {
[email protected]b3a25092013-05-28 22:08:16569 DCHECK(ui_loop_ == base::MessageLoop::current());
[email protected]c82da8c42012-06-08 19:49:11570 extension_ = extension;
571 permissions_ = extension->GetActivePermissions();
572 delegate_ = delegate;
573 prompt_ = prompt;
[email protected]c82da8c42012-06-08 19:49:11574
575 SetIcon(icon);
[email protected]4e494872013-09-06 09:13:54576 ShowConfirmation();
[email protected]c82da8c42012-06-08 19:49:11577}
578
[email protected]af6efb22012-10-12 02:23:05579void ExtensionInstallPrompt::ConfirmWebstoreInstall(
580 Delegate* delegate,
581 const Extension* extension,
582 const SkBitmap* icon,
583 const ShowDialogCallback& show_dialog_callback) {
[email protected]c82da8c42012-06-08 19:49:11584 // SetIcon requires |extension_| to be set. ConfirmInstall will setup the
585 // remaining fields.
586 extension_ = extension;
587 SetIcon(icon);
[email protected]af6efb22012-10-12 02:23:05588 ConfirmInstall(delegate, extension, show_dialog_callback);
[email protected]c82da8c42012-06-08 19:49:11589}
590
[email protected]af6efb22012-10-12 02:23:05591void ExtensionInstallPrompt::ConfirmInstall(
592 Delegate* delegate,
593 const Extension* extension,
594 const ShowDialogCallback& show_dialog_callback) {
[email protected]b3a25092013-05-28 22:08:16595 DCHECK(ui_loop_ == base::MessageLoop::current());
[email protected]c82da8c42012-06-08 19:49:11596 extension_ = extension;
597 permissions_ = extension->GetActivePermissions();
598 delegate_ = delegate;
[email protected]5db2e882012-12-20 10:17:26599 prompt_.set_type(INSTALL_PROMPT);
[email protected]af6efb22012-10-12 02:23:05600 show_dialog_callback_ = show_dialog_callback;
[email protected]c82da8c42012-06-08 19:49:11601
602 // We special-case themes to not show any confirm UI. Instead they are
603 // immediately installed, and then we show an infobar (see OnInstallSuccess)
604 // to allow the user to revert if they don't like it.
605 //
606 // We don't do this in the case where off-store extension installs are
607 // disabled because in that case, we don't show the dangerous download UI, so
608 // we need the UI confirmation.
609 if (extension->is_theme()) {
610 if (extension->from_webstore() ||
[email protected]544471a2012-10-13 05:27:09611 extensions::FeatureSwitch::easy_off_store_install()->IsEnabled()) {
[email protected]c82da8c42012-06-08 19:49:11612 delegate->InstallUIProceed();
613 return;
614 }
615 }
616
617 LoadImageIfNeeded();
618}
619
620void ExtensionInstallPrompt::ConfirmReEnable(Delegate* delegate,
621 const Extension* extension) {
[email protected]b3a25092013-05-28 22:08:16622 DCHECK(ui_loop_ == base::MessageLoop::current());
[email protected]c82da8c42012-06-08 19:49:11623 extension_ = extension;
624 permissions_ = extension->GetActivePermissions();
625 delegate_ = delegate;
[email protected]5db2e882012-12-20 10:17:26626 prompt_.set_type(RE_ENABLE_PROMPT);
[email protected]c82da8c42012-06-08 19:49:11627
628 LoadImageIfNeeded();
629}
630
[email protected]612a1cb12012-10-17 13:18:03631void ExtensionInstallPrompt::ConfirmExternalInstall(
[email protected]c8ff7c9e2013-04-20 12:36:27632 Delegate* delegate,
633 const Extension* extension,
634 const ShowDialogCallback& show_dialog_callback) {
[email protected]b3a25092013-05-28 22:08:16635 DCHECK(ui_loop_ == base::MessageLoop::current());
[email protected]612a1cb12012-10-17 13:18:03636 extension_ = extension;
637 permissions_ = extension->GetActivePermissions();
638 delegate_ = delegate;
[email protected]5db2e882012-12-20 10:17:26639 prompt_.set_type(EXTERNAL_INSTALL_PROMPT);
[email protected]c8ff7c9e2013-04-20 12:36:27640 show_dialog_callback_ = show_dialog_callback;
[email protected]612a1cb12012-10-17 13:18:03641
642 LoadImageIfNeeded();
643}
644
[email protected]c82da8c42012-06-08 19:49:11645void ExtensionInstallPrompt::ConfirmPermissions(
646 Delegate* delegate,
647 const Extension* extension,
[email protected]c2e66e12012-06-27 06:27:06648 const PermissionSet* permissions) {
[email protected]b3a25092013-05-28 22:08:16649 DCHECK(ui_loop_ == base::MessageLoop::current());
[email protected]c82da8c42012-06-08 19:49:11650 extension_ = extension;
651 permissions_ = permissions;
652 delegate_ = delegate;
[email protected]5db2e882012-12-20 10:17:26653 prompt_.set_type(PERMISSIONS_PROMPT);
[email protected]c82da8c42012-06-08 19:49:11654
655 LoadImageIfNeeded();
656}
657
[email protected]f746b3f2012-07-03 17:53:37658void ExtensionInstallPrompt::ConfirmIssueAdvice(
659 Delegate* delegate,
660 const Extension* extension,
661 const IssueAdviceInfo& issue_advice) {
[email protected]b3a25092013-05-28 22:08:16662 DCHECK(ui_loop_ == base::MessageLoop::current());
[email protected]f746b3f2012-07-03 17:53:37663 extension_ = extension;
664 delegate_ = delegate;
[email protected]5db2e882012-12-20 10:17:26665 prompt_.set_type(PERMISSIONS_PROMPT);
[email protected]f746b3f2012-07-03 17:53:37666
667 record_oauth2_grant_ = true;
668 prompt_.SetOAuthIssueAdvice(issue_advice);
669
670 LoadImageIfNeeded();
671}
672
[email protected]a2886e8b2013-06-08 05:15:02673void ExtensionInstallPrompt::ReviewPermissions(
674 Delegate* delegate,
675 const Extension* extension,
676 const std::vector<base::FilePath>& retained_file_paths) {
[email protected]b3a25092013-05-28 22:08:16677 DCHECK(ui_loop_ == base::MessageLoop::current());
[email protected]15d267b42013-02-14 23:43:32678 extension_ = extension;
679 permissions_ = extension->GetActivePermissions();
[email protected]a2886e8b2013-06-08 05:15:02680 prompt_.set_retained_files(retained_file_paths);
[email protected]15d267b42013-02-14 23:43:32681 delegate_ = delegate;
682 prompt_.set_type(POST_INSTALL_PERMISSIONS_PROMPT);
683
684 LoadImageIfNeeded();
685}
686
[email protected]c82da8c42012-06-08 19:49:11687void ExtensionInstallPrompt::OnInstallSuccess(const Extension* extension,
688 SkBitmap* icon) {
689 extension_ = extension;
690 SetIcon(icon);
691
692 install_ui_->OnInstallSuccess(extension, &icon_);
693}
694
[email protected]bf3d9df2012-07-24 23:20:27695void ExtensionInstallPrompt::OnInstallFailure(
696 const extensions::CrxInstallerError& error) {
[email protected]c82da8c42012-06-08 19:49:11697 install_ui_->OnInstallFailure(error);
698}
699
700void ExtensionInstallPrompt::SetIcon(const SkBitmap* image) {
701 if (image)
702 icon_ = *image;
703 else
704 icon_ = SkBitmap();
[email protected]dd46a4ce2012-09-15 10:50:50705 if (icon_.empty()) {
706 // Let's set default icon bitmap whose size is equal to the default icon's
707 // pixel size under maximal supported scale factor. If the bitmap is larger
708 // than the one we need, it will be scaled down by the ui code.
709 icon_ = GetDefaultIconBitmapForMaxScaleFactor(extension_->is_app());
710 }
[email protected]c82da8c42012-06-08 19:49:11711}
712
[email protected]ec7de0c5a2012-11-16 07:40:47713void ExtensionInstallPrompt::OnImageLoaded(const gfx::Image& image) {
[email protected]c82da8c42012-06-08 19:49:11714 SetIcon(image.IsEmpty() ? NULL : image.ToSkBitmap());
[email protected]4e494872013-09-06 09:13:54715 ShowConfirmation();
[email protected]c82da8c42012-06-08 19:49:11716}
717
718void ExtensionInstallPrompt::LoadImageIfNeeded() {
719 // Bundle install prompts do not have an icon.
[email protected]5db2e882012-12-20 10:17:26720 // Also |install_ui_.profile()| can be NULL in unit tests.
721 if (!icon_.empty() || !install_ui_->profile()) {
[email protected]4e494872013-09-06 09:13:54722 ShowConfirmation();
[email protected]c82da8c42012-06-08 19:49:11723 return;
724 }
725
726 // Load the image asynchronously. For the response, check OnImageLoaded.
[email protected]993da5e2013-03-23 21:25:16727 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
[email protected]702d8b42013-02-27 20:55:50728 extension_,
729 extension_misc::EXTENSION_ICON_LARGE,
730 ExtensionIconSet::MATCH_BIGGER);
[email protected]dd46a4ce2012-09-15 10:50:50731 // Load the icon whose pixel size is large enough to be displayed under
732 // maximal supported scale factor. UI code will scale the icon down if needed.
733 // TODO(tbarzic): We should use IconImage here and load the required bitmap
734 // lazily.
735 int pixel_size = GetSizeForMaxScaleFactor(kIconSize);
[email protected]5db2e882012-12-20 10:17:26736 extensions::ImageLoader::Get(install_ui_->profile())->LoadImageAsync(
[email protected]ec7de0c5a2012-11-16 07:40:47737 extension_, image, gfx::Size(pixel_size, pixel_size),
738 base::Bind(&ExtensionInstallPrompt::OnImageLoaded, AsWeakPtr()));
[email protected]c82da8c42012-06-08 19:49:11739}
740
[email protected]4f3fb352013-07-17 04:07:01741void ExtensionInstallPrompt::OnGetTokenSuccess(
742 const OAuth2TokenService::Request* request,
743 const std::string& access_token,
744 const base::Time& expiration_time) {
745 DCHECK_EQ(login_token_request_.get(), request);
746 login_token_request_.reset();
747
748 const extensions::OAuth2Info& oauth2_info =
749 extensions::OAuth2Info::GetOAuth2Info(extension_);
750
[email protected]b70a2d92012-06-28 19:51:21751 token_flow_.reset(new OAuth2MintTokenFlow(
[email protected]4f3fb352013-07-17 04:07:01752 install_ui_->profile()->GetRequestContext(),
[email protected]b70a2d92012-06-28 19:51:21753 this,
754 OAuth2MintTokenFlow::Parameters(
[email protected]4f3fb352013-07-17 04:07:01755 access_token,
[email protected]b70a2d92012-06-28 19:51:21756 extension_->id(),
757 oauth2_info.client_id,
[email protected]d4a37f1c2012-07-09 21:36:13758 oauth2_info.scopes,
[email protected]b70a2d92012-06-28 19:51:21759 OAuth2MintTokenFlow::MODE_ISSUE_ADVICE)));
760 token_flow_->Start();
761}
762
[email protected]4f3fb352013-07-17 04:07:01763void ExtensionInstallPrompt::OnGetTokenFailure(
764 const OAuth2TokenService::Request* request,
765 const GoogleServiceAuthError& error) {
766 DCHECK_EQ(login_token_request_.get(), request);
767 login_token_request_.reset();
768 ShowConfirmation();
769}
770
[email protected]b70a2d92012-06-28 19:51:21771void ExtensionInstallPrompt::OnIssueAdviceSuccess(
772 const IssueAdviceInfo& advice_info) {
773 prompt_.SetOAuthIssueAdvice(advice_info);
774 record_oauth2_grant_ = true;
775 ShowConfirmation();
776}
777
778void ExtensionInstallPrompt::OnMintTokenFailure(
779 const GoogleServiceAuthError& error) {
780 ShowConfirmation();
781}
782
[email protected]c82da8c42012-06-08 19:49:11783void ExtensionInstallPrompt::ShowConfirmation() {
[email protected]dc24976f2013-06-02 21:15:09784 if (permissions_.get() &&
[email protected]13c68b62013-05-17 11:29:05785 (!extension_ ||
786 !extensions::PermissionsData::ShouldSkipPermissionWarnings(
787 extension_))) {
[email protected]1d5e58b2013-01-31 08:41:40788 Manifest::Type extension_type = extension_ ?
789 extension_->GetType() : Manifest::TYPE_UNKNOWN;
[email protected]8ab7e6c2013-07-11 21:15:03790 prompt_.SetPermissions(
[email protected]c41003472013-10-19 15:37:25791 extensions::PermissionMessageProvider::Get()->
792 GetWarningMessages(permissions_, extension_type));
[email protected]8ab7e6c2013-07-11 21:15:03793 prompt_.SetPermissionsDetails(
[email protected]c41003472013-10-19 15:37:25794 extensions::PermissionMessageProvider::Get()->
795 GetWarningMessagesDetails(permissions_, extension_type));
[email protected]bebe1d02012-08-02 20:17:09796 }
[email protected]c82da8c42012-06-08 19:49:11797
[email protected]5db2e882012-12-20 10:17:26798 switch (prompt_.type()) {
[email protected]c82da8c42012-06-08 19:49:11799 case PERMISSIONS_PROMPT:
800 case RE_ENABLE_PROMPT:
[email protected]c82da8c42012-06-08 19:49:11801 case INLINE_INSTALL_PROMPT:
[email protected]612a1cb12012-10-17 13:18:03802 case EXTERNAL_INSTALL_PROMPT:
[email protected]15d267b42013-02-14 23:43:32803 case INSTALL_PROMPT:
[email protected]1a93d8d2013-10-27 23:09:20804 case LAUNCH_PROMPT:
[email protected]15d267b42013-02-14 23:43:32805 case POST_INSTALL_PERMISSIONS_PROMPT: {
[email protected]c82da8c42012-06-08 19:49:11806 prompt_.set_extension(extension_);
[email protected]32e7a9b2013-01-23 23:00:19807 prompt_.set_icon(gfx::Image::CreateFrom1xBitmap(icon_));
[email protected]c82da8c42012-06-08 19:49:11808 break;
809 }
810 case BUNDLE_INSTALL_PROMPT: {
811 prompt_.set_bundle(bundle_);
[email protected]c82da8c42012-06-08 19:49:11812 break;
813 }
814 default:
815 NOTREACHED() << "Unknown message";
[email protected]af6efb22012-10-12 02:23:05816 return;
[email protected]c82da8c42012-06-08 19:49:11817 }
[email protected]af6efb22012-10-12 02:23:05818
819 if (AutoConfirmPrompt(delegate_))
820 return;
821
[email protected]5db2e882012-12-20 10:17:26822 if (show_dialog_callback_.is_null())
823 GetDefaultShowDialogCallback().Run(show_params_, delegate_, prompt_);
824 else
825 show_dialog_callback_.Run(show_params_, delegate_, prompt_);
[email protected]c82da8c42012-06-08 19:49:11826}