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