Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / background / js / device_handler.js
1 // Copyright 2014 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 /**
6  * Handler of device event.
7  * @constructor
8  * @extends {cr.EventTarget}
9  */
10 function DeviceHandler() {
11   cr.EventTarget.call(this);
12
13   /**
14    * Map of device path and mount status of devices.
15    * @type {Object.<string, DeviceHandler.MountStatus>}
16    * @private
17    */
18   this.mountStatus_ = {};
19
20   chrome.fileManagerPrivate.onDeviceChanged.addListener(
21       this.onDeviceChanged_.bind(this));
22   chrome.fileManagerPrivate.onMountCompleted.addListener(
23       this.onMountCompleted_.bind(this));
24   chrome.notifications.onButtonClicked.addListener(
25       this.onNotificationButtonClicked_.bind(this));
26 }
27
28 DeviceHandler.prototype = {
29   __proto__: cr.EventTarget.prototype
30 };
31
32 /**
33  * An event name trigerred when a user requests to navigate to a volume.
34  * The event object must have a volumeId property.
35  * @type {string}
36  * @const
37  */
38 DeviceHandler.VOLUME_NAVIGATION_REQUESTED = 'volumenavigationrequested';
39
40 /**
41  * Notification type.
42  * @param {string} prefix Prefix of notification ID.
43  * @param {string} title String ID of title.
44  * @param {string} message String ID of message.
45  * @param {string=} opt_buttonLabel String ID of the button label.
46  * @constructor
47  */
48 DeviceHandler.Notification = function(prefix, title, message, opt_buttonLabel) {
49   /**
50    * Prefix of notification ID.
51    * @type {string}
52    */
53   this.prefix = prefix;
54
55   /**
56    * String ID of title.
57    * @type {string}
58    */
59   this.title = title;
60
61   /**
62    * String ID of message.
63    * @type {string}
64    */
65   this.message = message;
66
67   /**
68    * String ID of button label.
69    * @type {?string}
70    */
71   this.buttonLabel = opt_buttonLabel || null;
72
73   /**
74    * Queue of API call.
75    * @type {AsyncUtil.Queue}
76    * @private
77    */
78   this.queue_ = new AsyncUtil.Queue();
79
80   Object.seal(this);
81 };
82
83 /**
84  * @type {DeviceHandler.Notification}
85  * @const
86  */
87 DeviceHandler.Notification.DEVICE_NAVIGATION = new DeviceHandler.Notification(
88     'deviceNavigation',
89     'REMOVABLE_DEVICE_DETECTION_TITLE',
90     'REMOVABLE_DEVICE_NAVIGATION_MESSAGE',
91     'REMOVABLE_DEVICE_NAVIGATION_BUTTON_LABEL');
92
93 /**
94  * @type {DeviceHandler.Notification}
95  * @const
96  */
97 DeviceHandler.Notification.DEVICE_FAIL = new DeviceHandler.Notification(
98     'deviceFail',
99     'REMOVABLE_DEVICE_DETECTION_TITLE',
100     'DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
101
102 /**
103  * @type {DeviceHandler.Notification}
104  * @const
105  */
106 DeviceHandler.Notification.DEVICE_FAIL_UNKNOWN = new DeviceHandler.Notification(
107     'deviceFail',
108     'REMOVABLE_DEVICE_DETECTION_TITLE',
109     'DEVICE_UNKNOWN_DEFAULT_MESSAGE',
110     'DEVICE_UNKNOWN_BUTTON_LABEL');
111
112 /**
113  * @type {DeviceHandler.Notification}
114  * @const
115  */
116 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED =
117     new DeviceHandler.Notification(
118         'deviceFail',
119         'REMOVABLE_DEVICE_DETECTION_TITLE',
120         'EXTERNAL_STORAGE_DISABLED_MESSAGE');
121
122 /**
123  * @type {DeviceHandler.Notification}
124  * @const
125  */
126 DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED =
127     new DeviceHandler.Notification(
128         'hardUnplugged',
129         'DEVICE_HARD_UNPLUGGED_TITLE',
130         'DEVICE_HARD_UNPLUGGED_MESSAGE');
131
132 /**
133  * @type {DeviceHandler.Notification}
134  * @const
135  */
136 DeviceHandler.Notification.FORMAT_START = new DeviceHandler.Notification(
137     'formatStart',
138     'FORMATTING_OF_DEVICE_PENDING_TITLE',
139     'FORMATTING_OF_DEVICE_PENDING_MESSAGE');
140
141 /**
142  * @type {DeviceHandler.Notification}
143  * @const
144  */
145 DeviceHandler.Notification.FORMAT_SUCCESS = new DeviceHandler.Notification(
146     'formatSuccess',
147     'FORMATTING_OF_DEVICE_FINISHED_TITLE',
148     'FORMATTING_FINISHED_SUCCESS_MESSAGE');
149
150 /**
151  * @type {DeviceHandler.Notification}
152  * @const
153  */
154 DeviceHandler.Notification.FORMAT_FAIL = new DeviceHandler.Notification(
155     'formatFail',
156     'FORMATTING_OF_DEVICE_FAILED_TITLE',
157     'FORMATTING_FINISHED_FAILURE_MESSAGE');
158
159 /**
160  * Shows the notification for the device path.
161  * @param {string} devicePath Device path.
162  * @param {string=} opt_message Message overrides the default message.
163  * @return {string} Notification ID.
164  */
165 DeviceHandler.Notification.prototype.show = function(devicePath, opt_message) {
166   var notificationId = this.makeId_(devicePath);
167   this.queue_.run(function(callback) {
168     this.showInternal_(notificationId, opt_message || null, callback);
169   }.bind(this));
170   return notificationId;
171 };
172
173 /**
174  * Shows the notification for the device path.
175  * If the existing notification has been already shown, it does not anything.
176  * @param {string} devicePath Device path.
177  */
178 DeviceHandler.Notification.prototype.showOnce = function(devicePath) {
179   var notificationId = this.makeId_(devicePath);
180   this.queue_.run(function(callback) {
181     chrome.notifications.getAll(function(idList) {
182       if (idList.indexOf(notificationId) !== -1) {
183         callback();
184         return;
185       }
186       this.showInternal_(notificationId, null, callback);
187     }.bind(this));
188   });
189 };
190
191 /**
192  * Shows the notificaiton without using AsyncQueue.
193  * @param {string} notificationId Notification ID.
194  * @param {?string} message Message overriding the normal message.
195  * @param {function()} callback Callback to be invoked when the notification is
196  *     created.
197  * @private
198  */
199 DeviceHandler.Notification.prototype.showInternal_ = function(
200     notificationId, message, callback) {
201   var buttons =
202       this.buttonLabel ? [{title: str(this.buttonLabel)}] : undefined;
203   chrome.notifications.create(
204       notificationId,
205       {
206         type: 'basic',
207         title: str(this.title),
208         message: message || str(this.message),
209         iconUrl: chrome.runtime.getURL('/common/images/icon96.png'),
210         buttons: buttons
211       },
212       callback);
213 };
214
215 /**
216  * Hides the notification for the device path.
217  * @param {string} devicePath Device path.
218  */
219 DeviceHandler.Notification.prototype.hide = function(devicePath) {
220   this.queue_.run(function(callback) {
221     chrome.notifications.clear(this.makeId_(devicePath), callback);
222   }.bind(this));
223 };
224
225 /**
226  * Makes a notification ID for the device path.
227  * @param {string} devicePath Device path.
228  * @return {string} Notification ID.
229  * @private
230  */
231 DeviceHandler.Notification.prototype.makeId_ = function(devicePath) {
232   return this.prefix + ':' + devicePath;
233 };
234
235 /**
236  * Handles notifications from C++ sides.
237  * @param {DeviceEvent} event Device event.
238  * @private
239  */
240 DeviceHandler.prototype.onDeviceChanged_ = function(event) {
241   switch (event.type) {
242     case 'disabled':
243       DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.show(
244           event.devicePath);
245       break;
246     case 'removed':
247       DeviceHandler.Notification.DEVICE_FAIL.hide(event.devicePath);
248       DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.hide(
249           event.devicePath);
250       delete this.mountStatus_[event.devicePath];
251       break;
252     case 'hard_unplugged':
253       DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED.show(
254           event.devicePath);
255       break;
256     case 'format_start':
257       DeviceHandler.Notification.FORMAT_START.show(event.devicePath);
258       break;
259     case 'format_success':
260       DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
261       DeviceHandler.Notification.FORMAT_SUCCESS.show(event.devicePath);
262       break;
263     case 'format_fail':
264       DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
265       DeviceHandler.Notification.FORMAT_FAIL.show(event.devicePath);
266       break;
267     default:
268       console.error('Unknown event tyep: ' + event.type);
269       break;
270   }
271 };
272
273 /**
274  * Mount status for the device.
275  * Each multi-partition devices can obtain multiple mount completed events.
276  * This status shows what results are already obtained for the device.
277  * @enum {string}
278  * @const
279  */
280 DeviceHandler.MountStatus = {
281   // There is no mount results on the device.
282   NO_RESULT: 'noResult',
283   // There is no error on the device.
284   SUCCESS: 'success',
285   // There is only parent errors, that can be overridden by child results.
286   ONLY_PARENT_ERROR: 'onlyParentError',
287   // There is one child error.
288   CHILD_ERROR: 'childError',
289   // There is multiple child results and at least one is failure.
290   MULTIPART_ERROR: 'multipartError'
291 };
292 Object.freeze(DeviceHandler.MountStatus);
293
294 /**
295  * Handles mount completed events to show notifications for removable devices.
296  * @param {MountCompletedEvent} event Mount completed event.
297  * @private
298  */
299 DeviceHandler.prototype.onMountCompleted_ = function(event) {
300   // If this is remounting, which happens when resuming ChromeOS, the device has
301   // already inserted to the computer. So we suppress the notification.
302   var volume = event.volumeMetadata;
303   if (!volume.deviceType || !volume.devicePath || !event.shouldNotify)
304     return;
305
306   // If the current volume status is succeed and it should be handled in
307   // Files.app, show the notification to navigate the volume.
308   if (event.eventType === 'mount' && event.status === 'success') {
309     DeviceHandler.Notification.DEVICE_NAVIGATION.show(volume.devicePath);
310   } else if (event.eventType === 'unmount') {
311     DeviceHandler.Notification.DEVICE_NAVIGATION.hide(volume.devicePath);
312   }
313
314   var getFirstStatus = function(event) {
315     if (event.status === 'success')
316       return DeviceHandler.MountStatus.SUCCESS;
317     else if (event.volumeMetadata.isParentDevice)
318       return DeviceHandler.MountStatus.ONLY_PARENT_ERROR;
319     else
320       return DeviceHandler.MountStatus.CHILD_ERROR;
321   };
322
323   // Update the current status.
324   if (!this.mountStatus_[volume.devicePath])
325     this.mountStatus_[volume.devicePath] = DeviceHandler.MountStatus.NO_RESULT;
326   switch (this.mountStatus_[volume.devicePath]) {
327     // If the multipart error message has already shown, do nothing because the
328     // message does not changed by the following mount results.
329     case DeviceHandler.MountStatus.MULTIPART_ERROR:
330       return;
331     // If this is the first result, hide the scanning notification.
332     case DeviceHandler.MountStatus.NO_RESULT:
333       this.mountStatus_[volume.devicePath] = getFirstStatus(event);
334       break;
335     // If there are only parent errors, and the new result is child's one, hide
336     // the parent error. (parent device contains partition table, which is
337     // unmountable)
338     case DeviceHandler.MountStatus.ONLY_PARENT_ERROR:
339       if (!volume.isParentDevice)
340         DeviceHandler.Notification.DEVICE_FAIL.hide(volume.devicePath);
341       this.mountStatus_[volume.devicePath] = getFirstStatus(event);
342       break;
343     // We have a multi-partition device for which at least one mount
344     // failed.
345     case DeviceHandler.MountStatus.SUCCESS:
346     case DeviceHandler.MountStatus.CHILD_ERROR:
347       if (this.mountStatus_[volume.devicePath] ===
348               DeviceHandler.MountStatus.SUCCESS &&
349           event.status === 'success') {
350         this.mountStatus_[volume.devicePath] =
351             DeviceHandler.MountStatus.SUCCESS;
352       } else {
353         this.mountStatus_[volume.devicePath] =
354             DeviceHandler.MountStatus.MULTIPART_ERROR;
355       }
356       break;
357   }
358
359   if (event.eventType === 'unmount')
360     return;
361
362   // Show the notification for the current errors.
363   // If there is no error, do not show/update the notification.
364   var message;
365   switch (this.mountStatus_[volume.devicePath]) {
366     case DeviceHandler.MountStatus.MULTIPART_ERROR:
367       message = volume.deviceLabel ?
368           strf('MULTIPART_DEVICE_UNSUPPORTED_MESSAGE', volume.deviceLabel) :
369           str('MULTIPART_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
370       DeviceHandler.Notification.DEVICE_FAIL.show(
371           volume.devicePath,
372           message);
373       break;
374     case DeviceHandler.MountStatus.CHILD_ERROR:
375     case DeviceHandler.MountStatus.ONLY_PARENT_ERROR:
376       if (event.status === 'error_unsupported_filesystem') {
377         message = volume.deviceLabel ?
378             strf('DEVICE_UNSUPPORTED_MESSAGE', volume.deviceLabel) :
379             str('DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
380         DeviceHandler.Notification.DEVICE_FAIL.show(
381             volume.devicePath,
382             message);
383       } else {
384         message = volume.deviceLabel ?
385             strf('DEVICE_UNKNOWN_MESSAGE', volume.deviceLabel) :
386             str('DEVICE_UNKNOWN_DEFAULT_MESSAGE');
387         DeviceHandler.Notification.DEVICE_FAIL_UNKNOWN.show(
388             volume.devicePath,
389             message);
390       }
391   }
392 };
393
394 /**
395  * Handles notification button click.
396  * @param {string} id ID of the notification.
397  * @private
398  */
399 DeviceHandler.prototype.onNotificationButtonClicked_ = function(id) {
400   var pos = id.indexOf(':');
401   var type = id.substr(0, pos);
402   var devicePath = id.substr(pos + 1);
403   if (type === 'deviceNavigation' || type === 'deviceFail') {
404     chrome.notifications.clear(id, function() {});
405     var event = new Event(DeviceHandler.VOLUME_NAVIGATION_REQUESTED);
406     event.devicePath = devicePath;
407     this.dispatchEvent(event);
408   }
409 };