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.
10 * @extends {cr.EventTarget}
12 function DeviceHandler() {
13 cr.EventTarget.call(this);
16 * Map of device path and mount status of devices.
17 * @type {Object.<string, DeviceHandler.MountStatus>}
20 this.mountStatus_ = {};
23 * Map of device path and volumeId of the volume that should be navigated to
24 * from the 'device inserted' notification.
25 * @type {Object.<string, DirectoryEntry>}
28 this.navigationVolumes_ = {};
30 chrome.fileManagerPrivate.onDeviceChanged.addListener(
31 this.onDeviceChanged_.bind(this));
32 chrome.fileManagerPrivate.onMountCompleted.addListener(
33 this.onMountCompleted_.bind(this));
34 chrome.notifications.onButtonClicked.addListener(
35 this.onNotificationButtonClicked_.bind(this));
38 DeviceHandler.prototype = {
39 __proto__: cr.EventTarget.prototype
43 * An event name trigerred when a user requests to navigate to a volume.
44 * The event object must have a volumeId property.
48 DeviceHandler.VOLUME_NAVIGATION_REQUESTED = 'volumenavigationrequested';
52 * @param {string} prefix Prefix of notification ID.
53 * @param {string} title String ID of title.
54 * @param {string} message String ID of message.
55 * @param {string=} opt_buttonLabel String ID of the button label.
58 DeviceHandler.Notification = function(prefix, title, message, opt_buttonLabel) {
60 * Prefix of notification ID.
72 * String ID of message.
75 this.message = message;
78 * String ID of button label.
81 this.buttonLabel = opt_buttonLabel || null;
85 * @type {AsyncUtil.Queue}
88 this.queue_ = new AsyncUtil.Queue();
94 * @type {DeviceHandler.Notification}
97 DeviceHandler.Notification.DEVICE_NAVIGATION = new DeviceHandler.Notification(
99 'REMOVABLE_DEVICE_DETECTION_TITLE',
100 'REMOVABLE_DEVICE_NAVIGATION_MESSAGE',
101 'REMOVABLE_DEVICE_NAVIGATION_BUTTON_LABEL');
104 * @type {DeviceHandler.Notification}
107 DeviceHandler.Notification.DEVICE_FAIL = new DeviceHandler.Notification(
109 'REMOVABLE_DEVICE_DETECTION_TITLE',
110 'DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
113 * @type {DeviceHandler.Notification}
116 DeviceHandler.Notification.DEVICE_FAIL_UNKNOWN = new DeviceHandler.Notification(
118 'REMOVABLE_DEVICE_DETECTION_TITLE',
119 'DEVICE_UNKNOWN_DEFAULT_MESSAGE',
120 'DEVICE_UNKNOWN_BUTTON_LABEL');
123 * @type {DeviceHandler.Notification}
126 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED =
127 new DeviceHandler.Notification(
129 'REMOVABLE_DEVICE_DETECTION_TITLE',
130 'EXTERNAL_STORAGE_DISABLED_MESSAGE');
133 * @type {DeviceHandler.Notification}
136 DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED =
137 new DeviceHandler.Notification(
139 'DEVICE_HARD_UNPLUGGED_TITLE',
140 'DEVICE_HARD_UNPLUGGED_MESSAGE');
143 * @type {DeviceHandler.Notification}
146 DeviceHandler.Notification.FORMAT_START = new DeviceHandler.Notification(
148 'FORMATTING_OF_DEVICE_PENDING_TITLE',
149 'FORMATTING_OF_DEVICE_PENDING_MESSAGE');
152 * @type {DeviceHandler.Notification}
155 DeviceHandler.Notification.FORMAT_SUCCESS = new DeviceHandler.Notification(
157 'FORMATTING_OF_DEVICE_FINISHED_TITLE',
158 'FORMATTING_FINISHED_SUCCESS_MESSAGE');
161 * @type {DeviceHandler.Notification}
164 DeviceHandler.Notification.FORMAT_FAIL = new DeviceHandler.Notification(
166 'FORMATTING_OF_DEVICE_FAILED_TITLE',
167 'FORMATTING_FINISHED_FAILURE_MESSAGE');
170 * Shows the notification for the device path.
171 * @param {string} devicePath Device path.
172 * @param {string=} opt_message Message overrides the default message.
173 * @return {string} Notification ID.
175 DeviceHandler.Notification.prototype.show = function(devicePath, opt_message) {
176 var notificationId = this.makeId_(devicePath);
177 this.queue_.run(function(callback) {
179 this.buttonLabel ? [{title: str(this.buttonLabel)}] : undefined;
180 chrome.notifications.create(
184 title: str(this.title),
185 message: opt_message || str(this.message),
186 iconUrl: chrome.runtime.getURL('/common/images/icon96.png'),
191 return notificationId;
195 * Hides the notification for the device path.
196 * @param {string} devicePath Device path.
198 DeviceHandler.Notification.prototype.hide = function(devicePath) {
199 this.queue_.run(function(callback) {
200 chrome.notifications.clear(this.makeId_(devicePath), callback);
205 * Makes a notification ID for the device path.
206 * @param {string} devicePath Device path.
207 * @return {string} Notification ID.
210 DeviceHandler.Notification.prototype.makeId_ = function(devicePath) {
211 return this.prefix + ':' + devicePath;
215 * Handles notifications from C++ sides.
216 * @param {DeviceEvent} event Device event.
219 DeviceHandler.prototype.onDeviceChanged_ = function(event) {
220 switch (event.type) {
222 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.show(
226 DeviceHandler.Notification.DEVICE_FAIL.hide(event.devicePath);
227 DeviceHandler.Notification.DEVICE_EXTERNAL_STORAGE_DISABLED.hide(
229 delete this.mountStatus_[event.devicePath];
231 case 'hard_unplugged':
232 DeviceHandler.Notification.DEVICE_HARD_UNPLUGGED.show(
236 DeviceHandler.Notification.FORMAT_START.show(event.devicePath);
238 case 'format_success':
239 DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
240 DeviceHandler.Notification.FORMAT_SUCCESS.show(event.devicePath);
243 DeviceHandler.Notification.FORMAT_START.hide(event.devicePath);
244 DeviceHandler.Notification.FORMAT_FAIL.show(event.devicePath);
247 console.error('Unknown event tyep: ' + event.type);
253 * Mount status for the device.
254 * Each multi-partition devices can obtain multiple mount completed events.
255 * This status shows what results are already obtained for the device.
259 DeviceHandler.MountStatus = Object.freeze({
260 // There is no mount results on the device.
261 NO_RESULT: 'noResult',
262 // There is no error on the device.
264 // There is only parent errors, that can be overridden by child results.
265 ONLY_PARENT_ERROR: 'onlyParentError',
266 // There is one child error.
267 CHILD_ERROR: 'childError',
268 // There is multiple child results and at least one is failure.
269 MULTIPART_ERROR: 'multipartError'
273 * Handles mount completed events to show notifications for removable devices.
274 * @param {MountCompletedEvent} event Mount completed event.
277 DeviceHandler.prototype.onMountCompleted_ = function(event) {
278 // If this is remounting, which happens when resuming ChromeOS, the device has
279 // already inserted to the computer. So we suppress the notification.
280 var volume = event.volumeMetadata;
281 if (!volume.deviceType || !event.shouldNotify)
284 // If the current volume status is succeed and it should be handled in
285 // Files.app, show the notification to navigate the volume.
286 if (event.eventType === 'mount' &&
287 event.status === 'success') {
288 if (this.navigationVolumes_[event.volumeMetadata.devicePath]) {
289 // The notification has already shown for the device. It seems the device
290 // has multiple volumes. The order of mount events of volumes are
291 // undetermind, so it compares the volume Id and uses the earier order ID
292 // to prevent Files.app from navigating to different volumes for each
294 if (event.volumeMetadata.volumeId <
295 this.navigationVolumes_[event.volumeMetadata.devicePath]) {
296 this.navigationVolumes_[event.volumeMetadata.devicePath] =
297 event.volumeMetadata.volumeId;
300 this.navigationVolumes_[event.volumeMetadata.devicePath] =
301 event.volumeMetadata.volumeId;
302 DeviceHandler.Notification.DEVICE_NAVIGATION.show(
303 event.volumeMetadata.devicePath);
305 } else if (event.status === 'error_unknown_filesystem') {
306 // The volume id is necessary to navigate when users click start
308 this.navigationVolumes_[event.volumeMetadata.devicePath] =
309 event.volumeMetadata.volumeId;
312 if (event.eventType === 'unmount') {
313 this.navigationVolumes_[volume.devicePath] = null;
314 DeviceHandler.Notification.DEVICE_NAVIGATION.hide(volume.devicePath);
317 var getFirstStatus = function(event) {
318 if (event.status === 'success')
319 return DeviceHandler.MountStatus.SUCCESS;
320 else if (event.volumeMetadata.isParentDevice)
321 return DeviceHandler.MountStatus.ONLY_PARENT_ERROR;
323 return DeviceHandler.MountStatus.CHILD_ERROR;
326 // Update the current status.
327 if (!this.mountStatus_[volume.devicePath])
328 this.mountStatus_[volume.devicePath] = DeviceHandler.MountStatus.NO_RESULT;
329 switch (this.mountStatus_[volume.devicePath]) {
330 // If the multipart error message has already shown, do nothing because the
331 // message does not changed by the following mount results.
332 case DeviceHandler.MountStatus.MULTIPART_ERROR:
334 // If this is the first result, hide the scanning notification.
335 case DeviceHandler.MountStatus.NO_RESULT:
336 this.mountStatus_[volume.devicePath] = getFirstStatus(event);
338 // If there are only parent errors, and the new result is child's one, hide
339 // the parent error. (parent device contains partition table, which is
341 case DeviceHandler.MountStatus.ONLY_PARENT_ERROR:
342 if (!volume.isParentDevice)
343 DeviceHandler.Notification.DEVICE_FAIL.hide(volume.devicePath);
344 this.mountStatus_[volume.devicePath] = getFirstStatus(event);
346 // We have a multi-partition device for which at least one mount
348 case DeviceHandler.MountStatus.SUCCESS:
349 case DeviceHandler.MountStatus.CHILD_ERROR:
350 if (this.mountStatus_[volume.devicePath] ===
351 DeviceHandler.MountStatus.SUCCESS &&
352 event.status === 'success') {
353 this.mountStatus_[volume.devicePath] =
354 DeviceHandler.MountStatus.SUCCESS;
356 this.mountStatus_[volume.devicePath] =
357 DeviceHandler.MountStatus.MULTIPART_ERROR;
362 if (event.eventType === 'unmount')
365 // Show the notification for the current errors.
366 // If there is no error, do not show/update the notification.
368 switch (this.mountStatus_[volume.devicePath]) {
369 case DeviceHandler.MountStatus.MULTIPART_ERROR:
370 message = volume.deviceLabel ?
371 strf('MULTIPART_DEVICE_UNSUPPORTED_MESSAGE', volume.deviceLabel) :
372 str('MULTIPART_DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
373 DeviceHandler.Notification.DEVICE_FAIL.show(
377 case DeviceHandler.MountStatus.CHILD_ERROR:
378 case DeviceHandler.MountStatus.ONLY_PARENT_ERROR:
379 if (event.status === 'error_unsupported_filesystem') {
380 message = volume.deviceLabel ?
381 strf('DEVICE_UNSUPPORTED_MESSAGE', volume.deviceLabel) :
382 str('DEVICE_UNSUPPORTED_DEFAULT_MESSAGE');
383 DeviceHandler.Notification.DEVICE_FAIL.show(
387 message = volume.deviceLabel ?
388 strf('DEVICE_UNKNOWN_MESSAGE', volume.deviceLabel) :
389 str('DEVICE_UNKNOWN_DEFAULT_MESSAGE');
390 DeviceHandler.Notification.DEVICE_FAIL_UNKNOWN.show(
398 * Handles notification button click.
399 * @param {string} id ID of the notification.
402 DeviceHandler.prototype.onNotificationButtonClicked_ = function(id) {
403 var pos = id.indexOf(':');
404 var type = id.substr(0, pos);
405 var path = id.substr(pos + 1);
406 if (type === 'deviceNavigation' || type === 'deviceFail') {
407 chrome.notifications.clear(id, function() {});
408 var event = new Event(DeviceHandler.VOLUME_NAVIGATION_REQUESTED);
409 event.volumeId = this.navigationVolumes_[path];
410 this.dispatchEvent(event);