| 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 |