// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/file_manager/desktop_notifications.h" #include "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/chromeos/file_manager/url_util.h" #include "chrome/browser/notifications/desktop_notification_service.h" #include "chrome/browser/notifications/notification_delegate.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" namespace file_manager { namespace { struct NotificationTypeInfo { DesktopNotifications::NotificationType type; const char* notification_id_prefix; int icon_id; int title_id; int message_id; }; // Information about notification types. // The order of notification types in the array must match the order of types in // NotificationType enum (i.e. the following MUST be satisfied: // kNotificationTypes[type].type == type). const NotificationTypeInfo kNotificationTypes[] = { { DesktopNotifications::DEVICE, // type "Device_", // notification_id_prefix IDR_FILES_APP_ICON, // icon_id IDS_REMOVABLE_DEVICE_DETECTION_TITLE, // title_id IDS_REMOVABLE_DEVICE_SCANNING_MESSAGE // message_id }, { DesktopNotifications::DEVICE_FAIL, // type "DeviceFail_", // notification_id_prefix IDR_FILES_APP_ICON, // icon_id IDS_REMOVABLE_DEVICE_DETECTION_TITLE, // title_id IDS_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE // message_id }, { DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED, // type "DeviceFail_", // nottification_id_prefix; same as for DEVICE_FAIL. IDR_FILES_APP_ICON, // icon_id IDS_REMOVABLE_DEVICE_DETECTION_TITLE, // title_id IDS_EXTERNAL_STORAGE_DISABLED_MESSAGE // message_id }, { DesktopNotifications::FORMAT_START, // type "FormatStart_", // notification_id_prefix IDR_FILES_APP_ICON, // icon_id IDS_FORMATTING_OF_DEVICE_PENDING_TITLE, // title_id IDS_FORMATTING_OF_DEVICE_PENDING_MESSAGE // message_id }, { DesktopNotifications::FORMAT_START_FAIL, // type "FormatComplete_", // notification_id_prefix IDR_FILES_APP_ICON, // icon_id IDS_FORMATTING_OF_DEVICE_FAILED_TITLE, // title_id IDS_FORMATTING_STARTED_FAILURE_MESSAGE // message_id }, { DesktopNotifications::FORMAT_SUCCESS, // type "FormatComplete_", // notification_id_prefix IDR_FILES_APP_ICON, // icon_id IDS_FORMATTING_OF_DEVICE_FINISHED_TITLE, // title_id IDS_FORMATTING_FINISHED_SUCCESS_MESSAGE // message_id }, { DesktopNotifications::FORMAT_FAIL, // type "FormatComplete_", // notifications_id_prefix IDR_FILES_APP_ICON, // icon_id IDS_FORMATTING_OF_DEVICE_FAILED_TITLE, // title_id IDS_FORMATTING_FINISHED_FAILURE_MESSAGE // message_id }, }; int GetIconId(DesktopNotifications::NotificationType type) { DCHECK_GE(type, 0); DCHECK_LT(static_cast(type), arraysize(kNotificationTypes)); DCHECK(kNotificationTypes[type].type == type); return kNotificationTypes[type].icon_id; } base::string16 GetTitle(DesktopNotifications::NotificationType type) { DCHECK_GE(type, 0); DCHECK_LT(static_cast(type), arraysize(kNotificationTypes)); DCHECK(kNotificationTypes[type].type == type); int id = kNotificationTypes[type].title_id; if (id < 0) return base::string16(); return l10n_util::GetStringUTF16(id); } base::string16 GetMessage(DesktopNotifications::NotificationType type) { DCHECK_GE(type, 0); DCHECK_LT(static_cast(type), arraysize(kNotificationTypes)); DCHECK(kNotificationTypes[type].type == type); int id = kNotificationTypes[type].message_id; if (id < 0) return base::string16(); return l10n_util::GetStringUTF16(id); } std::string GetNotificationId(DesktopNotifications::NotificationType type, const std::string& path) { DCHECK_GE(type, 0); DCHECK_LT(static_cast(type), arraysize(kNotificationTypes)); DCHECK(kNotificationTypes[type].type == type); std::string id_prefix(kNotificationTypes[type].notification_id_prefix); return id_prefix.append(path); } } // namespace // Manages file browser notifications. Generates a desktop notification on // construction and removes it from the host when closed. Owned by the host. class DesktopNotifications::NotificationMessage { public: class Delegate : public NotificationDelegate { public: Delegate(const base::WeakPtr& host, const std::string& id) : host_(host), id_(id) {} virtual void Display() OVERRIDE {} virtual void Error() OVERRIDE {} virtual void Close(bool by_user) OVERRIDE { if (host_) host_->RemoveNotificationById(id_); } virtual void Click() OVERRIDE { // TODO(tbarzic): Show more info page once we have one. } virtual std::string id() const OVERRIDE { return id_; } virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE { return NULL; } private: virtual ~Delegate() {} base::WeakPtr host_; std::string id_; DISALLOW_COPY_AND_ASSIGN(Delegate); }; NotificationMessage(DesktopNotifications* host, Profile* profile, NotificationType type, const std::string& notification_id, const base::string16& message) : message_(message) { const gfx::Image& icon = ResourceBundle::GetSharedInstance().GetNativeImageNamed( GetIconId(type)); // TODO(mukai): refactor here to invoke NotificationUIManager directly. const base::string16 replace_id = UTF8ToUTF16(notification_id); DesktopNotificationService::AddIconNotification( util::GetFileManagerBaseUrl(), GetTitle(type), message, icon, replace_id, new Delegate(host->AsWeakPtr(), notification_id), profile); } ~NotificationMessage() {} // Used in test. base::string16 message() { return message_; } private: base::string16 message_; DISALLOW_COPY_AND_ASSIGN(NotificationMessage); }; struct DesktopNotifications::MountRequestsInfo { bool mount_success_exists; bool fail_message_finalized; bool fail_notification_shown; bool non_parent_device_failed; bool device_notification_hidden; MountRequestsInfo() : mount_success_exists(false), fail_message_finalized(false), fail_notification_shown(false), non_parent_device_failed(false), device_notification_hidden(false) { } }; DesktopNotifications::DesktopNotifications(Profile* profile) : profile_(profile) { } DesktopNotifications::~DesktopNotifications() { STLDeleteContainerPairSecondPointers(notification_map_.begin(), notification_map_.end()); } void DesktopNotifications::RegisterDevice(const std::string& path) { mount_requests_.insert(MountRequestsMap::value_type(path, MountRequestsInfo())); } void DesktopNotifications::UnregisterDevice(const std::string& path) { mount_requests_.erase(path); } void DesktopNotifications::ManageNotificationsOnMountCompleted( const std::string& system_path, const std::string& label, bool is_parent, bool success, bool is_unsupported) { MountRequestsMap::iterator it = mount_requests_.find(system_path); if (it == mount_requests_.end()) return; // We have to hide device scanning notification if we haven't done it already. if (!it->second.device_notification_hidden) { HideNotification(DEVICE, system_path); it->second.device_notification_hidden = true; } // Check if there is fail notification for parent device. If so, disregard it. // (parent device contains partition table, which is unmountable). if (!is_parent && it->second.fail_notification_shown && !it->second.non_parent_device_failed) { HideNotification(DEVICE_FAIL, system_path); it->second.fail_notification_shown = false; } // If notification can't change any more, no need to continue. if (it->second.fail_message_finalized) return; // Do we have a multi-partition device for which at least one mount failed. bool fail_on_multipartition_device = success ? it->second.non_parent_device_failed : it->second.mount_success_exists || it->second.non_parent_device_failed; base::string16 message; if (fail_on_multipartition_device) { it->second.fail_message_finalized = true; message = label.empty() ? l10n_util::GetStringUTF16( IDS_MULTIPART_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE) : l10n_util::GetStringFUTF16( IDS_MULTIPART_DEVICE_UNSUPPORTED_MESSAGE, UTF8ToUTF16(label)); } else if (!success) { // First device failed. if (!is_unsupported) { message = label.empty() ? l10n_util::GetStringUTF16(IDS_DEVICE_UNKNOWN_DEFAULT_MESSAGE) : l10n_util::GetStringFUTF16(IDS_DEVICE_UNKNOWN_MESSAGE, UTF8ToUTF16(label)); } else { message = label.empty() ? l10n_util::GetStringUTF16(IDS_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE) : l10n_util::GetStringFUTF16(IDS_DEVICE_UNSUPPORTED_MESSAGE, UTF8ToUTF16(label)); } } if (success) { it->second.mount_success_exists = true; } else { it->second.non_parent_device_failed |= !is_parent; } if (message.empty()) return; if (it->second.fail_notification_shown) { HideNotification(DEVICE_FAIL, system_path); } else { it->second.fail_notification_shown = true; } ShowNotificationWithMessage(DEVICE_FAIL, system_path, message); } void DesktopNotifications::ShowNotification(NotificationType type, const std::string& path) { ShowNotificationWithMessage(type, path, GetMessage(type)); } void DesktopNotifications::ShowNotificationWithMessage( NotificationType type, const std::string& path, const string16& message) { std::string notification_id = GetNotificationId(type, path); hidden_notifications_.erase(notification_id); ShowNotificationById(type, notification_id, message); } void DesktopNotifications::ShowNotificationDelayed( NotificationType type, const std::string& path, base::TimeDelta delay) { std::string notification_id = GetNotificationId(type, path); hidden_notifications_.erase(notification_id); base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&DesktopNotifications::ShowNotificationById, AsWeakPtr(), type, notification_id, GetMessage(type)), delay); } void DesktopNotifications::HideNotification(NotificationType type, const std::string& path) { std::string notification_id = GetNotificationId(type, path); HideNotificationById(notification_id); } void DesktopNotifications::HideNotificationDelayed( NotificationType type, const std::string& path, base::TimeDelta delay) { base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&DesktopNotifications::HideNotification, AsWeakPtr(), type, path), delay); } void DesktopNotifications::ShowNotificationById( NotificationType type, const std::string& notification_id, const string16& message) { if (hidden_notifications_.find(notification_id) != hidden_notifications_.end()) { // Notification was hidden after a delayed show was requested. hidden_notifications_.erase(notification_id); return; } if (notification_map_.find(notification_id) != notification_map_.end()) { // Remove any existing notification with |notification_id|. // Will trigger Delegate::Close which will call RemoveNotificationById. DesktopNotificationService::RemoveNotification(notification_id); DCHECK(notification_map_.find(notification_id) == notification_map_.end()); } // Create a new notification with |notification_id|. NotificationMessage* new_message = new NotificationMessage(this, profile_, type, notification_id, message); notification_map_[notification_id] = new_message; } void DesktopNotifications::HideNotificationById( const std::string& notification_id) { NotificationMap::iterator it = notification_map_.find(notification_id); if (it != notification_map_.end()) { // Will trigger Delegate::Close which will call RemoveNotificationById. DesktopNotificationService::RemoveNotification(notification_id); } else { // Mark as hidden so it does not get shown from a delayed task. hidden_notifications_.insert(notification_id); } } void DesktopNotifications::RemoveNotificationById( const std::string& notification_id) { NotificationMap::iterator it = notification_map_.find(notification_id); if (it != notification_map_.end()) { NotificationMessage* notification = it->second; notification_map_.erase(it); delete notification; } } string16 DesktopNotifications::GetNotificationMessageForTest( const std::string& id) const { NotificationMap::const_iterator it = notification_map_.find(id); if (it == notification_map_.end()) return string16(); return it->second->message(); } } // namespace file_manager