This source file includes following definitions.
- IsPortableDeviceStructure
- GetPnpDeviceId
- GetFriendlyName
- GetManufacturerName
- GetDeviceDescription
- GetClientInformation
- SetUp
- GetUniqueIdPropertyKey
- PopulatePropertyKeyCollection
- GetStringPropertyValue
- GetObjectUniqueId
- ConstructDeviceStorageUniqueId
- GetRemovableStorageObjectIds
- IsMassStoragePortableDevice
- GetDeviceNameOnBlockingThread
- GetDeviceStorageObjectsOnBlockingThread
- GetDeviceInfoOnBlockingThread
- GetPortableDeviceManager
- EnumerateAttachedDevicesOnBlockingThread
- HandleDeviceAttachedEventOnBlockingThread
- RegisterPortableDeviceNotification
- object_persistent_id
- weak_ptr_factory_
- Init
- OnWindowMessage
- GetMTPStorageInfoFromDeviceId
- GetStoragePathFromStorageId
- SetNotifications
- EjectDevice
- EnumerateAttachedDevices
- OnDidEnumerateAttachedDevices
- HandleDeviceAttachEvent
- OnDidHandleDeviceAttachEvent
- HandleDeviceDetachEvent
#include "components/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/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_propvariant.h"
#include "components/storage_monitor/media_storage_util.h"
#include "components/storage_monitor/removable_device_constants.h"
#include "components/storage_monitor/storage_info.h"
#include "content/public/browser/browser_thread.h"
namespace storage_monitor {
namespace {
const base::char16 kClientName[] = L"Chromium";
const char kMediaTaskRunnerName[] = "media-task-runner";
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);
}
base::string16 GetPnpDeviceId(LPARAM data) {
DEV_BROADCAST_DEVICEINTERFACE* dev_interface =
reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(data);
if (!dev_interface)
return base::string16();
base::string16 device_id(dev_interface->dbcc_name);
DCHECK(IsStringASCII(device_id));
return StringToLowerASCII(device_id);
}
bool GetFriendlyName(const base::string16& pnp_device_id,
IPortableDeviceManager* device_manager,
base::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());
}
bool GetManufacturerName(const base::string16& pnp_device_id,
IPortableDeviceManager* device_manager,
base::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());
}
bool GetDeviceDescription(const base::string16& pnp_device_id,
IPortableDeviceManager* device_manager,
base::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());
}
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;
}
(*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;
}
bool SetUp(const base::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;
}
REFPROPERTYKEY GetUniqueIdPropertyKey(const base::string16& object_id) {
return (object_id == WPD_DEVICE_OBJECT_ID) ?
WPD_DEVICE_SERIAL_NUMBER : WPD_OBJECT_PERSISTENT_UNIQUE_ID;
}
bool PopulatePropertyKeyCollection(
const base::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);
}
bool GetStringPropertyValue(IPortableDeviceValues* properties_values,
REFPROPERTYKEY key,
base::string16* value) {
DCHECK(properties_values);
DCHECK(value);
base::win::ScopedCoMem<base::char16> buffer;
HRESULT hr = properties_values->GetStringValue(key, &buffer);
if (FAILED(hr))
return false;
*value = static_cast<const base::char16*>(buffer);
return true;
}
bool GetObjectUniqueId(IPortableDevice* device,
const base::string16& object_id,
base::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);
}
bool ConstructDeviceStorageUniqueId(const base::string16& device_serial_num,
const base::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 = StorageInfo::MakeDeviceId(
StorageInfo::MTP_OR_PTP,
base::UTF16ToUTF8(storage_id + L':' + device_serial_num));
return true;
}
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;
}
bool IsMassStoragePortableDevice(const base::string16& pnp_device_id,
const base::string16& device_name) {
if (StartsWith(pnp_device_id, L"\\\\?\\wpdbusenumroot#", false))
return true;
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'))));
}
base::string16 GetDeviceNameOnBlockingThread(
IPortableDeviceManager* portable_device_manager,
const base::string16& pnp_device_id) {
DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
DCHECK(portable_device_manager);
base::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;
}
bool GetDeviceStorageObjectsOnBlockingThread(
const base::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;
base::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) {
base::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;
}
bool GetDeviceInfoOnBlockingThread(
IPortableDeviceManager* portable_device_manager,
const base::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);
}
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;
DCHECK_NE(CO_E_NOTINITIALIZED, hr);
return 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;
DWORD pnp_device_count = 0;
HRESULT hr = portable_device_mgr->GetDevices(NULL, &pnp_device_count);
if (FAILED(hr))
return false;
scoped_ptr<base::char16*[]> pnp_device_ids(
new base::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();
}
bool HandleDeviceAttachedEventOnBlockingThread(
const base::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;
portable_device_mgr->RefreshDeviceList();
return GetDeviceInfoOnBlockingThread(portable_device_mgr, pnp_device_id,
device_details);
}
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);
}
}
PortableDeviceWatcherWin::DeviceStorageObject::DeviceStorageObject(
const base::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;
base::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,
base::string16* device_location,
base::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;
}
base::string16 PortableDeviceWatcherWin::GetStoragePathFromStorageId(
const std::string& storage_unique_id) {
DCHECK(!storage_unique_id.empty());
return base::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) {
base::string16 device_location;
base::string16 storage_object_id;
if (!GetMTPStorageInfoFromDeviceId(device_id,
&device_location, &storage_object_id)) {
callback.Run(StorageMonitor::EJECT_NO_SUCH_DEVICE);
return;
}
HandleDeviceDetachEvent(device_location);
callback.Run(StorageMonitor::EJECT_OK);
}
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 base::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 base::string16& name = device_details->name;
const base::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));
MediaStorageUtil::RecordDeviceInfoHistogram(false, storage_id, name);
if (storage_id.empty() || name.empty())
return;
base::string16 model_name(name + L" (" +
storage_iter->object_temporary_id + L')');
StorageInfo info(storage_id, location, base::string16(), base::string16(),
model_name, 0);
storage_map_[storage_id] = info;
if (storage_notifications_) {
info.set_location(GetStoragePathFromStorageId(storage_id));
storage_notifications_->ProcessAttach(info);
}
}
device_map_[location] = storage_objects;
}
void PortableDeviceWatcherWin::HandleDeviceDetachEvent(
const base::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);
}
}