• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "ash/system/drive/tray_drive.h"
6 
7 #include <vector>
8 
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"
37 
38 namespace ash {
39 
40 namespace internal {
41 
42 namespace {
43 
44 const int kSidePadding = 8;
45 const int kHorizontalPadding = 6;
46 const int kVerticalPadding = 6;
47 const int kTopPadding = 6;
48 const int kBottomPadding = 10;
49 const int kProgressBarWidth = 100;
50 const int kProgressBarHeight = 11;
51 const int64 kHideDelayInMs = 1000;
52 
GetTrayLabel(const ash::DriveOperationStatusList & list)53 base::string16 GetTrayLabel(const ash::DriveOperationStatusList& list) {
54   return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DRIVE_SYNCING,
55       base::IntToString16(static_cast<int>(list.size())));
56 }
57 
GetCurrentOperationList()58 scoped_ptr<ash::DriveOperationStatusList> GetCurrentOperationList() {
59   ash::SystemTrayDelegate* delegate =
60       ash::Shell::GetInstance()->system_tray_delegate();
61   scoped_ptr<ash::DriveOperationStatusList> list(
62       new ash::DriveOperationStatusList);
63   delegate->GetDriveOperationStatusList(list.get());
64   return list.Pass();
65 }
66 
67 }
68 
69 namespace tray {
70 
71 class DriveDefaultView : public TrayItemMore {
72  public:
DriveDefaultView(SystemTrayItem * owner,const DriveOperationStatusList * list)73   DriveDefaultView(SystemTrayItem* owner,
74                    const DriveOperationStatusList* list)
75       : TrayItemMore(owner, true) {
76     ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
77 
78     SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DRIVE).ToImageSkia());
79     Update(list);
80   }
81 
~DriveDefaultView()82   virtual ~DriveDefaultView() {}
83 
Update(const DriveOperationStatusList * list)84   void Update(const DriveOperationStatusList* list) {
85     DCHECK(list);
86     base::string16 label = GetTrayLabel(*list);
87     SetLabel(label);
88     SetAccessibleName(label);
89   }
90 
91  private:
92   DISALLOW_COPY_AND_ASSIGN(DriveDefaultView);
93 };
94 
95 class DriveDetailedView : public TrayDetailsView,
96                           public ViewClickListener {
97  public:
DriveDetailedView(SystemTrayItem * owner,const DriveOperationStatusList * list)98   DriveDetailedView(SystemTrayItem* owner,
99                     const DriveOperationStatusList* list)
100       : TrayDetailsView(owner),
101         settings_(NULL),
102         in_progress_img_(NULL),
103         done_img_(NULL),
104         failed_img_(NULL) {
105     in_progress_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
106         IDR_AURA_UBER_TRAY_DRIVE);
107     done_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
108         IDR_AURA_UBER_TRAY_DRIVE_DONE);
109     failed_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
110         IDR_AURA_UBER_TRAY_DRIVE_FAILED);
111 
112     Update(list);
113   }
114 
~DriveDetailedView()115   virtual ~DriveDetailedView() {
116     STLDeleteValues(&update_map_);
117   }
118 
Update(const DriveOperationStatusList * list)119   void Update(const DriveOperationStatusList* list) {
120     AppendOperationList(list);
121     AppendSettings();
122     AppendHeaderEntry(list);
123 
124     SchedulePaint();
125   }
126 
127  private:
128 
129   class OperationProgressBar : public views::ProgressBar {
130    public:
OperationProgressBar()131     OperationProgressBar() {}
132    private:
133 
134     // Overridden from View:
GetPreferredSize()135     virtual gfx::Size GetPreferredSize() OVERRIDE {
136       return gfx::Size(kProgressBarWidth, kProgressBarHeight);
137     }
138 
139     DISALLOW_COPY_AND_ASSIGN(OperationProgressBar);
140   };
141 
142   class RowView : public HoverHighlightView,
143                   public views::ButtonListener {
144    public:
RowView(DriveDetailedView * parent,ash::DriveOperationStatus::OperationState state,double progress,const base::FilePath & file_path,int32 operation_id)145     RowView(DriveDetailedView* parent,
146             ash::DriveOperationStatus::OperationState state,
147             double progress,
148             const base::FilePath& file_path,
149             int32 operation_id)
150         : HoverHighlightView(parent),
151           container_(parent),
152           status_img_(NULL),
153           label_container_(NULL),
154           progress_bar_(NULL),
155           cancel_button_(NULL),
156           operation_id_(operation_id) {
157       // Status image.
158       status_img_ = new views::ImageView();
159       AddChildView(status_img_);
160 
161       label_container_ = new views::View();
162       label_container_->SetLayoutManager(new views::BoxLayout(
163           views::BoxLayout::kVertical, 0, 0, kVerticalPadding));
164 #if defined(OS_POSIX)
165       base::string16 file_label = UTF8ToUTF16(file_path.BaseName().value());
166 #elif defined(OS_WIN)
167       base::string16 file_label = WideToUTF16(file_path.BaseName().value());
168 #endif
169       views::Label* label = new views::Label(file_label);
170       label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
171       label_container_->AddChildView(label);
172       // Add progress bar.
173       progress_bar_ = new OperationProgressBar();
174       label_container_->AddChildView(progress_bar_);
175 
176       AddChildView(label_container_);
177 
178       cancel_button_ = new views::ImageButton(this);
179       cancel_button_->SetImage(views::ImageButton::STATE_NORMAL,
180           ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
181               IDR_AURA_UBER_TRAY_DRIVE_CANCEL));
182       cancel_button_->SetImage(views::ImageButton::STATE_HOVERED,
183           ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
184               IDR_AURA_UBER_TRAY_DRIVE_CANCEL_HOVER));
185 
186       UpdateStatus(state, progress);
187       AddChildView(cancel_button_);
188     }
189 
UpdateStatus(ash::DriveOperationStatus::OperationState state,double progress)190     void UpdateStatus(ash::DriveOperationStatus::OperationState state,
191                       double progress) {
192       status_img_->SetImage(container_->GetImageForState(state));
193       progress_bar_->SetValue(progress);
194       cancel_button_->SetVisible(
195           state == ash::DriveOperationStatus::OPERATION_NOT_STARTED ||
196           state == ash::DriveOperationStatus::OPERATION_IN_PROGRESS);
197     }
198 
199    private:
200 
201     // views::View overrides.
GetPreferredSize()202     virtual gfx::Size GetPreferredSize() OVERRIDE {
203       return gfx::Size(
204           status_img_->GetPreferredSize().width() +
205           label_container_->GetPreferredSize().width() +
206           cancel_button_->GetPreferredSize().width() +
207               2 * kSidePadding + 2 * kHorizontalPadding,
208           std::max(status_img_->GetPreferredSize().height(),
209                    std::max(label_container_->GetPreferredSize().height(),
210                             cancel_button_->GetPreferredSize().height())) +
211                    kTopPadding + kBottomPadding);
212     }
213 
Layout()214     virtual void Layout() OVERRIDE {
215       gfx::Rect child_area(GetLocalBounds());
216       if (child_area.IsEmpty())
217         return;
218 
219       int pos_x = child_area.x() + kSidePadding;
220       int pos_y = child_area.y() + kTopPadding;
221 
222       gfx::Rect bounds_status(
223           gfx::Point(pos_x,
224                      pos_y + (child_area.height() - kTopPadding -
225                          kBottomPadding -
226                          status_img_->GetPreferredSize().height())/2),
227           status_img_->GetPreferredSize());
228       status_img_->SetBoundsRect(
229           gfx::IntersectRects(bounds_status, child_area));
230       pos_x += status_img_->bounds().width() + kHorizontalPadding;
231 
232       gfx::Rect bounds_label(pos_x,
233                              pos_y,
234                              child_area.width() - 2 * kSidePadding -
235                                  2 * kHorizontalPadding -
236                                  status_img_->GetPreferredSize().width() -
237                                  cancel_button_->GetPreferredSize().width(),
238                              label_container_->GetPreferredSize().height());
239       label_container_->SetBoundsRect(
240           gfx::IntersectRects(bounds_label, child_area));
241       pos_x += label_container_->bounds().width() + kHorizontalPadding;
242 
243       gfx::Rect bounds_button(
244           gfx::Point(pos_x,
245                      pos_y + (child_area.height() - kTopPadding -
246                          kBottomPadding -
247                          cancel_button_->GetPreferredSize().height())/2),
248           cancel_button_->GetPreferredSize());
249       cancel_button_->SetBoundsRect(
250           gfx::IntersectRects(bounds_button, child_area));
251     }
252 
253     // views::ButtonListener overrides.
ButtonPressed(views::Button * sender,const ui::Event & event)254     virtual void ButtonPressed(views::Button* sender,
255                                const ui::Event& event) OVERRIDE {
256       DCHECK(sender == cancel_button_);
257       Shell::GetInstance()->metrics()->RecordUserMetricsAction(
258           ash::UMA_STATUS_AREA_DRIVE_CANCEL_OPERATION);
259       container_->OnCancelOperation(operation_id_);
260     }
261 
262     DriveDetailedView* container_;
263     views::ImageView* status_img_;
264     views::View* label_container_;
265     views::ProgressBar* progress_bar_;
266     views::ImageButton* cancel_button_;
267     int32 operation_id_;
268 
269     DISALLOW_COPY_AND_ASSIGN(RowView);
270   };
271 
AppendHeaderEntry(const DriveOperationStatusList * list)272   void AppendHeaderEntry(const DriveOperationStatusList* list) {
273     if (footer())
274       return;
275     CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE, this);
276   }
277 
GetImageForState(ash::DriveOperationStatus::OperationState state)278   gfx::ImageSkia* GetImageForState(
279       ash::DriveOperationStatus::OperationState state) {
280     switch (state) {
281       case ash::DriveOperationStatus::OPERATION_NOT_STARTED:
282       case ash::DriveOperationStatus::OPERATION_IN_PROGRESS:
283         return in_progress_img_;
284       case ash::DriveOperationStatus::OPERATION_COMPLETED:
285         return done_img_;
286       case ash::DriveOperationStatus::OPERATION_FAILED:
287         return failed_img_;
288     }
289     return failed_img_;
290   }
291 
OnCancelOperation(int32 operation_id)292   void OnCancelOperation(int32 operation_id) {
293     SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
294     delegate->CancelDriveOperation(operation_id);
295   }
296 
AppendOperationList(const DriveOperationStatusList * list)297   void AppendOperationList(const DriveOperationStatusList* list) {
298     if (!scroller())
299       CreateScrollableList();
300 
301     // Apply the update.
302     std::set<base::FilePath> new_set;
303     bool item_list_changed = false;
304     for (DriveOperationStatusList::const_iterator it = list->begin();
305          it != list->end(); ++it) {
306       const DriveOperationStatus& operation = *it;
307 
308       new_set.insert(operation.file_path);
309       std::map<base::FilePath, RowView*>::iterator existing_item =
310           update_map_.find(operation.file_path);
311 
312       if (existing_item != update_map_.end()) {
313         existing_item->second->UpdateStatus(operation.state,
314                                             operation.progress);
315       } else {
316         RowView* row_view = new RowView(this,
317                                         operation.state,
318                                         operation.progress,
319                                         operation.file_path,
320                                         operation.id);
321 
322         update_map_[operation.file_path] = row_view;
323         scroll_content()->AddChildView(row_view);
324         item_list_changed = true;
325       }
326     }
327 
328     // Remove items from the list that haven't been added or modified with this
329     // update batch.
330     std::set<base::FilePath> remove_set;
331     for (std::map<base::FilePath, RowView*>::iterator update_iter =
332              update_map_.begin();
333          update_iter != update_map_.end(); ++update_iter) {
334       if (new_set.find(update_iter->first) == new_set.end()) {
335         remove_set.insert(update_iter->first);
336       }
337     }
338 
339     for (std::set<base::FilePath>::iterator removed_iter = remove_set.begin();
340         removed_iter != remove_set.end(); ++removed_iter)  {
341       delete update_map_[*removed_iter];
342       update_map_.erase(*removed_iter);
343       item_list_changed = true;
344     }
345 
346     if (item_list_changed)
347       scroller()->Layout();
348 
349     // Close the details if there is really nothing to show there anymore.
350     if (new_set.empty() && GetWidget())
351       GetWidget()->Close();
352   }
353 
AppendSettings()354   void AppendSettings() {
355     if (settings_)
356       return;
357 
358     HoverHighlightView* container = new HoverHighlightView(this);
359     container->AddLabel(ui::ResourceBundle::GetSharedInstance().
360         GetLocalizedString(IDS_ASH_STATUS_TRAY_DRIVE_SETTINGS),
361         gfx::Font::NORMAL);
362     AddChildView(container);
363     settings_ = container;
364   }
365 
366   // Overridden from ViewClickListener.
OnViewClicked(views::View * sender)367   virtual void OnViewClicked(views::View* sender) OVERRIDE {
368     SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
369     if (sender == footer()->content()) {
370       TransitionToDefaultView();
371     } else if (sender == settings_) {
372       delegate->ShowDriveSettings();
373     }
374   }
375 
376   // Maps operation entries to their file paths.
377   std::map<base::FilePath, RowView*> update_map_;
378   views::View* settings_;
379   gfx::ImageSkia* in_progress_img_;
380   gfx::ImageSkia* done_img_;
381   gfx::ImageSkia* failed_img_;
382 
383   DISALLOW_COPY_AND_ASSIGN(DriveDetailedView);
384 };
385 
386 }  // namespace tray
387 
TrayDrive(SystemTray * system_tray)388 TrayDrive::TrayDrive(SystemTray* system_tray) :
389     TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_DRIVE_LIGHT),
390     default_(NULL),
391     detailed_(NULL) {
392   Shell::GetInstance()->system_tray_notifier()->AddDriveObserver(this);
393 }
394 
~TrayDrive()395 TrayDrive::~TrayDrive() {
396   Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
397 }
398 
GetInitialVisibility()399 bool TrayDrive::GetInitialVisibility() {
400   return false;
401 }
402 
CreateDefaultView(user::LoginStatus status)403 views::View* TrayDrive::CreateDefaultView(user::LoginStatus status) {
404   DCHECK(!default_);
405 
406   if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
407     return NULL;
408 
409   // If the list is empty AND the tray icon is invisible (= not in the margin
410   // duration of delayed item hiding), don't show the item.
411   scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
412   if (list->empty() && !tray_view()->visible())
413     return NULL;
414 
415   default_ = new tray::DriveDefaultView(this, list.get());
416   return default_;
417 }
418 
CreateDetailedView(user::LoginStatus status)419 views::View* TrayDrive::CreateDetailedView(user::LoginStatus status) {
420   DCHECK(!detailed_);
421 
422   if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
423     return NULL;
424 
425   // If the list is empty AND the tray icon is invisible (= not in the margin
426   // duration of delayed item hiding), don't show the item.
427   scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
428   if (list->empty() && !tray_view()->visible())
429     return NULL;
430 
431   Shell::GetInstance()->metrics()->RecordUserMetricsAction(
432       ash::UMA_STATUS_AREA_DETAILED_DRIVE_VIEW);
433   detailed_ = new tray::DriveDetailedView(this, list.get());
434   return detailed_;
435 }
436 
DestroyDefaultView()437 void TrayDrive::DestroyDefaultView() {
438   default_ = NULL;
439 }
440 
DestroyDetailedView()441 void TrayDrive::DestroyDetailedView() {
442   detailed_ = NULL;
443 }
444 
UpdateAfterLoginStatusChange(user::LoginStatus status)445 void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status) {
446   if (status == user::LOGGED_IN_USER || status == user::LOGGED_IN_OWNER)
447     return;
448 
449   tray_view()->SetVisible(false);
450   DestroyDefaultView();
451   DestroyDetailedView();
452 }
453 
OnDriveJobUpdated(const DriveOperationStatus & status)454 void TrayDrive::OnDriveJobUpdated(const DriveOperationStatus& status) {
455   // The Drive job list manager changed its notification interface *not* to send
456   // the whole list of operations each time, to clarify which operation is
457   // updated and to reduce redundancy.
458   //
459   // TrayDrive should be able to benefit from the change, but for now, to
460   // incrementally migrate to the new way with minimum diffs, we still get the
461   // list of operations each time the event is fired.
462   // TODO(kinaba) http://crbug.com/128079 clean it up.
463   scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
464   bool is_new_item = true;
465   for (size_t i = 0; i < list->size(); ++i) {
466     if ((*list)[i].id == status.id) {
467       (*list)[i] = status;
468       is_new_item = false;
469       break;
470     }
471   }
472   if (is_new_item)
473     list->push_back(status);
474 
475   // Check if all the operations are in the finished state.
476   bool all_jobs_finished = true;
477   for (size_t i = 0; i < list->size(); ++i) {
478     if ((*list)[i].state != DriveOperationStatus::OPERATION_COMPLETED &&
479         (*list)[i].state != DriveOperationStatus::OPERATION_FAILED) {
480       all_jobs_finished = false;
481       break;
482     }
483   }
484 
485   if (all_jobs_finished) {
486     // If all the jobs ended, the tray item will be hidden after a certain
487     // amount of delay. This is to avoid flashes between sequentially executed
488     // Drive operations (see crbug/165679).
489     hide_timer_.Start(FROM_HERE,
490                       base::TimeDelta::FromMilliseconds(kHideDelayInMs),
491                       this,
492                       &TrayDrive::HideIfNoOperations);
493     return;
494   }
495 
496   // If the list is non-empty, stop the hiding timer (if any).
497   hide_timer_.Stop();
498 
499   tray_view()->SetVisible(true);
500   if (default_)
501     default_->Update(list.get());
502   if (detailed_)
503     detailed_->Update(list.get());
504 }
505 
HideIfNoOperations()506 void TrayDrive::HideIfNoOperations() {
507   DriveOperationStatusList empty_list;
508 
509   tray_view()->SetVisible(false);
510   if (default_)
511     default_->Update(&empty_list);
512   if (detailed_)
513     detailed_->Update(&empty_list);
514 }
515 
516 }  // namespace internal
517 }  // namespace ash
518