text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  //
  // Any tasks that communicates with the portable device may take >100ms to
  // complete. Those tasks should be run on an blocking thread instead of the
  // UI thread.
  
  #include "chrome/browser/storage_monitor/portable_device_watcher_win.h"
  
  #include <dbt.h>
  #include <portabledevice.h>
  
  #include "base/files/file_path.h"
  #include "base/logging.h"
  #include "base/stl_util.h"
  #include "base/string_util.h"
  #include "base/threading/sequenced_worker_pool.h"
  #include "base/utf_string_conversions.h"
  #include "base/win/scoped_co_mem.h"
  #include "base/win/scoped_comptr.h"
  #include "base/win/scoped_propvariant.h"
  #include "chrome/browser/storage_monitor/media_storage_util.h"
  #include "chrome/browser/storage_monitor/removable_device_constants.h"
  #include "content/public/browser/browser_thread.h"
  
  namespace chrome {
  
  namespace {
  
  // Name of the client application that communicates with the MTP device.
  const char16 kClientName[] = L"Chromium";
  
  // Name of the sequenced task runner.
  const char kMediaTaskRunnerName[] = "media-task-runner";
  
  // Returns true if |data| represents a class of portable devices.
  bool IsPortableDeviceStructure(LPARAM data) {
    DEV_BROADCAST_HDR* broadcast_hdr =
        reinterpret_cast<DEV_BROADCAST_HDR*>(data);
    if (!broadcast_hdr ||
        (broadcast_hdr->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)) {
      return false;
    }
  
    GUID guidDevInterface = GUID_NULL;
    if (FAILED(CLSIDFromString(kWPDDevInterfaceGUID, &guidDevInterface)))
      return false;
    DEV_BROADCAST_DEVICEINTERFACE* dev_interface =
        reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(data);
    return (IsEqualGUID(dev_interface->dbcc_classguid, guidDevInterface) != 0);
  }
  
  // Returns the portable device plug and play device ID string.
  string16 GetPnpDeviceId(LPARAM data) {
    DEV_BROADCAST_DEVICEINTERFACE* dev_interface =
        reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(data);
    if (!dev_interface)
      return string16();
    string16 device_id(dev_interface->dbcc_name);
    DCHECK(IsStringASCII(device_id));
    return StringToLowerASCII(device_id);
  }
  
  // Gets the friendly name of the device specified by the |pnp_device_id|. On
  // success, returns true and fills in |name|.
  bool GetFriendlyName(const string16& pnp_device_id,
                       IPortableDeviceManager* device_manager,
                       string16* name) {
    DCHECK(device_manager);
    DCHECK(name);
    DWORD name_len = 0;
    HRESULT hr = device_manager->GetDeviceFriendlyName(pnp_device_id.c_str(),
                                                       NULL, &name_len);
    if (FAILED(hr))
      return false;
  
    hr = device_manager->GetDeviceFriendlyName(
        pnp_device_id.c_str(), WriteInto(name, name_len), &name_len);
    return (SUCCEEDED(hr) && !name->empty());
  }
  
  // Gets the manufacturer name of the device specified by the |pnp_device_id|.
  // On success, returns true and fills in |name|.
  bool GetManufacturerName(const string16& pnp_device_id,
                           IPortableDeviceManager* device_manager,
                           string16* name) {
    DCHECK(device_manager);
    DCHECK(name);
    DWORD name_len = 0;
    HRESULT hr = device_manager->GetDeviceManufacturer(pnp_device_id.c_str(),
                                                       NULL, &name_len);
    if (FAILED(hr))
      return false;
  
    hr = device_manager->GetDeviceManufacturer(pnp_device_id.c_str(),
                                               WriteInto(name, name_len),
                                               &name_len);
    return (SUCCEEDED(hr) && !name->empty());
  }
  
  // Gets the description of the device specified by the |pnp_device_id|. On
  // success, returns true and fills in |description|.
  bool GetDeviceDescription(const string16& pnp_device_id,
                            IPortableDeviceManager* device_manager,
                            string16* description) {
    DCHECK(device_manager);
    DCHECK(description);
    DWORD desc_len = 0;
    HRESULT hr = device_manager->GetDeviceDescription(pnp_device_id.c_str(), NULL,
                                                      &desc_len);
    if (FAILED(hr))
      return false;
  
    hr = device_manager->GetDeviceDescription(pnp_device_id.c_str(),
                                              WriteInto(description, desc_len),
                                              &desc_len);
    return (SUCCEEDED(hr) && !description->empty());
  }
  
  // On success, returns true and updates |client_info| with a reference to an
  // IPortableDeviceValues interface that holds information about the
  // application that communicates with the device.
  bool GetClientInformation(
      base::win::ScopedComPtr<IPortableDeviceValues>* client_info) {
    HRESULT hr = client_info->CreateInstance(__uuidof(PortableDeviceValues),
                                             NULL, CLSCTX_INPROC_SERVER);
    if (FAILED(hr)) {
      DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues";
      return false;
    }
  
    // Attempt to set client details.
    (*client_info)->SetStringValue(WPD_CLIENT_NAME, kClientName);
    (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0);
    (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
    (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0);
    (*client_info)->SetUnsignedIntegerValue(
        WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION);
    (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS,
                                            GENERIC_READ);
    return true;
  }
  
  // Opens the device for communication. |pnp_device_id| specifies the plug and
  // play device ID string. On success, returns true and updates |device| with a
  // reference to the portable device interface.
  bool SetUp(const string16& pnp_device_id,
             base::win::ScopedComPtr<IPortableDevice>* device) {
    base::win::ScopedComPtr<IPortableDeviceValues> client_info;
    if (!GetClientInformation(&client_info))
      return false;
  
    HRESULT hr = device->CreateInstance(__uuidof(PortableDevice), NULL,
                                        CLSCTX_INPROC_SERVER);
    if (FAILED(hr)) {
      DPLOG(ERROR) << "Failed to create an instance of IPortableDevice";
      return false;
    }
  
    hr = (*device)->Open(pnp_device_id.c_str(), client_info.get());
    if (SUCCEEDED(hr))
      return true;
  
    if (hr == E_ACCESSDENIED)
      DPLOG(ERROR) << "Access denied to open the device";
    return false;
  }
  
  // Returns the unique id property key of the object specified by the
  // |object_id|.
  REFPROPERTYKEY GetUniqueIdPropertyKey(const string16& object_id) {
    return (object_id == WPD_DEVICE_OBJECT_ID) ?
        WPD_DEVICE_SERIAL_NUMBER : WPD_OBJECT_PERSISTENT_UNIQUE_ID;
  }
  
  // On success, returns true and populates |properties_to_read| with the
  // property key of the object specified by the |object_id|.
  bool PopulatePropertyKeyCollection(
      const string16& object_id,
      base::win::ScopedComPtr<IPortableDeviceKeyCollection>* properties_to_read) {
    HRESULT hr = properties_to_read->CreateInstance(
        __uuidof(PortableDeviceKeyCollection), NULL, CLSCTX_INPROC_SERVER);
    if (FAILED(hr)) {
      DPLOG(ERROR) << "Failed to create IPortableDeviceKeyCollection instance";
      return false;
    }
    REFPROPERTYKEY key = GetUniqueIdPropertyKey(object_id);
    hr = (*properties_to_read)->Add(key);
    return SUCCEEDED(hr);
  }
  
  // Wrapper function to get content property string value.
  bool GetStringPropertyValue(IPortableDeviceValues* properties_values,
                              REFPROPERTYKEY key,
                              string16* value) {
    DCHECK(properties_values);
    DCHECK(value);
    base::win::ScopedCoMem<char16> buffer;
    HRESULT hr = properties_values->GetStringValue(key, &buffer);
    if (FAILED(hr))
      return false;
    *value = static_cast<const char16*>(buffer);
    return true;
  }
  
  // Constructs a unique identifier for the object specified by the |object_id|.
  // On success, returns true and fills in |unique_id|.
  bool GetObjectUniqueId(IPortableDevice* device,
                         const string16& object_id,
                         string16* unique_id) {
    DCHECK(device);
    DCHECK(unique_id);
    base::win::ScopedComPtr<IPortableDeviceContent> content;
    HRESULT hr = device->Content(content.Receive());
    if (FAILED(hr)) {
      DPLOG(ERROR) << "Failed to get IPortableDeviceContent interface";
      return false;
    }
  
    base::win::ScopedComPtr<IPortableDeviceProperties> properties;
    hr = content->Properties(properties.Receive());
    if (FAILED(hr)) {
      DPLOG(ERROR) << "Failed to get IPortableDeviceProperties interface";
      return false;
    }
  
    base::win::ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
    if (!PopulatePropertyKeyCollection(object_id, &properties_to_read))
      return false;
  
    base::win::ScopedComPtr<IPortableDeviceValues> properties_values;
    if (FAILED(properties->GetValues(object_id.c_str(),
                                     properties_to_read.get(),
                                     properties_values.Receive()))) {
      return false;
    }
  
    REFPROPERTYKEY key = GetUniqueIdPropertyKey(object_id);
    return GetStringPropertyValue(properties_values.get(), key, unique_id);
  }
  
  // Constructs the device storage unique identifier using |device_serial_num| and
  // |storage_id|. On success, returns true and fills in |device_storage_id|.
  bool ConstructDeviceStorageUniqueId(const string16& device_serial_num,
                                      const string16& storage_id,
                                      std::string* device_storage_id) {
    if (device_serial_num.empty() && storage_id.empty())
      return false;
  
    DCHECK(device_storage_id);
    *device_storage_id = MediaStorageUtil::MakeDeviceId(
         MediaStorageUtil::MTP_OR_PTP,
         UTF16ToUTF8(storage_id + L':' + device_serial_num));
    return true;
  }
  
  // Gets a list of removable storage object identifiers present in |device|.
  // On success, returns true and fills in |storage_object_ids|.
  bool GetRemovableStorageObjectIds(
      IPortableDevice* device,
      PortableDeviceWatcherWin::StorageObjectIDs* storage_object_ids) {
    DCHECK(device);
    DCHECK(storage_object_ids);
    base::win::ScopedComPtr<IPortableDeviceCapabilities> capabilities;
    HRESULT hr = device->Capabilities(capabilities.Receive());
    if (FAILED(hr)) {
      DPLOG(ERROR) << "Failed to get IPortableDeviceCapabilities interface";
      return false;
    }
  
    base::win::ScopedComPtr<IPortableDevicePropVariantCollection> storage_ids;
    hr = capabilities->GetFunctionalObjects(WPD_FUNCTIONAL_CATEGORY_STORAGE,
                                            storage_ids.Receive());
    if (FAILED(hr)) {
      DPLOG(ERROR) << "Failed to get IPortableDevicePropVariantCollection";
      return false;
    }
  
    DWORD num_storage_obj_ids = 0;
    hr = storage_ids->GetCount(&num_storage_obj_ids);
    if (FAILED(hr))
      return false;
  
    for (DWORD index = 0; index < num_storage_obj_ids; ++index) {
      base::win::ScopedPropVariant object_id;
      hr = storage_ids->GetAt(index, object_id.Receive());
      if (SUCCEEDED(hr) && object_id.get().vt == VT_LPWSTR &&
          object_id.get().pwszVal != NULL) {
        storage_object_ids->push_back(object_id.get().pwszVal);
      }
    }
    return true;
  }
  
  // Returns true if the portable device belongs to a mass storage class.
  // |pnp_device_id| specifies the plug and play device id.
  // |device_name| specifies the name of the device.
  bool IsMassStoragePortableDevice(const string16& pnp_device_id,
                                   const string16& device_name) {
    // Based on testing, if the pnp device id starts with "\\?\wpdbusenumroot#",
    // then the attached device belongs to a mass storage class.
    if (StartsWith(pnp_device_id, L"\\\\?\\wpdbusenumroot#", false))
      return true;
  
    // If the device is a volume mounted device, |device_name| will be
    // the volume name.
    return ((device_name.length() >= 2) && (device_name[1] == L':') &&
        (((device_name[0] >= L'A') && (device_name[0] <= L'Z')) ||
            ((device_name[0] >= L'a') && (device_name[0] <= L'z'))));
  }
  
  // Returns the name of the device specified by |pnp_device_id|.
  string16 GetDeviceNameOnBlockingThread(
      IPortableDeviceManager* portable_device_manager,
      const string16& pnp_device_id) {
    DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    DCHECK(portable_device_manager);
    string16 name;
    GetFriendlyName(pnp_device_id, portable_device_manager, &name) ||
        GetDeviceDescription(pnp_device_id, portable_device_manager, &name) ||
        GetManufacturerName(pnp_device_id, portable_device_manager, &name);
    return name;
  }
  
  // Access the device and gets the device storage details. On success, returns
  // true and populates |storage_objects| with device storage details.
  bool GetDeviceStorageObjectsOnBlockingThread(
      const string16& pnp_device_id,
      PortableDeviceWatcherWin::StorageObjects* storage_objects) {
    DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    DCHECK(storage_objects);
    base::win::ScopedComPtr<IPortableDevice> device;
    if (!SetUp(pnp_device_id, &device))
      return false;
  
    string16 device_serial_num;
    if (!GetObjectUniqueId(device.get(), WPD_DEVICE_OBJECT_ID,
                           &device_serial_num)) {
      return false;
    }
  
    PortableDeviceWatcherWin::StorageObjectIDs storage_obj_ids;
    if (!GetRemovableStorageObjectIds(device.get(), &storage_obj_ids))
      return false;
    for (PortableDeviceWatcherWin::StorageObjectIDs::const_iterator id_iter =
         storage_obj_ids.begin(); id_iter != storage_obj_ids.end(); ++id_iter) {
      string16 storage_persistent_id;
      if (!GetObjectUniqueId(device.get(), *id_iter, &storage_persistent_id))
        continue;
  
      std::string device_storage_id;
      if (ConstructDeviceStorageUniqueId(device_serial_num, storage_persistent_id,
                                         &device_storage_id)) {
        storage_objects->push_back(PortableDeviceWatcherWin::DeviceStorageObject(
            *id_iter, device_storage_id));
      }
    }
    return true;
  }
  
  // Accesses the device and gets the device details (name, storage info, etc).
  // On success returns true and fills in |device_details|. On failure, returns
  // false. |pnp_device_id| specifies the plug and play device ID string.
  bool GetDeviceInfoOnBlockingThread(
      IPortableDeviceManager* portable_device_manager,
      const string16& pnp_device_id,
      PortableDeviceWatcherWin::DeviceDetails* device_details) {
    DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    DCHECK(portable_device_manager);
    DCHECK(device_details);
    DCHECK(!pnp_device_id.empty());
    device_details->name = GetDeviceNameOnBlockingThread(portable_device_manager,
                                                         pnp_device_id);
    if (IsMassStoragePortableDevice(pnp_device_id, device_details->name))
      return false;
  
    device_details->location = pnp_device_id;
    PortableDeviceWatcherWin::StorageObjects storage_objects;
    return GetDeviceStorageObjectsOnBlockingThread(
        pnp_device_id, &device_details->storage_objects);
  }
  
  // Wrapper function to get an instance of portable device manager. On success,
  // returns true and fills in |portable_device_mgr|. On failure, returns false.
  bool GetPortableDeviceManager(
    base::win::ScopedComPtr<IPortableDeviceManager>* portable_device_mgr) {
    DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    HRESULT hr = portable_device_mgr->CreateInstance(
        __uuidof(PortableDeviceManager), NULL, CLSCTX_INPROC_SERVER);
    if (SUCCEEDED(hr))
      return true;
  
    // Either there is no portable device support (Windows XP with old versions of
    // Media Player) or the thread does not have COM initialized.
    DCHECK_NE(CO_E_NOTINITIALIZED, hr);
    return false;
  }
  
  // Enumerates the attached portable devices. On success, returns true and fills
  // in |devices| with the attached portable device details. On failure, returns
  // false.
  bool EnumerateAttachedDevicesOnBlockingThread(
      PortableDeviceWatcherWin::Devices* devices) {
    DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    DCHECK(devices);
    base::win::ScopedComPtr<IPortableDeviceManager> portable_device_mgr;
    if (!GetPortableDeviceManager(&portable_device_mgr))
      return false;
  
    // Get the total number of devices found on the system.
    DWORD pnp_device_count = 0;
    HRESULT hr = portable_device_mgr->GetDevices(NULL, &pnp_device_count);
    if (FAILED(hr))
      return false;
  
    scoped_ptr<char16*[]> pnp_device_ids(new char16*[pnp_device_count]);
    hr = portable_device_mgr->GetDevices(pnp_device_ids.get(), &pnp_device_count);
    if (FAILED(hr))
      return false;
  
    for (DWORD index = 0; index < pnp_device_count; ++index) {
      PortableDeviceWatcherWin::DeviceDetails device_details;
      if (GetDeviceInfoOnBlockingThread(
          portable_device_mgr, pnp_device_ids[index], &device_details))
        devices->push_back(device_details);
      CoTaskMemFree(pnp_device_ids[index]);
    }
    return !devices->empty();
  }
  
  // Handles the device attach event message on a media task runner.
  // |pnp_device_id| specifies the attached plug and play device ID string. On
  // success, returns true and populates |device_details| with device information.
  // On failure, returns false.
  bool HandleDeviceAttachedEventOnBlockingThread(
      const string16& pnp_device_id,
      PortableDeviceWatcherWin::DeviceDetails* device_details) {
    DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    DCHECK(device_details);
    base::win::ScopedComPtr<IPortableDeviceManager> portable_device_mgr;
    if (!GetPortableDeviceManager(&portable_device_mgr))
      return false;
    // Sometimes, portable device manager doesn't have the new device details.
    // Refresh the manager device list to update its details.
    portable_device_mgr->RefreshDeviceList();
    return GetDeviceInfoOnBlockingThread(portable_device_mgr, pnp_device_id,
                                         device_details);
  }
  
  // Registers |hwnd| to receive portable device notification details. On success,
  // returns the device notifications handle else returns NULL.
  HDEVNOTIFY RegisterPortableDeviceNotification(HWND hwnd) {
    GUID dev_interface_guid = GUID_NULL;
    HRESULT hr = CLSIDFromString(kWPDDevInterfaceGUID, &dev_interface_guid);
    if (FAILED(hr))
      return NULL;
    DEV_BROADCAST_DEVICEINTERFACE db = {
        sizeof(DEV_BROADCAST_DEVICEINTERFACE),
        DBT_DEVTYP_DEVICEINTERFACE,
        0,
        dev_interface_guid
    };
    return RegisterDeviceNotification(hwnd, &db, DEVICE_NOTIFY_WINDOW_HANDLE);
  }
  
  }  // namespace
  
  
  // PortableDeviceWatcherWin ---------------------------------------------------
  
  PortableDeviceWatcherWin::DeviceStorageObject::DeviceStorageObject(
      const string16& temporary_id,
      const std::string& persistent_id)
      : object_temporary_id(temporary_id),
        object_persistent_id(persistent_id) {
  }
  
  PortableDeviceWatcherWin::PortableDeviceWatcherWin()
      : notifications_(NULL),
        storage_notifications_(NULL),
        weak_ptr_factory_(this) {
  }
  
  PortableDeviceWatcherWin::~PortableDeviceWatcherWin() {
    UnregisterDeviceNotification(notifications_);
  }
  
  void PortableDeviceWatcherWin::Init(HWND hwnd) {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    notifications_ = RegisterPortableDeviceNotification(hwnd);
    base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool();
    media_task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
        pool->GetNamedSequenceToken(kMediaTaskRunnerName),
        base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
    EnumerateAttachedDevices();
  }
  
  void PortableDeviceWatcherWin::OnWindowMessage(UINT event_type, LPARAM data) {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    if (!IsPortableDeviceStructure(data))
      return;
  
    string16 device_id = GetPnpDeviceId(data);
    if (event_type == DBT_DEVICEARRIVAL)
      HandleDeviceAttachEvent(device_id);
    else if (event_type == DBT_DEVICEREMOVECOMPLETE)
      HandleDeviceDetachEvent(device_id);
  }
  
  bool PortableDeviceWatcherWin::GetMTPStorageInfoFromDeviceId(
      const std::string& storage_device_id,
      string16* device_location,
      string16* storage_object_id) const {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    DCHECK(device_location);
    DCHECK(storage_object_id);
    MTPStorageMap::const_iterator storage_map_iter =
        storage_map_.find(storage_device_id);
    if (storage_map_iter == storage_map_.end())
      return false;
  
    MTPDeviceMap::const_iterator device_iter =
        device_map_.find(storage_map_iter->second.location);
    if (device_iter == device_map_.end())
      return false;
    const StorageObjects& storage_objects = device_iter->second;
    for (StorageObjects::const_iterator storage_object_iter =
         storage_objects.begin(); storage_object_iter != storage_objects.end();
         ++storage_object_iter) {
      if (storage_device_id == storage_object_iter->object_persistent_id) {
        *device_location = storage_map_iter->second.location;
        *storage_object_id = storage_object_iter->object_temporary_id;
        return true;
      }
    }
    return false;
  }
  
  // static
  string16 PortableDeviceWatcherWin::GetStoragePathFromStorageId(
      const std::string& storage_unique_id) {
    // Construct a dummy device path using the storage name. This is only used
    // for registering the device media file system.
    DCHECK(!storage_unique_id.empty());
    return UTF8ToUTF16("\\\\" + storage_unique_id);
  }
  
  void PortableDeviceWatcherWin::SetNotifications(
      StorageMonitor::Receiver* notifications) {
    storage_notifications_ = notifications;
  }
  
  void PortableDeviceWatcherWin::EjectDevice(
      const std::string& device_id,
      base::Callback<void(StorageMonitor::EjectStatus)> callback) {
    callback.Run(chrome::StorageMonitor::EJECT_FAILURE);
  }
  
  void PortableDeviceWatcherWin::EnumerateAttachedDevices() {
    DCHECK(media_task_runner_.get());
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    Devices* devices = new Devices;
    base::PostTaskAndReplyWithResult(
        media_task_runner_,
        FROM_HERE,
        base::Bind(&EnumerateAttachedDevicesOnBlockingThread, devices),
        base::Bind(&PortableDeviceWatcherWin::OnDidEnumerateAttachedDevices,
                   weak_ptr_factory_.GetWeakPtr(), base::Owned(devices)));
  }
  
  void PortableDeviceWatcherWin::OnDidEnumerateAttachedDevices(
      const Devices* devices, const bool result) {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    DCHECK(devices);
    if (!result)
      return;
    for (Devices::const_iterator device_iter = devices->begin();
         device_iter != devices->end(); ++device_iter) {
      OnDidHandleDeviceAttachEvent(&(*device_iter), result);
    }
  }
  
  void PortableDeviceWatcherWin::HandleDeviceAttachEvent(
      const string16& pnp_device_id) {
    DCHECK(media_task_runner_.get());
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    DeviceDetails* device_details = new DeviceDetails;
    base::PostTaskAndReplyWithResult(
        media_task_runner_,
        FROM_HERE,
        base::Bind(&HandleDeviceAttachedEventOnBlockingThread, pnp_device_id,
                   device_details),
        base::Bind(&PortableDeviceWatcherWin::OnDidHandleDeviceAttachEvent,
                   weak_ptr_factory_.GetWeakPtr(), base::Owned(device_details)));
  }
  
  void PortableDeviceWatcherWin::OnDidHandleDeviceAttachEvent(
      const DeviceDetails* device_details, const bool result) {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    DCHECK(device_details);
    if (!result)
      return;
  
    const StorageObjects& storage_objects = device_details->storage_objects;
    const string16& name = device_details->name;
    const string16& location = device_details->location;
    DCHECK(!ContainsKey(device_map_, location));
    for (StorageObjects::const_iterator storage_iter = storage_objects.begin();
         storage_iter != storage_objects.end(); ++storage_iter) {
      const std::string& storage_id = storage_iter->object_persistent_id;
      DCHECK(!ContainsKey(storage_map_, storage_id));
  
      // Keep track of storage id and storage name to see how often we receive
      // empty values.
      MediaStorageUtil::RecordDeviceInfoHistogram(false, storage_id, name);
      if (storage_id.empty() || name.empty())
        return;
  
      // Device can have several data partitions. Therefore, add the
      // partition identifier to the storage name. E.g.: "Nexus 7 (s10001)"
      string16 storage_name(name + L" (" + storage_iter->object_temporary_id +
          L')');
      StorageInfo info(storage_id, storage_name, location,
                       string16(), string16(), string16(), 0);
      storage_map_[storage_id] = info;
      if (storage_notifications_) {
        info.location = GetStoragePathFromStorageId(storage_id);
        storage_notifications_->ProcessAttach(info);
      }
    }
    device_map_[location] = storage_objects;
  }
  
  void PortableDeviceWatcherWin::HandleDeviceDetachEvent(
      const string16& pnp_device_id) {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    MTPDeviceMap::iterator device_iter = device_map_.find(pnp_device_id);
    if (device_iter == device_map_.end())
      return;
  
    const StorageObjects& storage_objects = device_iter->second;
    for (StorageObjects::const_iterator storage_object_iter =
         storage_objects.begin(); storage_object_iter != storage_objects.end();
         ++storage_object_iter) {
      std::string storage_id = storage_object_iter->object_persistent_id;
      MTPStorageMap::iterator storage_map_iter = storage_map_.find(storage_id);
      DCHECK(storage_map_iter != storage_map_.end());
      if (storage_notifications_)
        storage_notifications_->ProcessDetach(storage_map_iter->second.device_id);
      storage_map_.erase(storage_map_iter);
    }
    device_map_.erase(device_iter);
  }
  
  }  // namespace chrome
  