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