Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(627)

Unified Diff: chrome/browser/system_monitor/media_transfer_protocol_device_observer_win.cc

Issue 11088012: [Win, MediaGallery] Enumerate and handle mtp device attach/detach events. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: '' Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698