Chromium Code Reviews| Index: chrome/browser/system_monitor/media_transfer_protocol_device_observer_win.cc |
| diff --git a/chrome/browser/system_monitor/media_transfer_protocol_device_observer_win.cc b/chrome/browser/system_monitor/media_transfer_protocol_device_observer_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..e9cf7e234a20e881a5535d88e70d1cad83d221e1 |
| --- /dev/null |
| +++ b/chrome/browser/system_monitor/media_transfer_protocol_device_observer_win.cc |
| @@ -0,0 +1,520 @@ |
| +// 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. |
| + |
| +#include "chrome/browser/system_monitor/media_transfer_protocol_device_observer_win.h" |
| + |
| +#include <PortableDevice.h> |
| + |
| +#include "base/file_path.h" |
| +#include "base/logging.h" |
| +#include "base/stl_util.h" |
| +#include "base/stringprintf.h" |
| +#include "base/threading/sequenced_worker_pool.h" |
| +#include "base/utf_string_conversions.h" |
| +#include "base/win/scoped_co_mem.h" |
| +#include "chrome/browser/system_monitor/media_storage_util.h" |
| +#include "chrome/browser/system_monitor/removable_device_constants.h" |
| + |
| +namespace chrome { |
| +namespace mtp { |
| + |
| +using base::Bind; |
| +using base::SequencedTaskRunner; |
| +using base::SystemMonitor; |
| +using base::win::ScopedCoMem; |
| +using base::win::ScopedComPtr; |
| +using chrome::MediaStorageUtil; |
| +using content::BrowserThread; |
| + |
| +namespace { |
| + |
| +// Name of the client application that communicates with the mtp device. |
| +const char16 kClientName[] = L"Chromium"; |
| + |
| +static MediaTransferProtocolDeviceObserverWin* |
| + g_mtp_device_notifications_window_win = NULL; |
| + |
| +// Returns the friendly name of the device specified by the |pnp_device_id|. |
| +string16 GetFriendlyName(LPWSTR pnp_device_id, |
| + IPortableDeviceManager* portable_device_mgr) { |
| + DWORD name_len = 0; |
| + HRESULT hr = portable_device_mgr->GetDeviceFriendlyName(pnp_device_id, NULL, |
| + &name_len); |
| + if (FAILED(hr)) |
| + return string16(); |
| + |
| + scoped_array<char16> friendly_name(new char16[name_len]); |
| + ZeroMemory(friendly_name.get(), name_len); |
| + hr = portable_device_mgr->GetDeviceFriendlyName(pnp_device_id, |
| + friendly_name.get(), |
| + &name_len); |
| + if (FAILED(hr)) |
| + return string16(); |
| + return string16(friendly_name.get()); |
| +} |
| + |
| +// Returns the manufacturer name of the device specified by the |
| +// |pnp_device_id|. |
| +string16 GetManufacturerName(LPWSTR pnp_device_id, |
| + IPortableDeviceManager* portable_device_mgr) { |
| + DWORD name_len = 0; |
| + HRESULT hr = portable_device_mgr->GetDeviceManufacturer(pnp_device_id, NULL, |
| + &name_len); |
| + if (FAILED(hr)) |
| + return string16(); |
| + |
| + scoped_array<char16> manufacturer_name(new char16[name_len]); |
| + ZeroMemory(manufacturer_name.get(), name_len); |
| + hr = portable_device_mgr->GetDeviceManufacturer(pnp_device_id, |
| + manufacturer_name.get(), |
| + &name_len); |
| + if (FAILED(hr)) |
| + return string16(); |
| + return string16(manufacturer_name.get()); |
| +} |
| + |
| +// Returns the description of the device specified by the |pnp_device_id|. |
| +string16 GetDeviceDescription(LPWSTR pnp_device_id, |
| + IPortableDeviceManager* portable_device_mgr) { |
| + DWORD desc_len = 0; |
| + HRESULT hr = portable_device_mgr->GetDeviceDescription(pnp_device_id, NULL, |
| + &desc_len); |
| + if (FAILED(hr)) |
| + return string16(); |
| + |
| + scoped_array<char16> description(new char16[desc_len]); |
| + ZeroMemory(description.get(), desc_len); |
| + hr = portable_device_mgr->GetDeviceDescription(pnp_device_id, |
| + description.get(), |
| + &desc_len); |
| + if (FAILED(hr)) |
| + return string16(); |
| + return string16(description.get()); |
| +} |
| + |
| +// 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(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; |
| + } |
| + |
| + hr = (*client_info)->SetStringValue(WPD_CLIENT_NAME, kClientName); |
| + if (FAILED(hr)) |
| + DPLOG(ERROR) << "Failed to set client name"; |
| + |
| + hr = (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0); |
| + if (FAILED(hr)) |
| + DPLOG(ERROR) << "Failed to set client major version"; |
| + |
| + hr = (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0); |
| + if (FAILED(hr)) |
| + DPLOG(ERROR) << "Failed to set client minor version"; |
| + |
| + hr = (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0); |
| + if (FAILED(hr)) |
| + DPLOG(ERROR) << "Failed to set client revision"; |
| + |
| + hr = (*client_info)->SetUnsignedIntegerValue( |
| + WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION); |
| + if (FAILED(hr)) |
| + DPLOG(ERROR) << "Failed to set security quality of service"; |
| + |
| + hr = (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS, |
| + GENERIC_READ); |
| + if (FAILED(hr)) |
| + DPLOG(ERROR) << "Failed to set client desired access value"; |
| + return true; |
| +} |
| + |
| +// Opens the device for communication. |pnp_device_id| specifies the plug and |
| +// play device id. On success, returns true and updates |device| with a |
| +// reference to the portable device interface. |
| +bool SetupConnection(LPWSTR pnp_device_id, |
|
vandebo (ex-Chrome)
2012/10/16 17:42:36
nit: SetUp
kmadhusu
2012/10/19 04:01:38
Done.
|
| + ScopedComPtr<IPortableDevice>* device) { |
| + 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, client_info.get()); |
| + if (FAILED(hr)) { |
| + if (hr == E_ACCESSDENIED) |
| + DPLOG(ERROR) << "Access denied to open the device"; |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +// Returns true if the |object_id| is the device root object identifier. |
| +bool IsDeviceObjectId(const string16& object_id) { |
| + return object_id == WPD_DEVICE_OBJECT_ID; |
| +} |
| + |
| +// Returns the unique id property key of the object specified by the |
| +// |object_id|. |
| +REFPROPERTYKEY GetUniqueIdPropertyKey(const string16& object_id) { |
| + if (IsDeviceObjectId(object_id)) |
| + return WPD_DEVICE_SERIAL_NUMBER; |
| + return 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, |
| + 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( |
| + const ScopedComPtr<IPortableDeviceValues>& properties_values, |
| + REFPROPERTYKEY key, |
| + string16* value) { |
| + ScopedCoMem<char16> buffer; |
| + HRESULT hr = properties_values->GetStringValue(key, &buffer); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + if (value) |
| + *value = string16(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(const ScopedComPtr<IPortableDevice>& device, |
| + const string16& object_id, |
| + string16* unique_id) { |
| + ScopedComPtr<IPortableDeviceContent> content; |
| + HRESULT hr = device->Content(content.Receive()); |
| + if (FAILED(hr)) { |
| + DPLOG(ERROR) << "Failed to get IPortableDeviceContent interface"; |
| + return false; |
| + } |
| + |
| + ScopedComPtr<IPortableDeviceProperties> properties; |
| + hr = content->Properties(properties.Receive()); |
| + if (FAILED(hr)) { |
| + DPLOG(ERROR) << "Failed to get IPortableDeviceProperties interface"; |
| + return false; |
| + } |
| + |
| + ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read; |
| + if (!PopulatePropertyKeyCollection(object_id, &properties_to_read)) |
| + return false; |
| + |
| + ScopedComPtr<IPortableDeviceValues> properties_values; |
| + hr = properties->GetValues(const_cast<char16*>(object_id.c_str()), |
| + properties_to_read.get(), |
| + properties_values.Receive()); |
| + if (FAILED(hr)) { |
| + DPLOG(ERROR) << "Failed to get property values"; |
| + return false; |
| + } |
| + |
| + REFPROPERTYKEY key = GetUniqueIdPropertyKey(object_id); |
| + return GetStringPropertyValue(properties_values, 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 GetDeviceStorageUniqueId(const string16& device_serial_num, |
| + const string16& storage_id, |
| + std::string* device_storage_id) { |
| + if (device_serial_num.empty() && storage_id.empty()) |
| + return false; |
| + |
| + // |unique_id| format: StorageSerial:|storage_id|:|device_serial_num| |
| + string16 unique_id = base::StringPrintf(L"%ls%ls%ls%ls", |
| + chrome::kMtpDeviceStorageIdPrefix, |
| + storage_id.c_str(), |
| + chrome::kNonSpaceDelim, |
| + device_serial_num.c_str()); |
| + |
| + // |device_storage_id| format: mtp:|unique_id| |
| + *device_storage_id = MediaStorageUtil::MakeDeviceId( |
| + MediaStorageUtil::MTP_OR_PTP, UTF16ToUTF8(unique_id)); |
| + return true; |
| +} |
| + |
| +// Gets the list of removable storage object identifiers present in |device|. |
| +// On success, returns true and fills in |storage_ids|. |
| +bool GetRemovableStorageObjectIds(const ScopedComPtr<IPortableDevice>& device, |
| + std::vector<string16>* storage_ids) { |
| + ScopedComPtr<IPortableDeviceCapabilities> capabilities; |
| + HRESULT hr = device->Capabilities(capabilities.Receive()); |
| + if (FAILED(hr)) { |
| + DPLOG(ERROR) << "Failed to get IPortableDeviceCapabilities interface"; |
| + return false; |
| + } |
| + |
| + ScopedComPtr<IPortableDevicePropVariantCollection> storage_obj_ids; |
| + hr = capabilities->GetFunctionalObjects(WPD_FUNCTIONAL_CATEGORY_STORAGE, |
| + storage_obj_ids.Receive()); |
| + if (FAILED(hr)) { |
| + DPLOG(ERROR) << "Failed to get IPortableDevicePropVariantCollection"; |
| + return false; |
| + } |
| + |
| + DWORD num_storage_obj_ids = 0; |
| + hr = storage_obj_ids->GetCount(&num_storage_obj_ids); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + for (DWORD index = 0; index < num_storage_obj_ids; ++index) { |
| + PROPVARIANT object_id = {0}; |
| + PropVariantInit(&object_id); |
| + hr = storage_obj_ids->GetAt(index, &object_id); |
| + if (SUCCEEDED(hr) && |
| + object_id.pwszVal != NULL && |
| + object_id.vt == VT_LPWSTR) { |
| + storage_ids->push_back(object_id.pwszVal); |
| + } |
| + PropVariantClear(&object_id); |
| + } |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| +MediaTransferProtocolDeviceObserverWin:: |
| +MediaTransferProtocolDeviceObserverWin() { |
| + DCHECK(!g_mtp_device_notifications_window_win); |
| + g_mtp_device_notifications_window_win = this; |
| +} |
| + |
| +MediaTransferProtocolDeviceObserverWin:: |
| +~MediaTransferProtocolDeviceObserverWin() { |
| + DCHECK(g_mtp_device_notifications_window_win); |
| + g_mtp_device_notifications_window_win = NULL; |
| +} |
| + |
| +// static |
| +MediaTransferProtocolDeviceObserverWin* |
| +MediaTransferProtocolDeviceObserverWin::GetInstance() { |
| + return g_mtp_device_notifications_window_win; |
| +} |
| + |
| +void MediaTransferProtocolDeviceObserverWin::Init() { |
| + base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); |
| + base::SequencedWorkerPool::SequenceToken media_sequence_token = |
| + pool->GetNamedSequenceToken("media-task-runner"); |
| + media_task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior( |
| + media_sequence_token, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); |
| + DCHECK(media_task_runner_); |
| + media_task_runner_->PostTask( |
| + FROM_HERE, Bind( |
| + &MediaTransferProtocolDeviceObserverWin::InitOnBlockingThread, this)); |
| +} |
| + |
| +void MediaTransferProtocolDeviceObserverWin::HandleMtpDeviceEventOnUIThread( |
| + bool is_attached, |
| + const string16& pnp_device_id) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + if (is_attached) { |
| + media_task_runner_->PostTask( |
| + FROM_HERE, Bind(&MediaTransferProtocolDeviceObserverWin:: |
| + HandleDeviceAttachEventOnBlockingThread, this, pnp_device_id)); |
| + } else { |
| + MtpDeviceMap::iterator device_entry = device_map_.find(pnp_device_id); |
| + if (device_entry == device_map_.end()) |
| + return; |
| + |
| + SystemMonitor* system_monitor = SystemMonitor::Get(); |
| + DCHECK(system_monitor); |
| + |
| + StorageInfoList storages = device_entry->second; |
| + for (size_t index = 0; index < storages.size(); ++index) { |
| + std::string storage_id = storages[index].unique_id; |
| + MtpStorageMap::iterator storage_entry = storage_map_.find(storage_id); |
| + if (storage_entry == storage_map_.end()) { |
| + NOTREACHED(); |
| + continue; |
| + } |
| + system_monitor->ProcessRemovableStorageDetached( |
| + storage_entry->second.device_id); |
| + storage_map_.erase(storage_entry); |
| + } |
| + device_map_.erase(device_entry); |
| + } |
| +} |
| + |
| +void MediaTransferProtocolDeviceObserverWin::InitOnBlockingThread() { |
| + DCHECK(media_task_runner_->RunsTasksOnCurrentThread()); |
| + DCHECK(!portable_device_mgr_.get()); |
| + |
| + HRESULT hr = portable_device_mgr_.CreateInstance( |
| + __uuidof(PortableDeviceManager), NULL, CLSCTX_INPROC_SERVER); |
| + if (FAILED(hr)) { |
| + // Either there is no portable device support (it must be a XP with |
| + // Windows Media Player Version < 10) or the thread does not have COM |
| + // initialized. |
| + if (hr == CO_E_NOTINITIALIZED) |
| + NOTREACHED(); |
| + return; |
| + } |
| + |
| + media_task_runner_->PostTask( |
| + FROM_HERE, Bind(&MediaTransferProtocolDeviceObserverWin:: |
| + EnumerateStoragesOnBlockingThread, this)); |
|
vandebo (ex-Chrome)
2012/10/16 17:42:36
Is there a reason to post this to the thread you'r
kmadhusu
2012/10/19 04:01:38
oops. Fixed.
|
| +} |
| + |
| +string16 MediaTransferProtocolDeviceObserverWin::GetStorageName( |
| + LPWSTR pnp_device_id) { |
| + DCHECK(media_task_runner_->RunsTasksOnCurrentThread()); |
| + DCHECK(portable_device_mgr_.get()); |
| + |
| + string16 name = GetFriendlyName(pnp_device_id, portable_device_mgr_.get()); |
| + if (!name.empty()) |
| + return name; |
| + |
| + name = GetDeviceDescription(pnp_device_id, portable_device_mgr_.get()); |
| + if (!name.empty()) |
| + return name; |
| + |
| + return GetManufacturerName(pnp_device_id, portable_device_mgr_.get()); |
| +} |
| + |
| +bool MediaTransferProtocolDeviceObserverWin::GetStorages( |
| + LPWSTR pnp_device_id, |
| + std::vector<DeviceStorageInfo>* storages) { |
| + DCHECK(media_task_runner_->RunsTasksOnCurrentThread()); |
| + |
| + ScopedComPtr<IPortableDevice> device; |
| + if (!SetupConnection(pnp_device_id, &device)) |
| + return false; |
| + |
| + std::vector<string16> storage_obj_ids; |
| + if (!GetRemovableStorageObjectIds(device, &storage_obj_ids)) |
| + return false; |
| + |
| + // Get the device serial number (E.g.: 4889033500677371). |
| + string16 device_serial_num; |
| + if (!GetObjectUniqueId(device, WPD_DEVICE_OBJECT_ID, &device_serial_num)) |
| + return false; |
| + |
| + for (size_t index = 0; index < storage_obj_ids.size(); ++index) { |
| + // Get the storage object persistent id (E.g.: SID-{10001,D,31080448}). |
| + string16 storage_unique_id; |
| + if (!GetObjectUniqueId(device, storage_obj_ids[index], |
| + &storage_unique_id)) { |
| + continue; |
| + } |
| + std::string device_storage_id; |
| + if (GetDeviceStorageUniqueId(device_serial_num, storage_unique_id, |
| + &device_storage_id)) { |
| + DeviceStorageInfo storage_info; |
| + storage_info.storage_object_id = storage_obj_ids[index]; |
| + storage_info.unique_id = device_storage_id; |
| + storages->push_back(storage_info); |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +void MediaTransferProtocolDeviceObserverWin:: |
| +EnumerateStoragesOnBlockingThread() { |
| + DCHECK(media_task_runner_->RunsTasksOnCurrentThread()); |
| + DCHECK(portable_device_mgr_.get()); |
| + |
| + // 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; |
| + |
| + scoped_array<LPWSTR> pnp_device_ids(new LPWSTR[pnp_device_count]); |
| + ZeroMemory(pnp_device_ids.get(), pnp_device_count); |
| + hr = portable_device_mgr_->GetDevices( |
| + reinterpret_cast<LPWSTR*>(pnp_device_ids.get()), &pnp_device_count); |
| + if (FAILED(hr)) |
| + return; |
| + |
| + for (DWORD index = 0; index < pnp_device_count; ++index) { |
| + GetDeviceInfoOnBlockingThread(pnp_device_ids[index]); |
| + } |
| +} |
| + |
| +void MediaTransferProtocolDeviceObserverWin:: |
| +HandleDeviceAttachEventOnBlockingThread(const string16& pnp_device_id) { |
| + DCHECK(media_task_runner_->RunsTasksOnCurrentThread()); |
| + |
| + // |portable_device_mgr_| is uninitialized during unit tests. |
| + if (portable_device_mgr_.get()) |
| + portable_device_mgr_->RefreshDeviceList(); |
| + GetDeviceInfoOnBlockingThread(pnp_device_id); |
| +} |
| + |
| +void MediaTransferProtocolDeviceObserverWin::GetDeviceInfoOnBlockingThread( |
| + const string16& device_location) { |
| + DCHECK(media_task_runner_->RunsTasksOnCurrentThread()); |
| + DCHECK(!device_location.empty()); |
| + LPWSTR pnp_device_id = const_cast<char16*>(device_location.c_str()); |
| + string16 storage_name = GetStorageName(pnp_device_id); |
| + StorageInfoList storages; |
| + if (!GetStorages(pnp_device_id, &storages)) |
| + return; |
| + |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, FROM_HERE, |
| + Bind(&MediaTransferProtocolDeviceObserverWin::AddMtpDeviceOnUIThread, |
| + this, storages, storage_name, device_location)); |
| +} |
| + |
| +void MediaTransferProtocolDeviceObserverWin::AddMtpDeviceOnUIThread( |
| + const StorageInfoList& storages, |
| + const string16& name, |
| + const string16& location) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DCHECK(!ContainsKey(device_map_, location)); |
| + |
| + SystemMonitor* system_monitor = SystemMonitor::Get(); |
| + DCHECK(system_monitor); |
| + for (size_t index = 0; index < storages.size(); ++index) { |
| + const std::string& storage_id = storages[index].unique_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 storages. Therefore, add the partition |
| + // details to the storage name. |
| + // E.g.: "Nexus 7 (SID-{10001,D,31080448})" |
| + string16 storage_name = base::StringPrintf( |
| + L"%ls (%ls)", name.c_str(), storages[index].storage_object_id.c_str()); |
| + |
| + SystemMonitor::RemovableStorageInfo storage_info(storage_id, storage_name, |
| + location); |
| + storage_map_[storage_id] = storage_info; |
| + system_monitor->ProcessRemovableStorageAttached(storage_id, storage_name, |
| + location); |
| + } |
| + device_map_[location] = storages; |
| +} |
| + |
| +} // namespace mtp |
| +} // namespace chrome |