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 // Any tasks that communicates with the portable device may take >100ms to 6 // complete. Those tasks should be run on an blocking thread instead of the 7 // UI thread. 8 9 #include "components/storage_monitor/portable_device_watcher_win.h" 10 11 #include <dbt.h> 12 #include <portabledevice.h> 13 14 #include "base/files/file_path.h" 15 #include "base/logging.h" 16 #include "base/stl_util.h" 17 #include "base/strings/string_util.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "base/threading/sequenced_worker_pool.h" 20 #include "base/win/scoped_co_mem.h" 21 #include "base/win/scoped_comptr.h" 22 #include "base/win/scoped_propvariant.h" 23 #include "components/storage_monitor/media_storage_util.h" 24 #include "components/storage_monitor/removable_device_constants.h" 25 #include "components/storage_monitor/storage_info.h" 26 #include "content/public/browser/browser_thread.h" 27 28 namespace storage_monitor { 29 30 namespace { 31 32 // Name of the client application that communicates with the MTP device. 33 const base::char16 kClientName[] = L"Chromium"; 34 35 // Name of the sequenced task runner. 36 const char kMediaTaskRunnerName[] = "media-task-runner"; 37 38 // Returns true if |data| represents a class of portable devices. 39 bool IsPortableDeviceStructure(LPARAM data) { 40 DEV_BROADCAST_HDR* broadcast_hdr = 41 reinterpret_cast<DEV_BROADCAST_HDR*>(data); 42 if (!broadcast_hdr || 43 (broadcast_hdr->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)) { 44 return false; 45 } 46 47 GUID guidDevInterface = GUID_NULL; 48 if (FAILED(CLSIDFromString(kWPDDevInterfaceGUID, &guidDevInterface))) 49 return false; 50 DEV_BROADCAST_DEVICEINTERFACE* dev_interface = 51 reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(data); 52 return (IsEqualGUID(dev_interface->dbcc_classguid, guidDevInterface) != 0); 53 } 54 55 // Returns the portable device plug and play device ID string. 56 base::string16 GetPnpDeviceId(LPARAM data) { 57 DEV_BROADCAST_DEVICEINTERFACE* dev_interface = 58 reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(data); 59 if (!dev_interface) 60 return base::string16(); 61 base::string16 device_id(dev_interface->dbcc_name); 62 DCHECK(base::IsStringASCII(device_id)); 63 return StringToLowerASCII(device_id); 64 } 65 66 // Gets the friendly name of the device specified by the |pnp_device_id|. On 67 // success, returns true and fills in |name|. 68 bool GetFriendlyName(const base::string16& pnp_device_id, 69 IPortableDeviceManager* device_manager, 70 base::string16* name) { 71 DCHECK(device_manager); 72 DCHECK(name); 73 DWORD name_len = 0; 74 HRESULT hr = device_manager->GetDeviceFriendlyName(pnp_device_id.c_str(), 75 NULL, &name_len); 76 if (FAILED(hr)) 77 return false; 78 79 hr = device_manager->GetDeviceFriendlyName( 80 pnp_device_id.c_str(), WriteInto(name, name_len), &name_len); 81 return (SUCCEEDED(hr) && !name->empty()); 82 } 83 84 // Gets the manufacturer name of the device specified by the |pnp_device_id|. 85 // On success, returns true and fills in |name|. 86 bool GetManufacturerName(const base::string16& pnp_device_id, 87 IPortableDeviceManager* device_manager, 88 base::string16* name) { 89 DCHECK(device_manager); 90 DCHECK(name); 91 DWORD name_len = 0; 92 HRESULT hr = device_manager->GetDeviceManufacturer(pnp_device_id.c_str(), 93 NULL, &name_len); 94 if (FAILED(hr)) 95 return false; 96 97 hr = device_manager->GetDeviceManufacturer(pnp_device_id.c_str(), 98 WriteInto(name, name_len), 99 &name_len); 100 return (SUCCEEDED(hr) && !name->empty()); 101 } 102 103 // Gets the description of the device specified by the |pnp_device_id|. On 104 // success, returns true and fills in |description|. 105 bool GetDeviceDescription(const base::string16& pnp_device_id, 106 IPortableDeviceManager* device_manager, 107 base::string16* description) { 108 DCHECK(device_manager); 109 DCHECK(description); 110 DWORD desc_len = 0; 111 HRESULT hr = device_manager->GetDeviceDescription(pnp_device_id.c_str(), NULL, 112 &desc_len); 113 if (FAILED(hr)) 114 return false; 115 116 hr = device_manager->GetDeviceDescription(pnp_device_id.c_str(), 117 WriteInto(description, desc_len), 118 &desc_len); 119 return (SUCCEEDED(hr) && !description->empty()); 120 } 121 122 // On success, returns true and updates |client_info| with a reference to an 123 // IPortableDeviceValues interface that holds information about the 124 // application that communicates with the device. 125 bool GetClientInformation( 126 base::win::ScopedComPtr<IPortableDeviceValues>* client_info) { 127 HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues), 128 NULL, CLSCTX_INPROC_SERVER); 129 if (FAILED(hr)) { 130 DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues"; 131 return false; 132 } 133 134 // Attempt to set client details. 135 (*client_info)->SetStringValue(WPD_CLIENT_NAME, kClientName); 136 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0); 137 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0); 138 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0); 139 (*client_info)->SetUnsignedIntegerValue( 140 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION); 141 (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS, 142 GENERIC_READ); 143 return true; 144 } 145 146 // Opens the device for communication. |pnp_device_id| specifies the plug and 147 // play device ID string. On success, returns true and updates |device| with a 148 // reference to the portable device interface. 149 bool SetUp(const base::string16& pnp_device_id, 150 base::win::ScopedComPtr<IPortableDevice>* device) { 151 base::win::ScopedComPtr<IPortableDeviceValues> client_info; 152 if (!GetClientInformation(&client_info)) 153 return false; 154 155 HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL, 156 CLSCTX_INPROC_SERVER); 157 if (FAILED(hr)) { 158 DPLOG(ERROR) << "Failed to create an instance of IPortableDevice"; 159 return false; 160 } 161 162 hr = (*device)->Open(pnp_device_id.c_str(), client_info.get()); 163 if (SUCCEEDED(hr)) 164 return true; 165 166 if (hr == E_ACCESSDENIED) 167 DPLOG(ERROR) << "Access denied to open the device"; 168 return false; 169 } 170 171 // Returns the unique id property key of the object specified by the 172 // |object_id|. 173 REFPROPERTYKEY GetUniqueIdPropertyKey(const base::string16& object_id) { 174 return (object_id == WPD_DEVICE_OBJECT_ID) ? 175 WPD_DEVICE_SERIAL_NUMBER : WPD_OBJECT_PERSISTENT_UNIQUE_ID; 176 } 177 178 // On success, returns true and populates |properties_to_read| with the 179 // property key of the object specified by the |object_id|. 180 bool PopulatePropertyKeyCollection( 181 const base::string16& object_id, 182 base::win::ScopedComPtr<IPortableDeviceKeyCollection>* properties_to_read) { 183 HRESULT hr = properties_to_read->CreateInstance( 184 __uuidof(PortableDeviceKeyCollection), NULL, CLSCTX_INPROC_SERVER); 185 if (FAILED(hr)) { 186 DPLOG(ERROR) << "Failed to create IPortableDeviceKeyCollection instance"; 187 return false; 188 } 189 REFPROPERTYKEY key = GetUniqueIdPropertyKey(object_id); 190 hr = (*properties_to_read)->Add(key); 191 return SUCCEEDED(hr); 192 } 193 194 // Wrapper function to get content property string value. 195 bool GetStringPropertyValue(IPortableDeviceValues* properties_values, 196 REFPROPERTYKEY key, 197 base::string16* value) { 198 DCHECK(properties_values); 199 DCHECK(value); 200 base::win::ScopedCoMem<base::char16> buffer; 201 HRESULT hr = properties_values->GetStringValue(key, &buffer); 202 if (FAILED(hr)) 203 return false; 204 *value = static_cast<const base::char16*>(buffer); 205 return true; 206 } 207 208 // Constructs a unique identifier for the object specified by the |object_id|. 209 // On success, returns true and fills in |unique_id|. 210 bool GetObjectUniqueId(IPortableDevice* device, 211 const base::string16& object_id, 212 base::string16* unique_id) { 213 DCHECK(device); 214 DCHECK(unique_id); 215 base::win::ScopedComPtr<IPortableDeviceContent> content; 216 HRESULT hr = device->Content(content.Receive()); 217 if (FAILED(hr)) { 218 DPLOG(ERROR) << "Failed to get IPortableDeviceContent interface"; 219 return false; 220 } 221 222 base::win::ScopedComPtr<IPortableDeviceProperties> properties; 223 hr = content->Properties(properties.Receive()); 224 if (FAILED(hr)) { 225 DPLOG(ERROR) << "Failed to get IPortableDeviceProperties interface"; 226 return false; 227 } 228 229 base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read; 230 if (!PopulatePropertyKeyCollection(object_id, &properties_to_read)) 231 return false; 232 233 base::win::ScopedComPtr<IPortableDeviceValues> properties_values; 234 if (FAILED(properties->GetValues(object_id.c_str(), 235 properties_to_read.get(), 236 properties_values.Receive()))) { 237 return false; 238 } 239 240 REFPROPERTYKEY key = GetUniqueIdPropertyKey(object_id); 241 return GetStringPropertyValue(properties_values.get(), key, unique_id); 242 } 243 244 // Constructs the device storage unique identifier using |device_serial_num| and 245 // |storage_id|. On success, returns true and fills in |device_storage_id|. 246 bool ConstructDeviceStorageUniqueId(const base::string16& device_serial_num, 247 const base::string16& storage_id, 248 std::string* device_storage_id) { 249 if (device_serial_num.empty() && storage_id.empty()) 250 return false; 251 252 DCHECK(device_storage_id); 253 *device_storage_id = StorageInfo::MakeDeviceId( 254 StorageInfo::MTP_OR_PTP, 255 base::UTF16ToUTF8(storage_id + L':' + device_serial_num)); 256 return true; 257 } 258 259 // Gets a list of removable storage object identifiers present in |device|. 260 // On success, returns true and fills in |storage_object_ids|. 261 bool GetRemovableStorageObjectIds( 262 IPortableDevice* device, 263 PortableDeviceWatcherWin::StorageObjectIDs* storage_object_ids) { 264 DCHECK(device); 265 DCHECK(storage_object_ids); 266 base::win::ScopedComPtr<IPortableDeviceCapabilities> capabilities; 267 HRESULT hr = device->Capabilities(capabilities.Receive()); 268 if (FAILED(hr)) { 269 DPLOG(ERROR) << "Failed to get IPortableDeviceCapabilities interface"; 270 return false; 271 } 272 273 base::win::ScopedComPtr<IPortableDevicePropVariantCollection> storage_ids; 274 hr = capabilities->GetFunctionalObjects(WPD_FUNCTIONAL_CATEGORY_STORAGE, 275 storage_ids.Receive()); 276 if (FAILED(hr)) { 277 DPLOG(ERROR) << "Failed to get IPortableDevicePropVariantCollection"; 278 return false; 279 } 280 281 DWORD num_storage_obj_ids = 0; 282 hr = storage_ids->GetCount(&num_storage_obj_ids); 283 if (FAILED(hr)) 284 return false; 285 286 for (DWORD index = 0; index < num_storage_obj_ids; ++index) { 287 base::win::ScopedPropVariant object_id; 288 hr = storage_ids->GetAt(index, object_id.Receive()); 289 if (SUCCEEDED(hr) && object_id.get().vt == VT_LPWSTR && 290 object_id.get().pwszVal != NULL) { 291 storage_object_ids->push_back(object_id.get().pwszVal); 292 } 293 } 294 return true; 295 } 296 297 // Returns true if the portable device belongs to a mass storage class. 298 // |pnp_device_id| specifies the plug and play device id. 299 // |device_name| specifies the name of the device. 300 bool IsMassStoragePortableDevice(const base::string16& pnp_device_id, 301 const base::string16& device_name) { 302 // Based on testing, if the pnp device id starts with "\\?\wpdbusenumroot#", 303 // then the attached device belongs to a mass storage class. 304 if (StartsWith(pnp_device_id, L"\\\\?\\wpdbusenumroot#", false)) 305 return true; 306 307 // If the device is a volume mounted device, |device_name| will be 308 // the volume name. 309 return ((device_name.length() >= 2) && (device_name[1] == L':') && 310 (((device_name[0] >= L'A') && (device_name[0] <= L'Z')) || 311 ((device_name[0] >= L'a') && (device_name[0] <= L'z')))); 312 } 313 314 // Returns the name of the device specified by |pnp_device_id|. 315 base::string16 GetDeviceNameOnBlockingThread( 316 IPortableDeviceManager* portable_device_manager, 317 const base::string16& pnp_device_id) { 318 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 319 DCHECK(portable_device_manager); 320 base::string16 name; 321 GetFriendlyName(pnp_device_id, portable_device_manager, &name) || 322 GetDeviceDescription(pnp_device_id, portable_device_manager, &name) || 323 GetManufacturerName(pnp_device_id, portable_device_manager, &name); 324 return name; 325 } 326 327 // Access the device and gets the device storage details. On success, returns 328 // true and populates |storage_objects| with device storage details. 329 bool GetDeviceStorageObjectsOnBlockingThread( 330 const base::string16& pnp_device_id, 331 PortableDeviceWatcherWin::StorageObjects* storage_objects) { 332 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 333 DCHECK(storage_objects); 334 base::win::ScopedComPtr<IPortableDevice> device; 335 if (!SetUp(pnp_device_id, &device)) 336 return false; 337 338 base::string16 device_serial_num; 339 if (!GetObjectUniqueId(device.get(), WPD_DEVICE_OBJECT_ID, 340 &device_serial_num)) { 341 return false; 342 } 343 344 PortableDeviceWatcherWin::StorageObjectIDs storage_obj_ids; 345 if (!GetRemovableStorageObjectIds(device.get(), &storage_obj_ids)) 346 return false; 347 for (PortableDeviceWatcherWin::StorageObjectIDs::const_iterator id_iter = 348 storage_obj_ids.begin(); id_iter != storage_obj_ids.end(); ++id_iter) { 349 base::string16 storage_persistent_id; 350 if (!GetObjectUniqueId(device.get(), *id_iter, &storage_persistent_id)) 351 continue; 352 353 std::string device_storage_id; 354 if (ConstructDeviceStorageUniqueId(device_serial_num, storage_persistent_id, 355 &device_storage_id)) { 356 storage_objects->push_back(PortableDeviceWatcherWin::DeviceStorageObject( 357 *id_iter, device_storage_id)); 358 } 359 } 360 return true; 361 } 362 363 // Accesses the device and gets the device details (name, storage info, etc). 364 // On success returns true and fills in |device_details|. On failure, returns 365 // false. |pnp_device_id| specifies the plug and play device ID string. 366 bool GetDeviceInfoOnBlockingThread( 367 IPortableDeviceManager* portable_device_manager, 368 const base::string16& pnp_device_id, 369 PortableDeviceWatcherWin::DeviceDetails* device_details) { 370 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 371 DCHECK(portable_device_manager); 372 DCHECK(device_details); 373 DCHECK(!pnp_device_id.empty()); 374 device_details->name = GetDeviceNameOnBlockingThread(portable_device_manager, 375 pnp_device_id); 376 if (IsMassStoragePortableDevice(pnp_device_id, device_details->name)) 377 return false; 378 379 device_details->location = pnp_device_id; 380 PortableDeviceWatcherWin::StorageObjects storage_objects; 381 return GetDeviceStorageObjectsOnBlockingThread( 382 pnp_device_id, &device_details->storage_objects); 383 } 384 385 // Wrapper function to get an instance of portable device manager. On success, 386 // returns true and fills in |portable_device_mgr|. On failure, returns false. 387 bool GetPortableDeviceManager( 388 base::win::ScopedComPtr<IPortableDeviceManager>* portable_device_mgr) { 389 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 390 HRESULT hr = portable_device_mgr->CreateInstance( 391 __uuidof(PortableDeviceManager), NULL, CLSCTX_INPROC_SERVER); 392 if (SUCCEEDED(hr)) 393 return true; 394 395 // Either there is no portable device support (Windows XP with old versions of 396 // Media Player) or the thread does not have COM initialized. 397 DCHECK_NE(CO_E_NOTINITIALIZED, hr); 398 return false; 399 } 400 401 // Enumerates the attached portable devices. On success, returns true and fills 402 // in |devices| with the attached portable device details. On failure, returns 403 // false. 404 bool EnumerateAttachedDevicesOnBlockingThread( 405 PortableDeviceWatcherWin::Devices* devices) { 406 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 407 DCHECK(devices); 408 base::win::ScopedComPtr<IPortableDeviceManager> portable_device_mgr; 409 if (!GetPortableDeviceManager(&portable_device_mgr)) 410 return false; 411 412 // Get the total number of devices found on the system. 413 DWORD pnp_device_count = 0; 414 HRESULT hr = portable_device_mgr->GetDevices(NULL, &pnp_device_count); 415 if (FAILED(hr)) 416 return false; 417 418 scoped_ptr<base::char16*[]> pnp_device_ids( 419 new base::char16*[pnp_device_count]); 420 hr = portable_device_mgr->GetDevices(pnp_device_ids.get(), &pnp_device_count); 421 if (FAILED(hr)) 422 return false; 423 424 for (DWORD index = 0; index < pnp_device_count; ++index) { 425 PortableDeviceWatcherWin::DeviceDetails device_details; 426 if (GetDeviceInfoOnBlockingThread( 427 portable_device_mgr, pnp_device_ids[index], &device_details)) 428 devices->push_back(device_details); 429 CoTaskMemFree(pnp_device_ids[index]); 430 } 431 return !devices->empty(); 432 } 433 434 // Handles the device attach event message on a media task runner. 435 // |pnp_device_id| specifies the attached plug and play device ID string. On 436 // success, returns true and populates |device_details| with device information. 437 // On failure, returns false. 438 bool HandleDeviceAttachedEventOnBlockingThread( 439 const base::string16& pnp_device_id, 440 PortableDeviceWatcherWin::DeviceDetails* device_details) { 441 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 442 DCHECK(device_details); 443 base::win::ScopedComPtr<IPortableDeviceManager> portable_device_mgr; 444 if (!GetPortableDeviceManager(&portable_device_mgr)) 445 return false; 446 // Sometimes, portable device manager doesn't have the new device details. 447 // Refresh the manager device list to update its details. 448 portable_device_mgr->RefreshDeviceList(); 449 return GetDeviceInfoOnBlockingThread(portable_device_mgr, pnp_device_id, 450 device_details); 451 } 452 453 // Registers |hwnd| to receive portable device notification details. On success, 454 // returns the device notifications handle else returns NULL. 455 HDEVNOTIFY RegisterPortableDeviceNotification(HWND hwnd) { 456 GUID dev_interface_guid = GUID_NULL; 457 HRESULT hr = CLSIDFromString(kWPDDevInterfaceGUID, &dev_interface_guid); 458 if (FAILED(hr)) 459 return NULL; 460 DEV_BROADCAST_DEVICEINTERFACE db = { 461 sizeof(DEV_BROADCAST_DEVICEINTERFACE), 462 DBT_DEVTYP_DEVICEINTERFACE, 463 0, 464 dev_interface_guid 465 }; 466 return RegisterDeviceNotification(hwnd, &db, DEVICE_NOTIFY_WINDOW_HANDLE); 467 } 468 469 } // namespace 470 471 472 // PortableDeviceWatcherWin --------------------------------------------------- 473 474 PortableDeviceWatcherWin::DeviceStorageObject::DeviceStorageObject( 475 const base::string16& temporary_id, 476 const std::string& persistent_id) 477 : object_temporary_id(temporary_id), 478 object_persistent_id(persistent_id) { 479 } 480 481 PortableDeviceWatcherWin::PortableDeviceWatcherWin() 482 : notifications_(NULL), 483 storage_notifications_(NULL), 484 weak_ptr_factory_(this) { 485 } 486 487 PortableDeviceWatcherWin::~PortableDeviceWatcherWin() { 488 UnregisterDeviceNotification(notifications_); 489 } 490 491 void PortableDeviceWatcherWin::Init(HWND hwnd) { 492 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 493 notifications_ = RegisterPortableDeviceNotification(hwnd); 494 base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool(); 495 media_task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior( 496 pool->GetNamedSequenceToken(kMediaTaskRunnerName), 497 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); 498 EnumerateAttachedDevices(); 499 } 500 501 void PortableDeviceWatcherWin::OnWindowMessage(UINT event_type, LPARAM data) { 502 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 503 if (!IsPortableDeviceStructure(data)) 504 return; 505 506 base::string16 device_id = GetPnpDeviceId(data); 507 if (event_type == DBT_DEVICEARRIVAL) 508 HandleDeviceAttachEvent(device_id); 509 else if (event_type == DBT_DEVICEREMOVECOMPLETE) 510 HandleDeviceDetachEvent(device_id); 511 } 512 513 bool PortableDeviceWatcherWin::GetMTPStorageInfoFromDeviceId( 514 const std::string& storage_device_id, 515 base::string16* device_location, 516 base::string16* storage_object_id) const { 517 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 518 DCHECK(device_location); 519 DCHECK(storage_object_id); 520 MTPStorageMap::const_iterator storage_map_iter = 521 storage_map_.find(storage_device_id); 522 if (storage_map_iter == storage_map_.end()) 523 return false; 524 525 MTPDeviceMap::const_iterator device_iter = 526 device_map_.find(storage_map_iter->second.location()); 527 if (device_iter == device_map_.end()) 528 return false; 529 const StorageObjects& storage_objects = device_iter->second; 530 for (StorageObjects::const_iterator storage_object_iter = 531 storage_objects.begin(); storage_object_iter != storage_objects.end(); 532 ++storage_object_iter) { 533 if (storage_device_id == storage_object_iter->object_persistent_id) { 534 *device_location = storage_map_iter->second.location(); 535 *storage_object_id = storage_object_iter->object_temporary_id; 536 return true; 537 } 538 } 539 return false; 540 } 541 542 // static 543 base::string16 PortableDeviceWatcherWin::GetStoragePathFromStorageId( 544 const std::string& storage_unique_id) { 545 // Construct a dummy device path using the storage name. This is only used 546 // for registering the device media file system. 547 DCHECK(!storage_unique_id.empty()); 548 return base::UTF8ToUTF16("\\\\" + storage_unique_id); 549 } 550 551 void PortableDeviceWatcherWin::SetNotifications( 552 StorageMonitor::Receiver* notifications) { 553 storage_notifications_ = notifications; 554 } 555 556 void PortableDeviceWatcherWin::EjectDevice( 557 const std::string& device_id, 558 base::Callback<void(StorageMonitor::EjectStatus)> callback) { 559 // MTP devices on Windows don't have a detach API needed -- signal 560 // the object as if the device is gone and tell the caller it is OK 561 // to remove. 562 base::string16 device_location; // The device_map_ key. 563 base::string16 storage_object_id; 564 if (!GetMTPStorageInfoFromDeviceId(device_id, 565 &device_location, &storage_object_id)) { 566 callback.Run(StorageMonitor::EJECT_NO_SUCH_DEVICE); 567 return; 568 } 569 HandleDeviceDetachEvent(device_location); 570 571 callback.Run(StorageMonitor::EJECT_OK); 572 } 573 574 void PortableDeviceWatcherWin::EnumerateAttachedDevices() { 575 DCHECK(media_task_runner_.get()); 576 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 577 Devices* devices = new Devices; 578 base::PostTaskAndReplyWithResult( 579 media_task_runner_, 580 FROM_HERE, 581 base::Bind(&EnumerateAttachedDevicesOnBlockingThread, devices), 582 base::Bind(&PortableDeviceWatcherWin::OnDidEnumerateAttachedDevices, 583 weak_ptr_factory_.GetWeakPtr(), base::Owned(devices))); 584 } 585 586 void PortableDeviceWatcherWin::OnDidEnumerateAttachedDevices( 587 const Devices* devices, const bool result) { 588 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 589 DCHECK(devices); 590 if (!result) 591 return; 592 for (Devices::const_iterator device_iter = devices->begin(); 593 device_iter != devices->end(); ++device_iter) { 594 OnDidHandleDeviceAttachEvent(&(*device_iter), result); 595 } 596 } 597 598 void PortableDeviceWatcherWin::HandleDeviceAttachEvent( 599 const base::string16& pnp_device_id) { 600 DCHECK(media_task_runner_.get()); 601 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 602 DeviceDetails* device_details = new DeviceDetails; 603 base::PostTaskAndReplyWithResult( 604 media_task_runner_, 605 FROM_HERE, 606 base::Bind(&HandleDeviceAttachedEventOnBlockingThread, pnp_device_id, 607 device_details), 608 base::Bind(&PortableDeviceWatcherWin::OnDidHandleDeviceAttachEvent, 609 weak_ptr_factory_.GetWeakPtr(), base::Owned(device_details))); 610 } 611 612 void PortableDeviceWatcherWin::OnDidHandleDeviceAttachEvent( 613 const DeviceDetails* device_details, const bool result) { 614 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 615 DCHECK(device_details); 616 if (!result) 617 return; 618 619 const StorageObjects& storage_objects = device_details->storage_objects; 620 const base::string16& name = device_details->name; 621 const base::string16& location = device_details->location; 622 DCHECK(!ContainsKey(device_map_, location)); 623 for (StorageObjects::const_iterator storage_iter = storage_objects.begin(); 624 storage_iter != storage_objects.end(); ++storage_iter) { 625 const std::string& storage_id = storage_iter->object_persistent_id; 626 DCHECK(!ContainsKey(storage_map_, storage_id)); 627 628 // Keep track of storage id and storage name to see how often we receive 629 // empty values. 630 MediaStorageUtil::RecordDeviceInfoHistogram(false, storage_id, name); 631 if (storage_id.empty() || name.empty()) 632 return; 633 634 // Device can have several data partitions. Therefore, add the 635 // partition identifier to the model name. E.g.: "Nexus 7 (s10001)" 636 base::string16 model_name(name + L" (" + 637 storage_iter->object_temporary_id + L')'); 638 StorageInfo info(storage_id, location, base::string16(), base::string16(), 639 model_name, 0); 640 storage_map_[storage_id] = info; 641 if (storage_notifications_) { 642 info.set_location(GetStoragePathFromStorageId(storage_id)); 643 storage_notifications_->ProcessAttach(info); 644 } 645 } 646 device_map_[location] = storage_objects; 647 } 648 649 void PortableDeviceWatcherWin::HandleDeviceDetachEvent( 650 const base::string16& pnp_device_id) { 651 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 652 MTPDeviceMap::iterator device_iter = device_map_.find(pnp_device_id); 653 if (device_iter == device_map_.end()) 654 return; 655 656 const StorageObjects& storage_objects = device_iter->second; 657 for (StorageObjects::const_iterator storage_object_iter = 658 storage_objects.begin(); storage_object_iter != storage_objects.end(); 659 ++storage_object_iter) { 660 std::string storage_id = storage_object_iter->object_persistent_id; 661 MTPStorageMap::iterator storage_map_iter = storage_map_.find(storage_id); 662 DCHECK(storage_map_iter != storage_map_.end()); 663 if (storage_notifications_) { 664 storage_notifications_->ProcessDetach( 665 storage_map_iter->second.device_id()); 666 } 667 storage_map_.erase(storage_map_iter); 668 } 669 device_map_.erase(device_iter); 670 } 671 672 } // namespace storage_monitor 673