Home | History | Annotate | Download | only in drive
      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 
     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 
     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:
     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 
     82   virtual ~DriveDefaultView() {}
     83 
     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:
     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 
    115   virtual ~DriveDetailedView() {
    116     STLDeleteValues(&update_map_);
    117   }
    118 
    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:
    131     OperationProgressBar() {}
    132    private:
    133 
    134     // Overridden from View:
    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:
    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 
    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.
    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 
    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.
    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 
    272   void AppendHeaderEntry(const DriveOperationStatusList* list) {
    273     if (footer())
    274       return;
    275     CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE, this);
    276   }
    277 
    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 
    292   void OnCancelOperation(int32 operation_id) {
    293     SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
    294     delegate->CancelDriveOperation(operation_id);
    295   }
    296 
    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 
    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.
    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 
    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 
    395 TrayDrive::~TrayDrive() {
    396   Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
    397 }
    398 
    399 bool TrayDrive::GetInitialVisibility() {
    400   return false;
    401 }
    402 
    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 
    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 
    437 void TrayDrive::DestroyDefaultView() {
    438   default_ = NULL;
    439 }
    440 
    441 void TrayDrive::DestroyDetailedView() {
    442   detailed_ = NULL;
    443 }
    444 
    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 
    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 
    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