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