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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 #include "chrome/browser/system_monitor/media_transfer_protocol_device_observer_ win.h"
6
7 #include <PortableDevice.h>
8
9 #include "base/file_path.h"
10 #include "base/logging.h"
11 #include "base/stl_util.h"
12 #include "base/stringprintf.h"
13 #include "base/threading/sequenced_worker_pool.h"
14 #include "base/utf_string_conversions.h"
15 #include "base/win/scoped_co_mem.h"
16 #include "chrome/browser/system_monitor/media_storage_util.h"
17 #include "chrome/browser/system_monitor/removable_device_constants.h"
18
19 namespace chrome {
20 namespace mtp {
21
22 using base::Bind;
23 using base::SequencedTaskRunner;
24 using base::SystemMonitor;
25 using base::win::ScopedCoMem;
26 using base::win::ScopedComPtr;
27 using chrome::MediaStorageUtil;
28 using content::BrowserThread;
29
30 namespace {
31
32 // Name of the client application that communicates with the mtp device.
33 const char16 kClientName[] = L"Chromium";
34
35 static MediaTransferProtocolDeviceObserverWin*
36 g_mtp_device_notifications_window_win = NULL;
37
38 // Returns the friendly name of the device specified by the |pnp_device_id|.
39 string16 GetFriendlyName(LPWSTR pnp_device_id,
40 IPortableDeviceManager* portable_device_mgr) {
41 DWORD name_len = 0;
42 HRESULT hr = portable_device_mgr->GetDeviceFriendlyName(pnp_device_id, NULL,
43 &name_len);
44 if (FAILED(hr))
45 return string16();
46
47 scoped_array<char16> friendly_name(new char16[name_len]);
48 ZeroMemory(friendly_name.get(), name_len);
49 hr = portable_device_mgr->GetDeviceFriendlyName(pnp_device_id,
50 friendly_name.get(),
51 &name_len);
52 if (FAILED(hr))
53 return string16();
54 return string16(friendly_name.get());
55 }
56
57 // Returns the manufacturer name of the device specified by the
58 // |pnp_device_id|.
59 string16 GetManufacturerName(LPWSTR pnp_device_id,
60 IPortableDeviceManager* portable_device_mgr) {
61 DWORD name_len = 0;
62 HRESULT hr = portable_device_mgr->GetDeviceManufacturer(pnp_device_id, NULL,
63 &name_len);
64 if (FAILED(hr))
65 return string16();
66
67 scoped_array<char16> manufacturer_name(new char16[name_len]);
68 ZeroMemory(manufacturer_name.get(), name_len);
69 hr = portable_device_mgr->GetDeviceManufacturer(pnp_device_id,
70 manufacturer_name.get(),
71 &name_len);
72 if (FAILED(hr))
73 return string16();
74 return string16(manufacturer_name.get());
75 }
76
77 // Returns the description of the device specified by the |pnp_device_id|.
78 string16 GetDeviceDescription(LPWSTR pnp_device_id,
79 IPortableDeviceManager* portable_device_mgr) {
80 DWORD desc_len = 0;
81 HRESULT hr = portable_device_mgr->GetDeviceDescription(pnp_device_id, NULL,
82 &desc_len);
83 if (FAILED(hr))
84 return string16();
85
86 scoped_array<char16> description(new char16[desc_len]);
87 ZeroMemory(description.get(), desc_len);
88 hr = portable_device_mgr->GetDeviceDescription(pnp_device_id,
89 description.get(),
90 &desc_len);
91 if (FAILED(hr))
92 return string16();
93 return string16(description.get());
94 }
95
96 // On success, returns true and updates |client_info| with a reference to an
97 // IPortableDeviceValues interface that holds information about the
98 // application that communicates with the device.
99 bool GetClientInformation(ScopedComPtr<IPortableDeviceValues>* client_info) {
100 HRESULT hr = (*client_info).CreateInstance(__uuidof(PortableDeviceValues),
101 NULL, CLSCTX_INPROC_SERVER);
102 if (FAILED(hr)) {
103 DPLOG(ERROR) << "Failed to create an instance of IPortableDeviceValues";
104 return false;
105 }
106
107 hr = (*client_info)->SetStringValue(WPD_CLIENT_NAME, kClientName);
108 if (FAILED(hr))
109 DPLOG(ERROR) << "Failed to set client name";
110
111 hr = (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 0);
112 if (FAILED(hr))
113 DPLOG(ERROR) << "Failed to set client major version";
114
115 hr = (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
116 if (FAILED(hr))
117 DPLOG(ERROR) << "Failed to set client minor version";
118
119 hr = (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0);
120 if (FAILED(hr))
121 DPLOG(ERROR) << "Failed to set client revision";
122
123 hr = (*client_info)->SetUnsignedIntegerValue(
124 WPD_CLIENT_SECURITY_QUALITY_OF_SERVICE, SECURITY_IMPERSONATION);
125 if (FAILED(hr))
126 DPLOG(ERROR) << "Failed to set security quality of service";
127
128 hr = (*client_info)->SetUnsignedIntegerValue(WPD_CLIENT_DESIRED_ACCESS,
129 GENERIC_READ);
130 if (FAILED(hr))
131 DPLOG(ERROR) << "Failed to set client desired access value";
132 return true;
133 }
134
135 // Opens the device for communication. |pnp_device_id| specifies the plug and
136 // play device id. On success, returns true and updates |device| with a
137 // reference to the portable device interface.
138 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.
139 ScopedComPtr<IPortableDevice>* device) {
140 ScopedComPtr<IPortableDeviceValues> client_info;
141 if (!GetClientInformation(&client_info))
142 return false;
143
144 HRESULT hr = (*device).CreateInstance(__uuidof(PortableDevice), NULL,
145 CLSCTX_INPROC_SERVER);
146 if (FAILED(hr)) {
147 DPLOG(ERROR) << "Failed to create an instance of IPortableDevice";
148 return false;
149 }
150
151 hr = (*device)->Open(pnp_device_id, client_info.get());
152 if (FAILED(hr)) {
153 if (hr == E_ACCESSDENIED)
154 DPLOG(ERROR) << "Access denied to open the device";
155 return false;
156 }
157 return true;
158 }
159
160 // Returns true if the |object_id| is the device root object identifier.
161 bool IsDeviceObjectId(const string16& object_id) {
162 return object_id == WPD_DEVICE_OBJECT_ID;
163 }
164
165 // Returns the unique id property key of the object specified by the
166 // |object_id|.
167 REFPROPERTYKEY GetUniqueIdPropertyKey(const string16& object_id) {
168 if (IsDeviceObjectId(object_id))
169 return WPD_DEVICE_SERIAL_NUMBER;
170 return WPD_OBJECT_PERSISTENT_UNIQUE_ID;
171 }
172
173 // On success, returns true and populates |properties_to_read| with the
174 // property key of the object specified by the |object_id|.
175 bool PopulatePropertyKeyCollection(
176 const string16& object_id,
177 ScopedComPtr<IPortableDeviceKeyCollection>* properties_to_read) {
178 HRESULT hr = properties_to_read->CreateInstance(
179 __uuidof(PortableDeviceKeyCollection), NULL, CLSCTX_INPROC_SERVER);
180 if (FAILED(hr)) {
181 DPLOG(ERROR) << "Failed to create IPortableDeviceKeyCollection instance";
182 return false;
183 }
184 REFPROPERTYKEY key = GetUniqueIdPropertyKey(object_id);
185 hr = (*properties_to_read)->Add(key);
186 return SUCCEEDED(hr);
187 }
188
189 // Wrapper function to get content property string value.
190 bool GetStringPropertyValue(
191 const ScopedComPtr<IPortableDeviceValues>& properties_values,
192 REFPROPERTYKEY key,
193 string16* value) {
194 ScopedCoMem<char16> buffer;
195 HRESULT hr = properties_values->GetStringValue(key, &buffer);
196 if (FAILED(hr))
197 return false;
198
199 if (value)
200 *value = string16(buffer);
201 return true;
202 }
203
204 // Constructs a unique identifier for the object specified by the |object_id|.
205 // On success, returns true and fills in |unique_id|.
206 bool GetObjectUniqueId(const ScopedComPtr<IPortableDevice>& device,
207 const string16& object_id,
208 string16* unique_id) {
209 ScopedComPtr<IPortableDeviceContent> content;
210 HRESULT hr = device->Content(content.Receive());
211 if (FAILED(hr)) {
212 DPLOG(ERROR) << "Failed to get IPortableDeviceContent interface";
213 return false;
214 }
215
216 ScopedComPtr<IPortableDeviceProperties> properties;
217 hr = content->Properties(properties.Receive());
218 if (FAILED(hr)) {
219 DPLOG(ERROR) << "Failed to get IPortableDeviceProperties interface";
220 return false;
221 }
222
223 ScopedComPtr<IPortableDeviceKeyCollection> properties_to_read;
224 if (!PopulatePropertyKeyCollection(object_id, &properties_to_read))
225 return false;
226
227 ScopedComPtr<IPortableDeviceValues> properties_values;
228 hr = properties->GetValues(const_cast<char16*>(object_id.c_str()),
229 properties_to_read.get(),
230 properties_values.Receive());
231 if (FAILED(hr)) {
232 DPLOG(ERROR) << "Failed to get property values";
233 return false;
234 }
235
236 REFPROPERTYKEY key = GetUniqueIdPropertyKey(object_id);
237 return GetStringPropertyValue(properties_values, key, unique_id);
238 }
239
240 // Constructs the device storage unique identifier using |device_serial_num| and
241 // |storage_id|. On success, returns true and fills in |device_storage_id|.
242 bool GetDeviceStorageUniqueId(const string16& device_serial_num,
243 const string16& storage_id,
244 std::string* device_storage_id) {
245 if (device_serial_num.empty() && storage_id.empty())
246 return false;
247
248 // |unique_id| format: StorageSerial:|storage_id|:|device_serial_num|
249 string16 unique_id = base::StringPrintf(L"%ls%ls%ls%ls",
250 chrome::kMtpDeviceStorageIdPrefix,
251 storage_id.c_str(),
252 chrome::kNonSpaceDelim,
253 device_serial_num.c_str());
254
255 // |device_storage_id| format: mtp:|unique_id|
256 *device_storage_id = MediaStorageUtil::MakeDeviceId(
257 MediaStorageUtil::MTP_OR_PTP, UTF16ToUTF8(unique_id));
258 return true;
259 }
260
261 // Gets the list of removable storage object identifiers present in |device|.
262 // On success, returns true and fills in |storage_ids|.
263 bool GetRemovableStorageObjectIds(const ScopedComPtr<IPortableDevice>& device,
264 std::vector<string16>* storage_ids) {
265 ScopedComPtr<IPortableDeviceCapabilities> capabilities;
266 HRESULT hr = device->Capabilities(capabilities.Receive());
267 if (FAILED(hr)) {
268 DPLOG(ERROR) << "Failed to get IPortableDeviceCapabilities interface";
269 return false;
270 }
271
272 ScopedComPtr<IPortableDevicePropVariantCollection> storage_obj_ids;
273 hr = capabilities->GetFunctionalObjects(WPD_FUNCTIONAL_CATEGORY_STORAGE,
274 storage_obj_ids.Receive());
275 if (FAILED(hr)) {
276 DPLOG(ERROR) << "Failed to get IPortableDevicePropVariantCollection";
277 return false;
278 }
279
280 DWORD num_storage_obj_ids = 0;
281 hr = storage_obj_ids->GetCount(&num_storage_obj_ids);
282 if (FAILED(hr))
283 return false;
284
285 for (DWORD index = 0; index < num_storage_obj_ids; ++index) {
286 PROPVARIANT object_id = {0};
287 PropVariantInit(&object_id);
288 hr = storage_obj_ids->GetAt(index, &object_id);
289 if (SUCCEEDED(hr) &&
290 object_id.pwszVal != NULL &&
291 object_id.vt == VT_LPWSTR) {
292 storage_ids->push_back(object_id.pwszVal);
293 }
294 PropVariantClear(&object_id);
295 }
296 return true;
297 }
298
299 } // namespace
300
301 MediaTransferProtocolDeviceObserverWin::
302 MediaTransferProtocolDeviceObserverWin() {
303 DCHECK(!g_mtp_device_notifications_window_win);
304 g_mtp_device_notifications_window_win = this;
305 }
306
307 MediaTransferProtocolDeviceObserverWin::
308 ~MediaTransferProtocolDeviceObserverWin() {
309 DCHECK(g_mtp_device_notifications_window_win);
310 g_mtp_device_notifications_window_win = NULL;
311 }
312
313 // static
314 MediaTransferProtocolDeviceObserverWin*
315 MediaTransferProtocolDeviceObserverWin::GetInstance() {
316 return g_mtp_device_notifications_window_win;
317 }
318
319 void MediaTransferProtocolDeviceObserverWin::Init() {
320 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
321 base::SequencedWorkerPool::SequenceToken media_sequence_token =
322 pool->GetNamedSequenceToken("media-task-runner");
323 media_task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
324 media_sequence_token, base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
325 DCHECK(media_task_runner_);
326 media_task_runner_->PostTask(
327 FROM_HERE, Bind(
328 &MediaTransferProtocolDeviceObserverWin::InitOnBlockingThread, this));
329 }
330
331 void MediaTransferProtocolDeviceObserverWin::HandleMtpDeviceEventOnUIThread(
332 bool is_attached,
333 const string16& pnp_device_id) {
334 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
335 if (is_attached) {
336 media_task_runner_->PostTask(
337 FROM_HERE, Bind(&MediaTransferProtocolDeviceObserverWin::
338 HandleDeviceAttachEventOnBlockingThread, this, pnp_device_id));
339 } else {
340 MtpDeviceMap::iterator device_entry = device_map_.find(pnp_device_id);
341 if (device_entry == device_map_.end())
342 return;
343
344 SystemMonitor* system_monitor = SystemMonitor::Get();
345 DCHECK(system_monitor);
346
347 StorageInfoList storages = device_entry->second;
348 for (size_t index = 0; index < storages.size(); ++index) {
349 std::string storage_id = storages[index].unique_id;
350 MtpStorageMap::iterator storage_entry = storage_map_.find(storage_id);
351 if (storage_entry == storage_map_.end()) {
352 NOTREACHED();
353 continue;
354 }
355 system_monitor->ProcessRemovableStorageDetached(
356 storage_entry->second.device_id);
357 storage_map_.erase(storage_entry);
358 }
359 device_map_.erase(device_entry);
360 }
361 }
362
363 void MediaTransferProtocolDeviceObserverWin::InitOnBlockingThread() {
364 DCHECK(media_task_runner_->RunsTasksOnCurrentThread());
365 DCHECK(!portable_device_mgr_.get());
366
367 HRESULT hr = portable_device_mgr_.CreateInstance(
368 __uuidof(PortableDeviceManager), NULL, CLSCTX_INPROC_SERVER);
369 if (FAILED(hr)) {
370 // Either there is no portable device support (it must be a XP with
371 // Windows Media Player Version < 10) or the thread does not have COM
372 // initialized.
373 if (hr == CO_E_NOTINITIALIZED)
374 NOTREACHED();
375 return;
376 }
377
378 media_task_runner_->PostTask(
379 FROM_HERE, Bind(&MediaTransferProtocolDeviceObserverWin::
380 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.
381 }
382
383 string16 MediaTransferProtocolDeviceObserverWin::GetStorageName(
384 LPWSTR pnp_device_id) {
385 DCHECK(media_task_runner_->RunsTasksOnCurrentThread());
386 DCHECK(portable_device_mgr_.get());
387
388 string16 name = GetFriendlyName(pnp_device_id, portable_device_mgr_.get());
389 if (!name.empty())
390 return name;
391
392 name = GetDeviceDescription(pnp_device_id, portable_device_mgr_.get());
393 if (!name.empty())
394 return name;
395
396 return GetManufacturerName(pnp_device_id, portable_device_mgr_.get());
397 }
398
399 bool MediaTransferProtocolDeviceObserverWin::GetStorages(
400 LPWSTR pnp_device_id,
401 std::vector<DeviceStorageInfo>* storages) {
402 DCHECK(media_task_runner_->RunsTasksOnCurrentThread());
403
404 ScopedComPtr<IPortableDevice> device;
405 if (!SetupConnection(pnp_device_id, &device))
406 return false;
407
408 std::vector<string16> storage_obj_ids;
409 if (!GetRemovableStorageObjectIds(device, &storage_obj_ids))
410 return false;
411
412 // Get the device serial number (E.g.: 4889033500677371).
413 string16 device_serial_num;
414 if (!GetObjectUniqueId(device, WPD_DEVICE_OBJECT_ID, &device_serial_num))
415 return false;
416
417 for (size_t index = 0; index < storage_obj_ids.size(); ++index) {
418 // Get the storage object persistent id (E.g.: SID-{10001,D,31080448}).
419 string16 storage_unique_id;
420 if (!GetObjectUniqueId(device, storage_obj_ids[index],
421 &storage_unique_id)) {
422 continue;
423 }
424 std::string device_storage_id;
425 if (GetDeviceStorageUniqueId(device_serial_num, storage_unique_id,
426 &device_storage_id)) {
427 DeviceStorageInfo storage_info;
428 storage_info.storage_object_id = storage_obj_ids[index];
429 storage_info.unique_id = device_storage_id;
430 storages->push_back(storage_info);
431 }
432 }
433 return true;
434 }
435
436 void MediaTransferProtocolDeviceObserverWin::
437 EnumerateStoragesOnBlockingThread() {
438 DCHECK(media_task_runner_->RunsTasksOnCurrentThread());
439 DCHECK(portable_device_mgr_.get());
440
441 // Get the total number of devices found on the system.
442 DWORD pnp_device_count = 0;
443 HRESULT hr = portable_device_mgr_->GetDevices(NULL, &pnp_device_count);
444 if (FAILED(hr))
445 return;
446
447 scoped_array<LPWSTR> pnp_device_ids(new LPWSTR[pnp_device_count]);
448 ZeroMemory(pnp_device_ids.get(), pnp_device_count);
449 hr = portable_device_mgr_->GetDevices(
450 reinterpret_cast<LPWSTR*>(pnp_device_ids.get()), &pnp_device_count);
451 if (FAILED(hr))
452 return;
453
454 for (DWORD index = 0; index < pnp_device_count; ++index) {
455 GetDeviceInfoOnBlockingThread(pnp_device_ids[index]);
456 }
457 }
458
459 void MediaTransferProtocolDeviceObserverWin::
460 HandleDeviceAttachEventOnBlockingThread(const string16& pnp_device_id) {
461 DCHECK(media_task_runner_->RunsTasksOnCurrentThread());
462
463 // |portable_device_mgr_| is uninitialized during unit tests.
464 if (portable_device_mgr_.get())
465 portable_device_mgr_->RefreshDeviceList();
466 GetDeviceInfoOnBlockingThread(pnp_device_id);
467 }
468
469 void MediaTransferProtocolDeviceObserverWin::GetDeviceInfoOnBlockingThread(
470 const string16& device_location) {
471 DCHECK(media_task_runner_->RunsTasksOnCurrentThread());
472 DCHECK(!device_location.empty());
473 LPWSTR pnp_device_id = const_cast<char16*>(device_location.c_str());
474 string16 storage_name = GetStorageName(pnp_device_id);
475 StorageInfoList storages;
476 if (!GetStorages(pnp_device_id, &storages))
477 return;
478
479 BrowserThread::PostTask(
480 BrowserThread::UI, FROM_HERE,
481 Bind(&MediaTransferProtocolDeviceObserverWin::AddMtpDeviceOnUIThread,
482 this, storages, storage_name, device_location));
483 }
484
485 void MediaTransferProtocolDeviceObserverWin::AddMtpDeviceOnUIThread(
486 const StorageInfoList& storages,
487 const string16& name,
488 const string16& location) {
489 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
490 DCHECK(!ContainsKey(device_map_, location));
491
492 SystemMonitor* system_monitor = SystemMonitor::Get();
493 DCHECK(system_monitor);
494 for (size_t index = 0; index < storages.size(); ++index) {
495 const std::string& storage_id = storages[index].unique_id;
496 DCHECK(!ContainsKey(storage_map_, storage_id));
497
498 // Keep track of storage id and storage name to see how often we receive
499 // empty values.
500 MediaStorageUtil::RecordDeviceInfoHistogram(false, storage_id, name);
501 if (storage_id.empty() || name.empty())
502 return;
503
504 // Device can have several data storages. Therefore, add the partition
505 // details to the storage name.
506 // E.g.: "Nexus 7 (SID-{10001,D,31080448})"
507 string16 storage_name = base::StringPrintf(
508 L"%ls (%ls)", name.c_str(), storages[index].storage_object_id.c_str());
509
510 SystemMonitor::RemovableStorageInfo storage_info(storage_id, storage_name,
511 location);
512 storage_map_[storage_id] = storage_info;
513 system_monitor->ProcessRemovableStorageAttached(storage_id, storage_name,
514 location);
515 }
516 device_map_[location] = storages;
517 }
518
519 } // namespace mtp
520 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698