Home | History | Annotate | Download | only in file_manager
      1 // Copyright 2013 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/chromeos/file_manager/desktop_notifications.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/message_loop/message_loop.h"
      9 #include "base/stl_util.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/browser/chromeos/file_manager/url_util.h"
     12 #include "chrome/browser/notifications/desktop_notification_service.h"
     13 #include "chrome/browser/notifications/notification_delegate.h"
     14 #include "grit/generated_resources.h"
     15 #include "grit/theme_resources.h"
     16 #include "ui/base/l10n/l10n_util.h"
     17 #include "ui/base/resource/resource_bundle.h"
     18 
     19 namespace file_manager {
     20 namespace {
     21 
     22 struct NotificationTypeInfo {
     23   DesktopNotifications::NotificationType type;
     24   const char* notification_id_prefix;
     25   int icon_id;
     26   int title_id;
     27   int message_id;
     28 };
     29 
     30 // Information about notification types.
     31 // The order of notification types in the array must match the order of types in
     32 // NotificationType enum (i.e. the following MUST be satisfied:
     33 // kNotificationTypes[type].type == type).
     34 const NotificationTypeInfo kNotificationTypes[] = {
     35   {
     36     DesktopNotifications::DEVICE,  // type
     37     "Device_",  // notification_id_prefix
     38     IDR_FILES_APP_ICON,  // icon_id
     39     IDS_REMOVABLE_DEVICE_DETECTION_TITLE,  // title_id
     40     IDS_REMOVABLE_DEVICE_SCANNING_MESSAGE  // message_id
     41   },
     42   {
     43     DesktopNotifications::DEVICE_FAIL,  // type
     44     "DeviceFail_",  // notification_id_prefix
     45     IDR_FILES_APP_ICON,  // icon_id
     46     IDS_REMOVABLE_DEVICE_DETECTION_TITLE,  // title_id
     47     IDS_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE  // message_id
     48   },
     49   {
     50     DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED,  // type
     51     "DeviceFail_",  // nottification_id_prefix; same as for DEVICE_FAIL.
     52     IDR_FILES_APP_ICON,  // icon_id
     53     IDS_REMOVABLE_DEVICE_DETECTION_TITLE,  // title_id
     54     IDS_EXTERNAL_STORAGE_DISABLED_MESSAGE  // message_id
     55   },
     56   {
     57     DesktopNotifications::FORMAT_START,  // type
     58     "FormatStart_",  // notification_id_prefix
     59     IDR_FILES_APP_ICON,  // icon_id
     60     IDS_FORMATTING_OF_DEVICE_PENDING_TITLE,  // title_id
     61     IDS_FORMATTING_OF_DEVICE_PENDING_MESSAGE  // message_id
     62   },
     63   {
     64     DesktopNotifications::FORMAT_START_FAIL,  // type
     65     "FormatComplete_",  // notification_id_prefix
     66     IDR_FILES_APP_ICON,  // icon_id
     67     IDS_FORMATTING_OF_DEVICE_FAILED_TITLE,  // title_id
     68     IDS_FORMATTING_STARTED_FAILURE_MESSAGE  // message_id
     69   },
     70   {
     71     DesktopNotifications::FORMAT_SUCCESS,  // type
     72     "FormatComplete_",  // notification_id_prefix
     73     IDR_FILES_APP_ICON,  // icon_id
     74     IDS_FORMATTING_OF_DEVICE_FINISHED_TITLE,  // title_id
     75     IDS_FORMATTING_FINISHED_SUCCESS_MESSAGE  // message_id
     76   },
     77   {
     78     DesktopNotifications::FORMAT_FAIL,  // type
     79     "FormatComplete_",  // notifications_id_prefix
     80     IDR_FILES_APP_ICON,  // icon_id
     81     IDS_FORMATTING_OF_DEVICE_FAILED_TITLE,  // title_id
     82     IDS_FORMATTING_FINISHED_FAILURE_MESSAGE  // message_id
     83   },
     84 };
     85 
     86 int GetIconId(DesktopNotifications::NotificationType type) {
     87   DCHECK_GE(type, 0);
     88   DCHECK_LT(static_cast<size_t>(type), arraysize(kNotificationTypes));
     89   DCHECK(kNotificationTypes[type].type == type);
     90 
     91   return kNotificationTypes[type].icon_id;
     92 }
     93 
     94 base::string16 GetTitle(DesktopNotifications::NotificationType type) {
     95   DCHECK_GE(type, 0);
     96   DCHECK_LT(static_cast<size_t>(type), arraysize(kNotificationTypes));
     97   DCHECK(kNotificationTypes[type].type == type);
     98 
     99   int id = kNotificationTypes[type].title_id;
    100   if (id < 0)
    101     return base::string16();
    102   return l10n_util::GetStringUTF16(id);
    103 }
    104 
    105 base::string16 GetMessage(DesktopNotifications::NotificationType type) {
    106   DCHECK_GE(type, 0);
    107   DCHECK_LT(static_cast<size_t>(type), arraysize(kNotificationTypes));
    108   DCHECK(kNotificationTypes[type].type == type);
    109 
    110   int id = kNotificationTypes[type].message_id;
    111   if (id < 0)
    112     return base::string16();
    113   return l10n_util::GetStringUTF16(id);
    114 }
    115 
    116 std::string GetNotificationId(DesktopNotifications::NotificationType type,
    117                               const std::string& path) {
    118   DCHECK_GE(type, 0);
    119   DCHECK_LT(static_cast<size_t>(type), arraysize(kNotificationTypes));
    120   DCHECK(kNotificationTypes[type].type == type);
    121 
    122   std::string id_prefix(kNotificationTypes[type].notification_id_prefix);
    123   return id_prefix.append(path);
    124 }
    125 
    126 }  // namespace
    127 
    128 // Manages file browser notifications. Generates a desktop notification on
    129 // construction and removes it from the host when closed. Owned by the host.
    130 class DesktopNotifications::NotificationMessage {
    131  public:
    132   class Delegate : public NotificationDelegate {
    133    public:
    134     Delegate(const base::WeakPtr<DesktopNotifications>& host,
    135              const std::string& id)
    136         : host_(host),
    137           id_(id) {}
    138     virtual void Display() OVERRIDE {}
    139     virtual void Error() OVERRIDE {}
    140     virtual void Close(bool by_user) OVERRIDE {
    141       if (host_)
    142         host_->RemoveNotificationById(id_);
    143     }
    144     virtual void Click() OVERRIDE {
    145       // TODO(tbarzic): Show more info page once we have one.
    146     }
    147     virtual std::string id() const OVERRIDE { return id_; }
    148     virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
    149       return NULL;
    150     }
    151 
    152    private:
    153     virtual ~Delegate() {}
    154 
    155     base::WeakPtr<DesktopNotifications> host_;
    156     std::string id_;
    157 
    158     DISALLOW_COPY_AND_ASSIGN(Delegate);
    159   };
    160 
    161   NotificationMessage(DesktopNotifications* host,
    162                       Profile* profile,
    163                       NotificationType type,
    164                       const std::string& notification_id,
    165                       const base::string16& message)
    166       : message_(message) {
    167     const gfx::Image& icon =
    168         ResourceBundle::GetSharedInstance().GetNativeImageNamed(
    169             GetIconId(type));
    170     // TODO(mukai): refactor here to invoke NotificationUIManager directly.
    171     const base::string16 replace_id = UTF8ToUTF16(notification_id);
    172     DesktopNotificationService::AddIconNotification(
    173         util::GetFileManagerBaseUrl(), GetTitle(type),
    174         message, icon, replace_id,
    175         new Delegate(host->AsWeakPtr(), notification_id), profile);
    176   }
    177 
    178   ~NotificationMessage() {}
    179 
    180   // Used in test.
    181   base::string16 message() { return message_; }
    182 
    183  private:
    184   base::string16 message_;
    185 
    186   DISALLOW_COPY_AND_ASSIGN(NotificationMessage);
    187 };
    188 
    189 struct DesktopNotifications::MountRequestsInfo {
    190   bool mount_success_exists;
    191   bool fail_message_finalized;
    192   bool fail_notification_shown;
    193   bool non_parent_device_failed;
    194   bool device_notification_hidden;
    195 
    196   MountRequestsInfo() : mount_success_exists(false),
    197                         fail_message_finalized(false),
    198                         fail_notification_shown(false),
    199                         non_parent_device_failed(false),
    200                         device_notification_hidden(false) {
    201   }
    202 };
    203 
    204 DesktopNotifications::DesktopNotifications(Profile* profile)
    205     : profile_(profile) {
    206 }
    207 
    208 DesktopNotifications::~DesktopNotifications() {
    209   STLDeleteContainerPairSecondPointers(notification_map_.begin(),
    210                                        notification_map_.end());
    211 }
    212 
    213 void DesktopNotifications::RegisterDevice(const std::string& path) {
    214   mount_requests_.insert(MountRequestsMap::value_type(path,
    215                                                       MountRequestsInfo()));
    216 }
    217 
    218 void DesktopNotifications::UnregisterDevice(const std::string& path) {
    219   mount_requests_.erase(path);
    220 }
    221 
    222 void DesktopNotifications::ManageNotificationsOnMountCompleted(
    223     const std::string& system_path, const std::string& label, bool is_parent,
    224     bool success, bool is_unsupported) {
    225   MountRequestsMap::iterator it = mount_requests_.find(system_path);
    226   if (it == mount_requests_.end())
    227     return;
    228 
    229   // We have to hide device scanning notification if we haven't done it already.
    230   if (!it->second.device_notification_hidden) {
    231     HideNotification(DEVICE, system_path);
    232     it->second.device_notification_hidden = true;
    233   }
    234 
    235   // Check if there is fail notification for parent device. If so, disregard it.
    236   // (parent device contains partition table, which is unmountable).
    237   if (!is_parent && it->second.fail_notification_shown &&
    238       !it->second.non_parent_device_failed) {
    239     HideNotification(DEVICE_FAIL, system_path);
    240     it->second.fail_notification_shown = false;
    241   }
    242 
    243   // If notification can't change any more, no need to continue.
    244   if (it->second.fail_message_finalized)
    245     return;
    246 
    247   // Do we have a multi-partition device for which at least one mount failed.
    248   bool fail_on_multipartition_device =
    249       success ? it->second.non_parent_device_failed
    250       : it->second.mount_success_exists ||
    251       it->second.non_parent_device_failed;
    252 
    253   base::string16 message;
    254   if (fail_on_multipartition_device) {
    255     it->second.fail_message_finalized = true;
    256     message = label.empty() ?
    257         l10n_util::GetStringUTF16(
    258             IDS_MULTIPART_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE) :
    259         l10n_util::GetStringFUTF16(
    260             IDS_MULTIPART_DEVICE_UNSUPPORTED_MESSAGE, UTF8ToUTF16(label));
    261   } else if (!success) {
    262     // First device failed.
    263     if (!is_unsupported) {
    264       message = label.empty() ?
    265           l10n_util::GetStringUTF16(IDS_DEVICE_UNKNOWN_DEFAULT_MESSAGE) :
    266           l10n_util::GetStringFUTF16(IDS_DEVICE_UNKNOWN_MESSAGE,
    267                                      UTF8ToUTF16(label));
    268     } else {
    269       message = label.empty() ?
    270           l10n_util::GetStringUTF16(IDS_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE) :
    271           l10n_util::GetStringFUTF16(IDS_DEVICE_UNSUPPORTED_MESSAGE,
    272                                      UTF8ToUTF16(label));
    273     }
    274   }
    275 
    276   if (success) {
    277     it->second.mount_success_exists = true;
    278   } else {
    279     it->second.non_parent_device_failed |= !is_parent;
    280   }
    281 
    282   if (message.empty())
    283     return;
    284 
    285   if (it->second.fail_notification_shown) {
    286     HideNotification(DEVICE_FAIL, system_path);
    287   } else {
    288     it->second.fail_notification_shown = true;
    289   }
    290 
    291   ShowNotificationWithMessage(DEVICE_FAIL, system_path, message);
    292 }
    293 
    294 void DesktopNotifications::ShowNotification(NotificationType type,
    295                                             const std::string& path) {
    296   ShowNotificationWithMessage(type, path, GetMessage(type));
    297 }
    298 
    299 void DesktopNotifications::ShowNotificationWithMessage(
    300     NotificationType type,
    301     const std::string& path,
    302     const base::string16& message) {
    303   std::string notification_id = GetNotificationId(type, path);
    304   hidden_notifications_.erase(notification_id);
    305   ShowNotificationById(type, notification_id, message);
    306 }
    307 
    308 void DesktopNotifications::ShowNotificationDelayed(
    309     NotificationType type,
    310     const std::string& path,
    311     base::TimeDelta delay) {
    312   std::string notification_id = GetNotificationId(type, path);
    313   hidden_notifications_.erase(notification_id);
    314   base::MessageLoop::current()->PostDelayedTask(
    315       FROM_HERE,
    316       base::Bind(&DesktopNotifications::ShowNotificationById, AsWeakPtr(),
    317                  type, notification_id, GetMessage(type)),
    318       delay);
    319 }
    320 
    321 void DesktopNotifications::HideNotification(NotificationType type,
    322                                             const std::string& path) {
    323   std::string notification_id = GetNotificationId(type, path);
    324   HideNotificationById(notification_id);
    325 }
    326 
    327 void DesktopNotifications::HideNotificationDelayed(
    328     NotificationType type, const std::string& path, base::TimeDelta delay) {
    329   base::MessageLoop::current()->PostDelayedTask(
    330       FROM_HERE,
    331       base::Bind(&DesktopNotifications::HideNotification, AsWeakPtr(),
    332                  type, path),
    333       delay);
    334 }
    335 
    336 void DesktopNotifications::ShowNotificationById(
    337     NotificationType type,
    338     const std::string& notification_id,
    339     const base::string16& message) {
    340   if (hidden_notifications_.find(notification_id) !=
    341       hidden_notifications_.end()) {
    342     // Notification was hidden after a delayed show was requested.
    343     hidden_notifications_.erase(notification_id);
    344     return;
    345   }
    346   if (notification_map_.find(notification_id) != notification_map_.end()) {
    347     // Remove any existing notification with |notification_id|.
    348     // Will trigger Delegate::Close which will call RemoveNotificationById.
    349     DesktopNotificationService::RemoveNotification(notification_id);
    350     DCHECK(notification_map_.find(notification_id) == notification_map_.end());
    351   }
    352   // Create a new notification with |notification_id|.
    353   NotificationMessage* new_message =
    354       new NotificationMessage(this, profile_, type, notification_id, message);
    355   notification_map_[notification_id] = new_message;
    356 }
    357 
    358 void DesktopNotifications::HideNotificationById(
    359     const std::string& notification_id) {
    360   NotificationMap::iterator it = notification_map_.find(notification_id);
    361   if (it != notification_map_.end()) {
    362     // Will trigger Delegate::Close which will call RemoveNotificationById.
    363     DesktopNotificationService::RemoveNotification(notification_id);
    364   } else {
    365     // Mark as hidden so it does not get shown from a delayed task.
    366     hidden_notifications_.insert(notification_id);
    367   }
    368 }
    369 
    370 void DesktopNotifications::RemoveNotificationById(
    371     const std::string& notification_id) {
    372   NotificationMap::iterator it = notification_map_.find(notification_id);
    373   if (it != notification_map_.end()) {
    374     NotificationMessage* notification = it->second;
    375     notification_map_.erase(it);
    376     delete notification;
    377   }
    378 }
    379 
    380 base::string16 DesktopNotifications::GetNotificationMessageForTest(
    381     const std::string& id) const {
    382   NotificationMap::const_iterator it = notification_map_.find(id);
    383   if (it == notification_map_.end())
    384     return base::string16();
    385   return it->second->message();
    386 }
    387 
    388 }  // namespace file_manager
    389