Home | History | Annotate | Download | only in storage_monitor
      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