Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / 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 'use strict';
6
7 /**
8  * Handler of device event.
9  * @constructor
10  */
11 function DeviceHandler() {
12   /**
13    * Map of device path and mount status of devices.
14    * @type {Object.<string, DeviceHandler.MountStatus>}
15    * @private
16    */
17   this.mountStatus_ = {};
18
19   chrome.fileBrowserPrivate.onDeviceChanged.addListener(
20       this.onDeviceChanged_.bind(this));
21   chrome.fileBrowserPrivate.onMountCompleted.addListener(
22       this.onMountCompleted_.bind(this));
23
24   Object.seal(this);
25 }
26
27 /**
28  * Notification type.
29  * @param {string} prefix Prefix of notification ID.
30  * @param {string} title String ID of title.
31  * @param {string} message String ID of message.
32  * @constructor
33  */
34 DeviceHandler.Notification = function(prefix, title, message) {
35   /**
36    * Prefix of notification ID.
37    * @type {string}
38    */
39   this.prefix = prefix;
40
41   /**
42    * String ID of title.
43    * @type {string}
44    */
45   this.title = title;
46
47   /**
48    * String ID of message.
49    * @type {string}
50    */
51   this.message = message;
52
53   /**
54    * Queue of API call.
55    * @type {AsyncUtil.Queue}
56    * @private
57    */
58   this.queue_ = new AsyncUtil.Queue();
59
60   /**
61    * Timeout ID.
62    * @type {number}
63    * @private
64    */
65   this.pendingShowTimerId_ = 0;
66
67   Object.seal(this);
68 };
69
70 /**
71  * @type {DeviceHandler.Notification}
72  * @const
73  */
74 DeviceHandler.Notification.DEVICE = new DeviceHandler.Notification(
75     'device',
76     'REMOVABLE_DEVICE_DETECTION_TITLE',
77     'REMOVABLE_DEVICE_SCANNING_MESSAGE');
78
79 /**
80  * @type {DeviceHandler.Notification}
81  * @const
82  */
83 DeviceHandler.Notification.DEVICE_FAIL = new DeviceHandler.Notification(
84     'deviceFail',
85     'REMOVABLE_DEVICE_DETECTION_TITLE',
86     'DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
87
88 /**
89  * @type {DeviceHandler.Notification}
90  * @const
91  */
92 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED =
93     new DeviceHandler.Notification(
94         'deviceFail',
95         'REMOVABLE_DEVICE_DETECTION_TITLE',
96         'EXTERNAL_STORAGE_DISABLED_MESSAGE');
97
98 /**
99  * @type {DeviceHandler.Notification}
100  * @const
101  */
102 DeviceHandler.Notification.FORMAT_START = new DeviceHandler.Notification(
103     'formatStart',
104     'FORMATTING_OF_DEVICE_PENDING_TITLE',
105     'FORMATTING_OF_DEVICE_PENDING_MESSAGE');
106
107 /**
108  * @type {DeviceHandler.Notification}
109  * @const
110  */
111 DeviceHandler.Notification.FORMAT_SUCCESS = new DeviceHandler.Notification(
112     'formatSuccess',
113     'FORMATTING_OF_DEVICE_FINISHED_TITLE',
114     'FORMATTING_FINISHED_SUCCESS_MESSAGE');
115
116 /**
117  * @type {DeviceHandler.Notification}
118  * @const
119  */
120 DeviceHandler.Notification.FORMAT_FAIL = new DeviceHandler.Notification(
121     'formatFail',
122     'FORMATTING_OF_DEVICE_FAILED_TITLE',
123     'FORMATTING_FINISHED_FAILURE_MESSAGE');
124
125 /**
126  * Shows the notification for the device path.
127  * @param {string} devicePath Device path.
128  * @param {string=} opt_message Message overrides the default message.
129  * @return {string} Notification ID.
130  */
131 DeviceHandler.Notification.prototype.show = function(devicePath, opt_message) {
132   this.clearTimeout_();
133   var notificationId = this.makeId_(devicePath);
134   this.queue_.run(function(callback) {
135     chrome.notifications.create(
136         notificationId,
137         {
138           type: 'basic',
139           title: str(this.title),
140           message: opt_message || str(this.message),
141           iconUrl: chrome.runtime.getURL('/common/images/icon96.png')
142         },
143         callback);
144   }.bind(this));
145   return notificationId;
146 };
147
148 /**
149  * Shows the notificaiton after 5 seconds.
150  * @param {string} devicePath Device path.
151  */
152 DeviceHandler.Notification.prototype.showLater = function(devicePath) {
153   this.clearTimeout_();
154   this.pendingShowTimerId_ = setTimeout(this.show.bind(this, devicePath), 5000);
155 };
156
157 /**
158  * Hides the notification for the device path.
159  * @param {string} devicePath Device path.
160  */
161 DeviceHandler.Notification.prototype.hide = function(devicePath) {
162   this.clearTimeout_();
163   this.queue_.run(function(callback) {
164     chrome.notifications.clear(this.makeId_(devicePath), callback);
165   }.bind(this));
166 };
167
168 /**
169  * Makes a notification ID for the device path.
170  * @param {string} devicePath Device path.
171  * @return {string} Notification ID.
172  * @private
173  */
174 DeviceHandler.Notification.prototype.makeId_ = function(devicePath) {
175   return this.prefix + ':' + devicePath;
176 };
177
178 /**
179  * Cancels the timeout request.
180  * @private
181  */
182 DeviceHandler.Notification.prototype.clearTimeout_ = function() {
183   if (this.pendingShowTimerId_) {
184     clearTimeout(this.pendingShowTimerId_);
185     this.pendingShowTimerId_ = 0;
186   }
187 };
188
189 /**
190  * Handles notifications from C++ sides.
191  * @param {DeviceEvent} event Device event.
192  * @private
193  */
194 DeviceHandler.prototype.onDeviceChanged_ = function(event) {
195   switch (event.type) {
196     case 'added':
197       DeviceHandler.Notification.DEVICE.showLater(event.devicePath);
198       this.mountStatus_[event.devicePath] = DeviceHandler.MountStatus.NO_RESULT;
199       break;
200     case 'disabled':
201       DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.show(
202           event.devicePath);
203       break;
204     case 'scan_canceled':
205       DeviceHandler.Notification.DEVICE.hide(event.devicePath);
206       break;
207     case 'removed':
208       DeviceHandler.Notification.DEVICE.hide(event.devicePath);
209       DeviceHandler.Notification.DEVICE_FAIL.hide(event.devicePath);
210       DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.hide(
211           event.devicePath);
212       delete this.mountStatus_[event.devicePath];
213       break;
214     case 'format_start':
215       DeviceHandler.Notification.FORMAT_START.show(event.devicePath);
216       break;
217     case 'format_success':
218       DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
219       DeviceHandler.Notification.FORMAT_SUCCESS.show(event.devicePath);
220       break;
221     case 'format_fail':
222       DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
223       DeviceHandler.Notification.FORMAT_FAIL.show(event.devicePath);
224       break;
225   }
226 };
227
228 /**
229  * Mount status for the device.
230  * Each multi-partition devices can obtain multiple mount completed events.
231  * This status shows what results are already obtained for the device.
232  * @enum {string}
233  * @const
234  */
235 DeviceHandler.MountStatus = Object.freeze({
236   // There is no mount results on the device.
237   NO_RESULT: 'noResult',
238   // There is no error on the device.
239   SUCCESS: 'success',
240   // There is only parent errors, that can be overrided by child results.
241   ONLY_PARENT_ERROR: 'onlyParentError',
242   // There is one child error.
243   CHILD_ERROR: 'childError',
244   // There is multiple child results and at least one is failure.
245   MULTIPART_ERROR: 'multipartError'
246 });
247
248 /**
249  * Handles mount completed events to show notifications for removable devices.
250  * @param {MountCompletedEvent} event Mount completed event.
251  * @private
252  */
253 DeviceHandler.prototype.onMountCompleted_ = function(event) {
254   // If this is remounting, which happens when resuming ChromeOS, the device has
255   // already inserted to the computer. So we suppress the notification.
256   var volume = event.volumeMetadata;
257   if (!volume.deviceType || event.isRemounting)
258     return;
259
260   var getFirstStatus = function(event) {
261     if (event.status === 'success')
262       return DeviceHandler.MountStatus.SUCCESS;
263     else if (event.volumeMetadata.isParentDevice)
264       return DeviceHandler.MountStatus.ONLY_PARENT_ERROR;
265     else
266       return DeviceHandler.MountStatus.CHILD_ERROR;
267   };
268
269   // Update the current status.
270   switch (this.mountStatus_[volume.devicePath]) {
271     // If there is no related device, do nothing.
272     case undefined:
273       return;
274     // If the multipart error message has already shown, do nothing because the
275     // message does not changed by the following mount results.
276     case DeviceHandler.MULTIPART_ERROR:
277       return;
278     // If this is the first result, hide the scanning notification.
279     case DeviceHandler.MountStatus.NO_RESULT:
280       DeviceHandler.Notification.DEVICE.hide(volume.devicePath);
281       this.mountStatus_[volume.devicePath] = getFirstStatus(event);
282       break;
283     // If there are only parent errors, and the new result is child's one, hide
284     // the parent error. (parent device contains partition table, which is
285     // unmountable)
286     case DeviceHandler.MountStatus.ONLY_PARENT_ERROR:
287       if (!volume.isParentDevice)
288         DeviceHandler.Notification.DEVICE_FAIL.hide(volume.devicePath);
289       this.mountStatus_[volume.devicePath] = getFirstStatus(event);
290       break;
291     // We have a multi-partition device for which at least one mount
292     // failed.
293     case DeviceHandler.MountStatus.SUCCESS:
294     case DeviceHandler.MountStatus.CHILD_ERROR:
295       if (this.mountStatus_[volume.devicePath] ===
296               DeviceHandler.MountStatus.SUCCESS &&
297           event.status === 'success') {
298         this.mountStatus_[volume.devicePath] =
299             DeviceHandler.MountStatus.SUCCESS;
300       } else {
301         this.mountStatus_[volume.devicePath] =
302             DeviceHandler.MountStatus.MULTIPART_ERROR;
303       }
304       break;
305   }
306
307   // Show the notification for the current errors.
308   // If there is no error, do not show/update the notification.
309   var message;
310   switch (this.mountStatus_[volume.devicePath]) {
311     case DeviceHandler.MountStatus.MULTIPART_ERROR:
312       message = volume.deviceLabel ?
313           strf('MULTIPART_DEVICE_UNSUPPORTED_MESSAGE', volume.deviceLabel) :
314           str('MULTIPART_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
315       break;
316     case DeviceHandler.MountStatus.CHILD_ERROR:
317     case DeviceHandler.MountStatus.ONLY_PARENT_ERROR:
318       if (event.status === 'error_unsuported_filesystem') {
319         message = volume.deviceLabel ?
320             strf('DEVICE_UNSUPPORTED_MESSAGE', volume.deviceLabel) :
321             str('DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
322       } else {
323         message = volume.deviceLabel ?
324             strf('DEVICE_UNKNOWN_MESSAGE', volume.deviceLabel) :
325             str('DEVICE_UNKNOWN_DEFAULT_MESSAGE');
326       }
327       break;
328   }
329   if (message) {
330     DeviceHandler.Notification.DEVICE_FAIL.hide(volume.devicePath);
331     DeviceHandler.Notification.DEVICE_FAIL.show(volume.devicePath, message);
332   }
333 };