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