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
28namespace storage_monitor {
29
30namespace {
31
32// Name of the client application that communicates with the MTP device.
33const base::char16 kClientName[] = L"Chromium";
34
35// Name of the sequenced task runner.
36const char kMediaTaskRunnerName[] = "media-task-runner";
37
38// Returns true if |data| represents a class of portable devices.
39bool 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.
56base::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 base::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|.
68bool 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|.
86bool 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|.
105bool 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.
125bool 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.
149bool 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|.
173REFPROPERTYKEY 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|.
180bool 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.
195bool 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|.
210bool 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|.
246bool 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|.
261bool 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.
300bool 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|.
315base::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.
329bool 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.
366bool 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.
387bool 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.
404bool 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.
438bool 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.
455HDEVNOTIFY 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
474PortableDeviceWatcherWin::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
481PortableDeviceWatcherWin::PortableDeviceWatcherWin()
482    : notifications_(NULL),
483      storage_notifications_(NULL),
484      weak_ptr_factory_(this) {
485}
486
487PortableDeviceWatcherWin::~PortableDeviceWatcherWin() {
488  UnregisterDeviceNotification(notifications_);
489}
490
491void 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
501void 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
513bool 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
543base::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
551void PortableDeviceWatcherWin::SetNotifications(
552    StorageMonitor::Receiver* notifications) {
553  storage_notifications_ = notifications;
554}
555
556void 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
574void 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
586void 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
598void 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
612void 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
649void 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