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.
6 * Handler of device event.
8 * @extends {cr.EventTarget}
10 function DeviceHandler() {
11 cr.EventTarget.call(this);
14 * Map of device path and mount status of devices.
15 * @type {Object.<string, DeviceHandler.MountStatus>}
18 this.mountStatus_ = {};
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));
28 DeviceHandler.prototype = {
29 __proto__: cr.EventTarget.prototype
33 * An event name trigerred when a user requests to navigate to a volume.
34 * The event object must have a volumeId property.
38 DeviceHandler.VOLUME_NAVIGATION_REQUESTED = 'volumenavigationrequested';
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.
48 DeviceHandler.Notification = function(prefix, title, message, opt_buttonLabel) {
50 * Prefix of notification ID.
62 * String ID of message.
65 this.message = message;
68 * String ID of button label.
71 this.buttonLabel = opt_buttonLabel || null;
75 * @type {AsyncUtil.Queue}
78 this.queue_ = new AsyncUtil.Queue();
84 * @type {DeviceHandler.Notification}
87 DeviceHandler.Notification.DEVICE_NAVIGATION = new DeviceHandler.Notification(
89 'REMOVABLE_DEVICE_DETECTION_TITLE',
90 'REMOVABLE_DEVICE_NAVIGATION_MESSAGE',
91 'REMOVABLE_DEVICE_NAVIGATION_BUTTON_LABEL');
94 * @type {DeviceHandler.Notification}
97 DeviceHandler.Notification.DEVICE_FAIL = new DeviceHandler.Notification(
99 'REMOVABLE_DEVICE_DETECTION_TITLE',
100 'DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
103 * @type {DeviceHandler.Notification}
106 DeviceHandler.Notification.DEVICE_FAIL_UNKNOWN = new DeviceHandler.Notification(
108 'REMOVABLE_DEVICE_DETECTION_TITLE',
109 'DEVICE_UNKNOWN_DEFAULT_MESSAGE',
110 'DEVICE_UNKNOWN_BUTTON_LABEL');
113 * @type {DeviceHandler.Notification}
116 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED =
117 new DeviceHandler.Notification(
119 'REMOVABLE_DEVICE_DETECTION_TITLE',
120 'EXTERNAL_STORAGE_DISABLED_MESSAGE');
123 * @type {DeviceHandler.Notification}
126 DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED =
127 new DeviceHandler.Notification(
129 'DEVICE_HARD_UNPLUGGED_TITLE',
130 'DEVICE_HARD_UNPLUGGED_MESSAGE');
133 * @type {DeviceHandler.Notification}
136 DeviceHandler.Notification.FORMAT_START = new DeviceHandler.Notification(
138 'FORMATTING_OF_DEVICE_PENDING_TITLE',
139 'FORMATTING_OF_DEVICE_PENDING_MESSAGE');
142 * @type {DeviceHandler.Notification}
145 DeviceHandler.Notification.FORMAT_SUCCESS = new DeviceHandler.Notification(
147 'FORMATTING_OF_DEVICE_FINISHED_TITLE',
148 'FORMATTING_FINISHED_SUCCESS_MESSAGE');
151 * @type {DeviceHandler.Notification}
154 DeviceHandler.Notification.FORMAT_FAIL = new DeviceHandler.Notification(
156 'FORMATTING_OF_DEVICE_FAILED_TITLE',
157 'FORMATTING_FINISHED_FAILURE_MESSAGE');
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.
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);
170 return notificationId;
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.
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) {
186 this.showInternal_(notificationId, null, callback);
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
199 DeviceHandler.Notification.prototype.showInternal_ = function(
200 notificationId, message, callback) {
202 this.buttonLabel ? [{title: str(this.buttonLabel)}] : undefined;
203 chrome.notifications.create(
207 title: str(this.title),
208 message: message || str(this.message),
209 iconUrl: chrome.runtime.getURL('/common/images/icon96.png'),
216 * Hides the notification for the device path.
217 * @param {string} devicePath Device path.
219 DeviceHandler.Notification.prototype.hide = function(devicePath) {
220 this.queue_.run(function(callback) {
221 chrome.notifications.clear(this.makeId_(devicePath), callback);
226 * Makes a notification ID for the device path.
227 * @param {string} devicePath Device path.
228 * @return {string} Notification ID.
231 DeviceHandler.Notification.prototype.makeId_ = function(devicePath) {
232 return this.prefix + ':' + devicePath;
236 * Handles notifications from C++ sides.
237 * @param {DeviceEvent} event Device event.
240 DeviceHandler.prototype.onDeviceChanged_ = function(event) {
241 switch (event.type) {
243 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.show(
247 DeviceHandler.Notification.DEVICE_FAIL.hide(event.devicePath);
248 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.hide(
250 delete this.mountStatus_[event.devicePath];
252 case 'hard_unplugged':
253 DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED.show(
257 DeviceHandler.Notification.FORMAT_START.show(event.devicePath);
259 case 'format_success':
260 DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
261 DeviceHandler.Notification.FORMAT_SUCCESS.show(event.devicePath);
264 DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
265 DeviceHandler.Notification.FORMAT_FAIL.show(event.devicePath);
268 console.error('Unknown event tyep: ' + event.type);
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.
280 DeviceHandler.MountStatus = {
281 // There is no mount results on the device.
282 NO_RESULT: 'noResult',
283 // There is no error on the device.
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'
292 Object.freeze(DeviceHandler.MountStatus);
295 * Handles mount completed events to show notifications for removable devices.
296 * @param {MountCompletedEvent} event Mount completed event.
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)
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);
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;
320 return DeviceHandler.MountStatus.CHILD_ERROR;
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:
331 // If this is the first result, hide the scanning notification.
332 case DeviceHandler.MountStatus.NO_RESULT:
333 this.mountStatus_[volume.devicePath] = getFirstStatus(event);
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
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);
343 // We have a multi-partition device for which at least one mount
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;
353 this.mountStatus_[volume.devicePath] =
354 DeviceHandler.MountStatus.MULTIPART_ERROR;
359 if (event.eventType === 'unmount')
362 // Show the notification for the current errors.
363 // If there is no error, do not show/update the notification.
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(
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(
384 message = volume.deviceLabel ?
385 strf('DEVICE_UNKNOWN_MESSAGE', volume.deviceLabel) :
386 str('DEVICE_UNKNOWN_DEFAULT_MESSAGE');
387 DeviceHandler.Notification.DEVICE_FAIL_UNKNOWN.show(
395 * Handles notification button click.
396 * @param {string} id ID of the notification.
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);