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
19namespace file_manager {
20namespace {
21
22struct 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).
34const 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
86int 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
94base::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
105base::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
116std::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.
130class 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
189struct 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
204DesktopNotifications::DesktopNotifications(Profile* profile)
205    : profile_(profile) {
206}
207
208DesktopNotifications::~DesktopNotifications() {
209  STLDeleteContainerPairSecondPointers(notification_map_.begin(),
210                                       notification_map_.end());
211}
212
213void DesktopNotifications::RegisterDevice(const std::string& path) {
214  mount_requests_.insert(MountRequestsMap::value_type(path,
215                                                      MountRequestsInfo()));
216}
217
218void DesktopNotifications::UnregisterDevice(const std::string& path) {
219  mount_requests_.erase(path);
220}
221
222void 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
294void DesktopNotifications::ShowNotification(NotificationType type,
295                                            const std::string& path) {
296  ShowNotificationWithMessage(type, path, GetMessage(type));
297}
298
299void 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
308void 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
321void DesktopNotifications::HideNotification(NotificationType type,
322                                            const std::string& path) {
323  std::string notification_id = GetNotificationId(type, path);
324  HideNotificationById(notification_id);
325}
326
327void 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
336void 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
358void 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
370void 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
380base::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