Don't allow null bytes in hosts of host permissions.
[chromium-blink-merge.git] / ash / system / drive / tray_drive.cc
blob33217f193373a3e65c70a70cd02465699b4e4534
1 // 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.
5 #include "ash/system/drive/tray_drive.h"
7 #include <vector>
9 #include "ash/metrics/user_metrics_recorder.h"
10 #include "ash/shell.h"
11 #include "ash/system/tray/fixed_sized_scroll_view.h"
12 #include "ash/system/tray/hover_highlight_view.h"
13 #include "ash/system/tray/system_tray.h"
14 #include "ash/system/tray/system_tray_delegate.h"
15 #include "ash/system/tray/system_tray_notifier.h"
16 #include "ash/system/tray/tray_constants.h"
17 #include "ash/system/tray/tray_details_view.h"
18 #include "ash/system/tray/tray_item_more.h"
19 #include "ash/system/tray/tray_item_view.h"
20 #include "base/logging.h"
21 #include "base/stl_util.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "grit/ash_resources.h"
25 #include "grit/ash_strings.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/font.h"
29 #include "ui/gfx/image/image.h"
30 #include "ui/views/controls/button/image_button.h"
31 #include "ui/views/controls/image_view.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/controls/progress_bar.h"
34 #include "ui/views/layout/box_layout.h"
35 #include "ui/views/layout/grid_layout.h"
36 #include "ui/views/widget/widget.h"
38 namespace ash {
39 namespace {
41 const int kSidePadding = 8;
42 const int kHorizontalPadding = 6;
43 const int kVerticalPadding = 6;
44 const int kTopPadding = 6;
45 const int kBottomPadding = 10;
46 const int kProgressBarWidth = 100;
47 const int kProgressBarHeight = 11;
48 const int64 kHideDelayInMs = 1000;
50 base::string16 GetTrayLabel(const ash::DriveOperationStatusList& list) {
51 return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DRIVE_SYNCING,
52 base::IntToString16(static_cast<int>(list.size())));
55 scoped_ptr<ash::DriveOperationStatusList> GetCurrentOperationList() {
56 ash::SystemTrayDelegate* delegate =
57 ash::Shell::GetInstance()->system_tray_delegate();
58 scoped_ptr<ash::DriveOperationStatusList> list(
59 new ash::DriveOperationStatusList);
60 delegate->GetDriveOperationStatusList(list.get());
61 return list.Pass();
66 namespace tray {
68 class DriveDefaultView : public TrayItemMore {
69 public:
70 DriveDefaultView(SystemTrayItem* owner,
71 const DriveOperationStatusList* list)
72 : TrayItemMore(owner, true) {
73 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
75 SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DRIVE).ToImageSkia());
76 Update(list);
79 virtual ~DriveDefaultView() {}
81 void Update(const DriveOperationStatusList* list) {
82 DCHECK(list);
83 base::string16 label = GetTrayLabel(*list);
84 SetLabel(label);
85 SetAccessibleName(label);
88 private:
89 DISALLOW_COPY_AND_ASSIGN(DriveDefaultView);
92 class DriveDetailedView : public TrayDetailsView,
93 public ViewClickListener {
94 public:
95 DriveDetailedView(SystemTrayItem* owner,
96 const DriveOperationStatusList* list)
97 : TrayDetailsView(owner),
98 settings_(NULL),
99 in_progress_img_(NULL),
100 done_img_(NULL),
101 failed_img_(NULL) {
102 in_progress_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
103 IDR_AURA_UBER_TRAY_DRIVE);
104 done_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
105 IDR_AURA_UBER_TRAY_DRIVE_DONE);
106 failed_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
107 IDR_AURA_UBER_TRAY_DRIVE_FAILED);
109 Update(list);
112 virtual ~DriveDetailedView() {
113 STLDeleteValues(&update_map_);
116 void Update(const DriveOperationStatusList* list) {
117 AppendOperationList(list);
118 AppendSettings();
119 AppendHeaderEntry(list);
121 SchedulePaint();
124 private:
126 class OperationProgressBar : public views::ProgressBar {
127 public:
128 OperationProgressBar() {}
129 private:
131 // Overridden from View:
132 virtual gfx::Size GetPreferredSize() const OVERRIDE {
133 return gfx::Size(kProgressBarWidth, kProgressBarHeight);
136 DISALLOW_COPY_AND_ASSIGN(OperationProgressBar);
139 class RowView : public HoverHighlightView,
140 public views::ButtonListener {
141 public:
142 RowView(DriveDetailedView* parent,
143 ash::DriveOperationStatus::OperationState state,
144 double progress,
145 const base::FilePath& file_path,
146 int32 operation_id)
147 : HoverHighlightView(parent),
148 container_(parent),
149 status_img_(NULL),
150 label_container_(NULL),
151 progress_bar_(NULL),
152 cancel_button_(NULL),
153 operation_id_(operation_id) {
154 // Status image.
155 status_img_ = new views::ImageView();
156 AddChildView(status_img_);
158 label_container_ = new views::View();
159 label_container_->SetLayoutManager(new views::BoxLayout(
160 views::BoxLayout::kVertical, 0, 0, kVerticalPadding));
161 #if defined(OS_POSIX)
162 base::string16 file_label =
163 base::UTF8ToUTF16(file_path.BaseName().value());
164 #elif defined(OS_WIN)
165 base::string16 file_label =
166 base::WideToUTF16(file_path.BaseName().value());
167 #endif
168 views::Label* label = new views::Label(file_label);
169 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
170 label_container_->AddChildView(label);
171 // Add progress bar.
172 progress_bar_ = new OperationProgressBar();
173 label_container_->AddChildView(progress_bar_);
175 AddChildView(label_container_);
177 cancel_button_ = new views::ImageButton(this);
178 cancel_button_->SetImage(views::ImageButton::STATE_NORMAL,
179 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
180 IDR_AURA_UBER_TRAY_DRIVE_CANCEL));
181 cancel_button_->SetImage(views::ImageButton::STATE_HOVERED,
182 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
183 IDR_AURA_UBER_TRAY_DRIVE_CANCEL_HOVER));
185 UpdateStatus(state, progress);
186 AddChildView(cancel_button_);
189 void UpdateStatus(ash::DriveOperationStatus::OperationState state,
190 double progress) {
191 status_img_->SetImage(container_->GetImageForState(state));
192 progress_bar_->SetValue(progress);
193 cancel_button_->SetVisible(
194 state == ash::DriveOperationStatus::OPERATION_NOT_STARTED ||
195 state == ash::DriveOperationStatus::OPERATION_IN_PROGRESS);
198 private:
200 // views::View overrides.
201 virtual gfx::Size GetPreferredSize() const OVERRIDE {
202 return gfx::Size(
203 status_img_->GetPreferredSize().width() +
204 label_container_->GetPreferredSize().width() +
205 cancel_button_->GetPreferredSize().width() +
206 2 * kSidePadding + 2 * kHorizontalPadding,
207 std::max(status_img_->GetPreferredSize().height(),
208 std::max(label_container_->GetPreferredSize().height(),
209 cancel_button_->GetPreferredSize().height())) +
210 kTopPadding + kBottomPadding);
213 virtual void Layout() OVERRIDE {
214 gfx::Rect child_area(GetLocalBounds());
215 if (child_area.IsEmpty())
216 return;
218 int pos_x = child_area.x() + kSidePadding;
219 int pos_y = child_area.y() + kTopPadding;
221 gfx::Rect bounds_status(
222 gfx::Point(pos_x,
223 pos_y + (child_area.height() - kTopPadding -
224 kBottomPadding -
225 status_img_->GetPreferredSize().height())/2),
226 status_img_->GetPreferredSize());
227 status_img_->SetBoundsRect(
228 gfx::IntersectRects(bounds_status, child_area));
229 pos_x += status_img_->bounds().width() + kHorizontalPadding;
231 gfx::Rect bounds_label(pos_x,
232 pos_y,
233 child_area.width() - 2 * kSidePadding -
234 2 * kHorizontalPadding -
235 status_img_->GetPreferredSize().width() -
236 cancel_button_->GetPreferredSize().width(),
237 label_container_->GetPreferredSize().height());
238 label_container_->SetBoundsRect(
239 gfx::IntersectRects(bounds_label, child_area));
240 pos_x += label_container_->bounds().width() + kHorizontalPadding;
242 gfx::Rect bounds_button(
243 gfx::Point(pos_x,
244 pos_y + (child_area.height() - kTopPadding -
245 kBottomPadding -
246 cancel_button_->GetPreferredSize().height())/2),
247 cancel_button_->GetPreferredSize());
248 cancel_button_->SetBoundsRect(
249 gfx::IntersectRects(bounds_button, child_area));
252 // views::ButtonListener overrides.
253 virtual void ButtonPressed(views::Button* sender,
254 const ui::Event& event) OVERRIDE {
255 DCHECK(sender == cancel_button_);
256 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
257 ash::UMA_STATUS_AREA_DRIVE_CANCEL_OPERATION);
258 container_->OnCancelOperation(operation_id_);
261 DriveDetailedView* container_;
262 views::ImageView* status_img_;
263 views::View* label_container_;
264 views::ProgressBar* progress_bar_;
265 views::ImageButton* cancel_button_;
266 int32 operation_id_;
268 DISALLOW_COPY_AND_ASSIGN(RowView);
271 void AppendHeaderEntry(const DriveOperationStatusList* list) {
272 if (footer())
273 return;
274 CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE, this);
277 gfx::ImageSkia* GetImageForState(
278 ash::DriveOperationStatus::OperationState state) {
279 switch (state) {
280 case ash::DriveOperationStatus::OPERATION_NOT_STARTED:
281 case ash::DriveOperationStatus::OPERATION_IN_PROGRESS:
282 return in_progress_img_;
283 case ash::DriveOperationStatus::OPERATION_COMPLETED:
284 return done_img_;
285 case ash::DriveOperationStatus::OPERATION_FAILED:
286 return failed_img_;
288 return failed_img_;
291 void OnCancelOperation(int32 operation_id) {
292 SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
293 delegate->CancelDriveOperation(operation_id);
296 void AppendOperationList(const DriveOperationStatusList* list) {
297 if (!scroller())
298 CreateScrollableList();
300 // Apply the update.
301 std::set<base::FilePath> new_set;
302 bool item_list_changed = false;
303 for (DriveOperationStatusList::const_iterator it = list->begin();
304 it != list->end(); ++it) {
305 const DriveOperationStatus& operation = *it;
307 new_set.insert(operation.file_path);
308 std::map<base::FilePath, RowView*>::iterator existing_item =
309 update_map_.find(operation.file_path);
311 if (existing_item != update_map_.end()) {
312 existing_item->second->UpdateStatus(operation.state,
313 operation.progress);
314 } else {
315 RowView* row_view = new RowView(this,
316 operation.state,
317 operation.progress,
318 operation.file_path,
319 operation.id);
321 update_map_[operation.file_path] = row_view;
322 scroll_content()->AddChildView(row_view);
323 item_list_changed = true;
327 // Remove items from the list that haven't been added or modified with this
328 // update batch.
329 std::set<base::FilePath> remove_set;
330 for (std::map<base::FilePath, RowView*>::iterator update_iter =
331 update_map_.begin();
332 update_iter != update_map_.end(); ++update_iter) {
333 if (new_set.find(update_iter->first) == new_set.end()) {
334 remove_set.insert(update_iter->first);
338 for (std::set<base::FilePath>::iterator removed_iter = remove_set.begin();
339 removed_iter != remove_set.end(); ++removed_iter) {
340 delete update_map_[*removed_iter];
341 update_map_.erase(*removed_iter);
342 item_list_changed = true;
345 if (item_list_changed)
346 scroller()->Layout();
348 // Close the details if there is really nothing to show there anymore.
349 if (new_set.empty() && GetWidget())
350 GetWidget()->Close();
353 void AppendSettings() {
354 if (settings_)
355 return;
357 HoverHighlightView* container = new HoverHighlightView(this);
358 container->AddLabel(
359 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
360 IDS_ASH_STATUS_TRAY_DRIVE_SETTINGS),
361 gfx::ALIGN_LEFT,
362 gfx::Font::NORMAL);
363 AddChildView(container);
364 settings_ = container;
367 // Overridden from ViewClickListener.
368 virtual void OnViewClicked(views::View* sender) OVERRIDE {
369 SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
370 if (sender == footer()->content()) {
371 TransitionToDefaultView();
372 } else if (sender == settings_) {
373 delegate->ShowDriveSettings();
377 // Maps operation entries to their file paths.
378 std::map<base::FilePath, RowView*> update_map_;
379 views::View* settings_;
380 gfx::ImageSkia* in_progress_img_;
381 gfx::ImageSkia* done_img_;
382 gfx::ImageSkia* failed_img_;
384 DISALLOW_COPY_AND_ASSIGN(DriveDetailedView);
387 } // namespace tray
389 TrayDrive::TrayDrive(SystemTray* system_tray) :
390 TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_DRIVE_LIGHT),
391 default_(NULL),
392 detailed_(NULL) {
393 Shell::GetInstance()->system_tray_notifier()->AddDriveObserver(this);
396 TrayDrive::~TrayDrive() {
397 Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
400 bool TrayDrive::GetInitialVisibility() {
401 return false;
404 views::View* TrayDrive::CreateDefaultView(user::LoginStatus status) {
405 DCHECK(!default_);
407 if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
408 return NULL;
410 // If the list is empty AND the tray icon is invisible (= not in the margin
411 // duration of delayed item hiding), don't show the item.
412 scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
413 if (list->empty() && !tray_view()->visible())
414 return NULL;
416 default_ = new tray::DriveDefaultView(this, list.get());
417 return default_;
420 views::View* TrayDrive::CreateDetailedView(user::LoginStatus status) {
421 DCHECK(!detailed_);
423 if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
424 return NULL;
426 // If the list is empty AND the tray icon is invisible (= not in the margin
427 // duration of delayed item hiding), don't show the item.
428 scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
429 if (list->empty() && !tray_view()->visible())
430 return NULL;
432 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
433 ash::UMA_STATUS_AREA_DETAILED_DRIVE_VIEW);
434 detailed_ = new tray::DriveDetailedView(this, list.get());
435 return detailed_;
438 void TrayDrive::DestroyDefaultView() {
439 default_ = NULL;
442 void TrayDrive::DestroyDetailedView() {
443 detailed_ = NULL;
446 void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status) {
447 if (status == user::LOGGED_IN_USER || status == user::LOGGED_IN_OWNER)
448 return;
450 tray_view()->SetVisible(false);
451 DestroyDefaultView();
452 DestroyDetailedView();
455 void TrayDrive::OnDriveJobUpdated(const DriveOperationStatus& status) {
456 // The Drive job list manager changed its notification interface *not* to send
457 // the whole list of operations each time, to clarify which operation is
458 // updated and to reduce redundancy.
460 // TrayDrive should be able to benefit from the change, but for now, to
461 // incrementally migrate to the new way with minimum diffs, we still get the
462 // list of operations each time the event is fired.
463 // TODO(kinaba) http://crbug.com/128079 clean it up.
464 scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
465 bool is_new_item = true;
466 for (size_t i = 0; i < list->size(); ++i) {
467 if ((*list)[i].id == status.id) {
468 (*list)[i] = status;
469 is_new_item = false;
470 break;
473 if (is_new_item)
474 list->push_back(status);
476 // Check if all the operations are in the finished state.
477 bool all_jobs_finished = true;
478 for (size_t i = 0; i < list->size(); ++i) {
479 if ((*list)[i].state != DriveOperationStatus::OPERATION_COMPLETED &&
480 (*list)[i].state != DriveOperationStatus::OPERATION_FAILED) {
481 all_jobs_finished = false;
482 break;
486 if (all_jobs_finished) {
487 // If all the jobs ended, the tray item will be hidden after a certain
488 // amount of delay. This is to avoid flashes between sequentially executed
489 // Drive operations (see crbug/165679).
490 hide_timer_.Start(FROM_HERE,
491 base::TimeDelta::FromMilliseconds(kHideDelayInMs),
492 this,
493 &TrayDrive::HideIfNoOperations);
494 return;
497 // If the list is non-empty, stop the hiding timer (if any).
498 hide_timer_.Stop();
500 tray_view()->SetVisible(true);
501 if (default_)
502 default_->Update(list.get());
503 if (detailed_)
504 detailed_->Update(list.get());
507 void TrayDrive::HideIfNoOperations() {
508 DriveOperationStatusList empty_list;
510 tray_view()->SetVisible(false);
511 if (default_)
512 default_->Update(&empty_list);
513 if (detailed_)
514 detailed_->Update(&empty_list);
517 } // namespace ash