OLD | NEW |
---|---|
(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 | |
OLD | NEW |