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.
8 * Handler of device event.
11 function DeviceHandler() {
13 * Map of device path and mount status of devices.
14 * @type {Object.<string, DeviceHandler.MountStatus>}
17 this.mountStatus_ = {};
19 chrome.fileBrowserPrivate.onDeviceChanged.addListener(
20 this.onDeviceChanged_.bind(this));
21 chrome.fileBrowserPrivate.onMountCompleted.addListener(
22 this.onMountCompleted_.bind(this));
29 * @param {string} prefix Prefix of notification ID.
30 * @param {string} title String ID of title.
31 * @param {string} message String ID of message.
34 DeviceHandler.Notification = function(prefix, title, message) {
36 * Prefix of notification ID.
48 * String ID of message.
51 this.message = message;
55 * @type {AsyncUtil.Queue}
58 this.queue_ = new AsyncUtil.Queue();
65 this.pendingShowTimerId_ = 0;
71 * @type {DeviceHandler.Notification}
74 DeviceHandler.Notification.DEVICE = new DeviceHandler.Notification(
76 'REMOVABLE_DEVICE_DETECTION_TITLE',
77 'REMOVABLE_DEVICE_SCANNING_MESSAGE');
80 * @type {DeviceHandler.Notification}
83 DeviceHandler.Notification.DEVICE_FAIL = new DeviceHandler.Notification(
85 'REMOVABLE_DEVICE_DETECTION_TITLE',
86 'DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
89 * @type {DeviceHandler.Notification}
92 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED =
93 new DeviceHandler.Notification(
95 'REMOVABLE_DEVICE_DETECTION_TITLE',
96 'EXTERNAL_STORAGE_DISABLED_MESSAGE');
99 * @type {DeviceHandler.Notification}
102 DeviceHandler.Notification.FORMAT_START = new DeviceHandler.Notification(
104 'FORMATTING_OF_DEVICE_PENDING_TITLE',
105 'FORMATTING_OF_DEVICE_PENDING_MESSAGE');
108 * @type {DeviceHandler.Notification}
111 DeviceHandler.Notification.FORMAT_SUCCESS = new DeviceHandler.Notification(
113 'FORMATTING_OF_DEVICE_FINISHED_TITLE',
114 'FORMATTING_FINISHED_SUCCESS_MESSAGE');
117 * @type {DeviceHandler.Notification}
120 DeviceHandler.Notification.FORMAT_FAIL = new DeviceHandler.Notification(
122 'FORMATTING_OF_DEVICE_FAILED_TITLE',
123 'FORMATTING_FINISHED_FAILURE_MESSAGE');
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.
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(
139 title: str(this.title),
140 message: opt_message || str(this.message),
141 iconUrl: chrome.runtime.getURL('/common/images/icon96.png')
145 return notificationId;
149 * Shows the notificaiton after 5 seconds.
150 * @param {string} devicePath Device path.
152 DeviceHandler.Notification.prototype.showLater = function(devicePath) {
153 this.clearTimeout_();
154 this.pendingShowTimerId_ = setTimeout(this.show.bind(this, devicePath), 5000);
158 * Hides the notification for the device path.
159 * @param {string} devicePath Device path.
161 DeviceHandler.Notification.prototype.hide = function(devicePath) {
162 this.clearTimeout_();
163 this.queue_.run(function(callback) {
164 chrome.notifications.clear(this.makeId_(devicePath), callback);
169 * Makes a notification ID for the device path.
170 * @param {string} devicePath Device path.
171 * @return {string} Notification ID.
174 DeviceHandler.Notification.prototype.makeId_ = function(devicePath) {
175 return this.prefix + ':' + devicePath;
179 * Cancels the timeout request.
182 DeviceHandler.Notification.prototype.clearTimeout_ = function() {
183 if (this.pendingShowTimerId_) {
184 clearTimeout(this.pendingShowTimerId_);
185 this.pendingShowTimerId_ = 0;
190 * Handles notifications from C++ sides.
191 * @param {DeviceEvent} event Device event.
194 DeviceHandler.prototype.onDeviceChanged_ = function(event) {
195 switch (event.type) {
197 DeviceHandler.Notification.DEVICE.showLater(event.devicePath);
198 this.mountStatus_[event.devicePath] = DeviceHandler.MountStatus.NO_RESULT;
201 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.show(
204 case 'scan_canceled':
205 DeviceHandler.Notification.DEVICE.hide(event.devicePath);
208 DeviceHandler.Notification.DEVICE.hide(event.devicePath);
209 DeviceHandler.Notification.DEVICE_FAIL.hide(event.devicePath);
210 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.hide(
212 delete this.mountStatus_[event.devicePath];
215 DeviceHandler.Notification.FORMAT_START.show(event.devicePath);
217 case 'format_success':
218 DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
219 DeviceHandler.Notification.FORMAT_SUCCESS.show(event.devicePath);
222 DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
223 DeviceHandler.Notification.FORMAT_FAIL.show(event.devicePath);
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.
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.
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'
249 * Handles mount completed events to show notifications for removable devices.
250 * @param {MountCompletedEvent} event Mount completed event.
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)
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;
266 return DeviceHandler.MountStatus.CHILD_ERROR;
269 // Update the current status.
270 switch (this.mountStatus_[volume.devicePath]) {
271 // If there is no related device, do nothing.
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:
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);
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
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);
291 // We have a multi-partition device for which at least one mount
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;
301 this.mountStatus_[volume.devicePath] =
302 DeviceHandler.MountStatus.MULTIPART_ERROR;
307 // Show the notification for the current errors.
308 // If there is no error, do not show/update the notification.
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');
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');
323 message = volume.deviceLabel ?
324 strf('DEVICE_UNKNOWN_MESSAGE', volume.deviceLabel) :
325 str('DEVICE_UNKNOWN_DEFAULT_MESSAGE');
330 DeviceHandler.Notification.DEVICE_FAIL.hide(volume.devicePath);
331 DeviceHandler.Notification.DEVICE_FAIL.show(volume.devicePath, message);