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

Side by Side Diff: media/device_monitors/device_monitor_mac.mm

Issue 2529493002: mac: Remove more media/base/mac glue unneeded now that we target 10.9 (Closed)
Patch Set: . Created 4 years, 1 month 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
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "media/device_monitors/device_monitor_mac.h" 5 #include "media/device_monitors/device_monitor_mac.h"
6 6
7 #include <AVFoundation/AVFoundation.h>
7 #include <set> 8 #include <set>
8 9
9 #include "base/bind_helpers.h" 10 #include "base/bind_helpers.h"
10 #include "base/logging.h" 11 #include "base/logging.h"
11 #include "base/mac/bind_objc_block.h" 12 #include "base/mac/bind_objc_block.h"
12 #include "base/mac/scoped_nsobject.h" 13 #include "base/mac/scoped_nsobject.h"
13 #include "base/macros.h" 14 #include "base/macros.h"
14 #include "base/profiler/scoped_tracker.h" 15 #include "base/profiler/scoped_tracker.h"
15 #include "base/task_runner_util.h" 16 #include "base/task_runner_util.h"
16 #include "base/threading/thread_checker.h" 17 #include "base/threading/thread_checker.h"
17 #import "media/base/mac/avfoundation_glue.h"
18 18
19 namespace { 19 namespace {
20 20
21 // This class is used to keep track of system devices names and their types. 21 // This class is used to keep track of system devices names and their types.
22 class DeviceInfo { 22 class DeviceInfo {
23 public: 23 public:
24 enum DeviceType { kAudio, kVideo, kMuxed, kUnknown, kInvalid }; 24 enum DeviceType { kAudio, kVideo, kMuxed, kUnknown, kInvalid };
25 25
26 DeviceInfo(const std::string& unique_id, DeviceType type) 26 DeviceInfo(const std::string& unique_id, DeviceType type)
27 : unique_id_(unique_id), type_(type) {} 27 : unique_id_(unique_id), type_(type) {}
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after
131 131
132 // This class is a Key-Value Observer (KVO) shim. It is needed because C++ 132 // This class is a Key-Value Observer (KVO) shim. It is needed because C++
133 // classes cannot observe Key-Values directly. Created, manipulated, and 133 // classes cannot observe Key-Values directly. Created, manipulated, and
134 // destroyed on the UI Thread by SuspendObserverDelegate. 134 // destroyed on the UI Thread by SuspendObserverDelegate.
135 @interface CrAVFoundationDeviceObserver : NSObject { 135 @interface CrAVFoundationDeviceObserver : NSObject {
136 @private 136 @private
137 // Callback for device changed, has to run on Device Thread. 137 // Callback for device changed, has to run on Device Thread.
138 base::Closure onDeviceChangedCallback_; 138 base::Closure onDeviceChangedCallback_;
139 139
140 // Member to keep track of the devices we are already monitoring. 140 // Member to keep track of the devices we are already monitoring.
141 std::set<base::scoped_nsobject<CrAVCaptureDevice>> monitoredDevices_; 141 std::set<base::scoped_nsobject<AVCaptureDevice>> monitoredDevices_;
142 142
143 // Pegged to the "main" thread -- usually content::BrowserThread::UI. 143 // Pegged to the "main" thread -- usually content::BrowserThread::UI.
144 base::ThreadChecker mainThreadChecker_; 144 base::ThreadChecker mainThreadChecker_;
145 } 145 }
146 146
147 - (id)initWithOnChangedCallback:(const base::Closure&)callback; 147 - (id)initWithOnChangedCallback:(const base::Closure&)callback;
148 - (void)startObserving:(base::scoped_nsobject<CrAVCaptureDevice>)device; 148 - (void)startObserving:(base::scoped_nsobject<AVCaptureDevice>)device;
149 - (void)stopObserving:(CrAVCaptureDevice*)device; 149 - (void)stopObserving:(AVCaptureDevice*)device;
150 - (void)clearOnDeviceChangedCallback; 150 - (void)clearOnDeviceChangedCallback;
151 151
152 @end 152 @end
153 153
154 namespace { 154 namespace {
155 155
156 // This class owns and manages the lifetime of a CrAVFoundationDeviceObserver. 156 // This class owns and manages the lifetime of a CrAVFoundationDeviceObserver.
157 // It is created and destroyed on AVFoundationMonitorImpl's main thread (usually 157 // It is created and destroyed on AVFoundationMonitorImpl's main thread (usually
158 // browser's UI thread), and it operates on this thread except for the expensive 158 // browser's UI thread), and it operates on this thread except for the expensive
159 // device enumerations which are run on Device Thread. 159 // device enumerations which are run on Device Thread.
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
209 base::Closure on_device_changed_callback = base::Bind( 209 base::Closure on_device_changed_callback = base::Bind(
210 &SuspendObserverDelegate::OnDeviceChanged, this, device_thread); 210 &SuspendObserverDelegate::OnDeviceChanged, this, device_thread);
211 suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc] 211 suspend_observer_.reset([[CrAVFoundationDeviceObserver alloc]
212 initWithOnChangedCallback:on_device_changed_callback]); 212 initWithOnChangedCallback:on_device_changed_callback]);
213 213
214 // Enumerate the devices in Device thread and post the observers start to be 214 // Enumerate the devices in Device thread and post the observers start to be
215 // done on UI thread. The devices array is retained in |device_thread| and 215 // done on UI thread. The devices array is retained in |device_thread| and
216 // released in DoStartObserver(). 216 // released in DoStartObserver().
217 base::PostTaskAndReplyWithResult( 217 base::PostTaskAndReplyWithResult(
218 device_thread.get(), FROM_HERE, base::BindBlock(^{ 218 device_thread.get(), FROM_HERE, base::BindBlock(^{
219 return [[AVCaptureDeviceGlue devices] retain]; 219 return [[AVCaptureDevice devices] retain];
220 }), 220 }),
221 base::Bind(&SuspendObserverDelegate::DoStartObserver, this)); 221 base::Bind(&SuspendObserverDelegate::DoStartObserver, this));
222 } 222 }
223 223
224 void SuspendObserverDelegate::OnDeviceChanged( 224 void SuspendObserverDelegate::OnDeviceChanged(
225 const scoped_refptr<base::SingleThreadTaskRunner>& device_thread) { 225 const scoped_refptr<base::SingleThreadTaskRunner>& device_thread) {
226 DCHECK(main_thread_checker_.CalledOnValidThread()); 226 DCHECK(main_thread_checker_.CalledOnValidThread());
227 // Enumerate the devices in Device thread and post the consolidation of the 227 // Enumerate the devices in Device thread and post the consolidation of the
228 // new devices and the old ones to be done on main thread. The devices array 228 // new devices and the old ones to be done on main thread. The devices array
229 // is retained in |device_thread| and released in DoOnDeviceChanged(). 229 // is retained in |device_thread| and released in DoOnDeviceChanged().
230 PostTaskAndReplyWithResult( 230 PostTaskAndReplyWithResult(
231 device_thread.get(), FROM_HERE, base::BindBlock(^{ 231 device_thread.get(), FROM_HERE, base::BindBlock(^{
232 return [[AVCaptureDeviceGlue devices] retain]; 232 return [[AVCaptureDevice devices] retain];
233 }), 233 }),
234 base::Bind(&SuspendObserverDelegate::DoOnDeviceChanged, this)); 234 base::Bind(&SuspendObserverDelegate::DoOnDeviceChanged, this));
235 } 235 }
236 236
237 void SuspendObserverDelegate::ResetDeviceMonitor() { 237 void SuspendObserverDelegate::ResetDeviceMonitor() {
238 DCHECK(main_thread_checker_.CalledOnValidThread()); 238 DCHECK(main_thread_checker_.CalledOnValidThread());
239 avfoundation_monitor_impl_ = NULL; 239 avfoundation_monitor_impl_ = NULL;
240 [suspend_observer_ clearOnDeviceChangedCallback]; 240 [suspend_observer_ clearOnDeviceChangedCallback];
241 } 241 }
242 242
243 SuspendObserverDelegate::~SuspendObserverDelegate() { 243 SuspendObserverDelegate::~SuspendObserverDelegate() {
244 DCHECK(main_thread_checker_.CalledOnValidThread()); 244 DCHECK(main_thread_checker_.CalledOnValidThread());
245 } 245 }
246 246
247 void SuspendObserverDelegate::DoStartObserver(NSArray* devices) { 247 void SuspendObserverDelegate::DoStartObserver(NSArray* devices) {
248 DCHECK(main_thread_checker_.CalledOnValidThread()); 248 DCHECK(main_thread_checker_.CalledOnValidThread());
249 base::scoped_nsobject<NSArray> auto_release(devices); 249 base::scoped_nsobject<NSArray> auto_release(devices);
250 for (CrAVCaptureDevice* device in devices) { 250 for (AVCaptureDevice* device in devices) {
251 base::scoped_nsobject<CrAVCaptureDevice> device_ptr([device retain]); 251 base::scoped_nsobject<AVCaptureDevice> device_ptr([device retain]);
252 [suspend_observer_ startObserving:device_ptr]; 252 [suspend_observer_ startObserving:device_ptr];
253 } 253 }
254 } 254 }
255 255
256 void SuspendObserverDelegate::DoOnDeviceChanged(NSArray* devices) { 256 void SuspendObserverDelegate::DoOnDeviceChanged(NSArray* devices) {
257 DCHECK(main_thread_checker_.CalledOnValidThread()); 257 DCHECK(main_thread_checker_.CalledOnValidThread());
258 base::scoped_nsobject<NSArray> auto_release(devices); 258 base::scoped_nsobject<NSArray> auto_release(devices);
259 std::vector<DeviceInfo> snapshot_devices; 259 std::vector<DeviceInfo> snapshot_devices;
260 for (CrAVCaptureDevice* device in devices) { 260 for (AVCaptureDevice* device in devices) {
261 base::scoped_nsobject<CrAVCaptureDevice> device_ptr([device retain]); 261 base::scoped_nsobject<AVCaptureDevice> device_ptr([device retain]);
262 [suspend_observer_ startObserving:device_ptr]; 262 [suspend_observer_ startObserving:device_ptr];
263 263
264 BOOL suspended = [device respondsToSelector:@selector(isSuspended)] && 264 BOOL suspended = [device respondsToSelector:@selector(isSuspended)] &&
265 [device isSuspended]; 265 [device isSuspended];
266 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown; 266 DeviceInfo::DeviceType device_type = DeviceInfo::kUnknown;
267 if ([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()]) { 267 if ([device hasMediaType:AVMediaTypeVideo]) {
268 if (suspended) 268 if (suspended)
269 continue; 269 continue;
270 device_type = DeviceInfo::kVideo; 270 device_type = DeviceInfo::kVideo;
271 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeMuxed()]) { 271 } else if ([device hasMediaType:AVMediaTypeMuxed]) {
272 device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed; 272 device_type = suspended ? DeviceInfo::kAudio : DeviceInfo::kMuxed;
273 } else if ([device hasMediaType:AVFoundationGlue::AVMediaTypeAudio()]) { 273 } else if ([device hasMediaType:AVMediaTypeAudio]) {
274 device_type = DeviceInfo::kAudio; 274 device_type = DeviceInfo::kAudio;
275 } 275 }
276 snapshot_devices.push_back( 276 snapshot_devices.push_back(
277 DeviceInfo([[device uniqueID] UTF8String], device_type)); 277 DeviceInfo([[device uniqueID] UTF8String], device_type));
278 } 278 }
279 // Make sure no references are held to |devices| when 279 // Make sure no references are held to |devices| when
280 // ConsolidateDevicesListAndNotify is called since the VideoCaptureManager 280 // ConsolidateDevicesListAndNotify is called since the VideoCaptureManager
281 // and AudioCaptureManagers also enumerates the available devices but on 281 // and AudioCaptureManagers also enumerates the available devices but on
282 // another thread. 282 // another thread.
283 auto_release.reset(); 283 auto_release.reset();
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
318 318
319 AVFoundationMonitorImpl::AVFoundationMonitorImpl( 319 AVFoundationMonitorImpl::AVFoundationMonitorImpl(
320 media::DeviceMonitorMac* monitor, 320 media::DeviceMonitorMac* monitor,
321 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) 321 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner)
322 : DeviceMonitorMacImpl(monitor), 322 : DeviceMonitorMacImpl(monitor),
323 device_task_runner_(device_task_runner), 323 device_task_runner_(device_task_runner),
324 suspend_observer_delegate_(new SuspendObserverDelegate(this)) { 324 suspend_observer_delegate_(new SuspendObserverDelegate(this)) {
325 DCHECK(main_thread_checker_.CalledOnValidThread()); 325 DCHECK(main_thread_checker_.CalledOnValidThread());
326 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; 326 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
327 device_arrival_ = 327 device_arrival_ =
328 [nc addObserverForName:AVFoundationGlue:: 328 [nc addObserverForName:AVCaptureDeviceWasConnectedNotification
329 AVCaptureDeviceWasConnectedNotification()
330 object:nil 329 object:nil
331 queue:nil 330 queue:nil
332 usingBlock:^(NSNotification* notification) { 331 usingBlock:^(NSNotification* notification) {
333 OnDeviceChanged(); 332 OnDeviceChanged();
334 }]; 333 }];
335 device_removal_ = 334 device_removal_ =
336 [nc addObserverForName:AVFoundationGlue:: 335 [nc addObserverForName:AVCaptureDeviceWasDisconnectedNotification
337 AVCaptureDeviceWasDisconnectedNotification()
338 object:nil 336 object:nil
339 queue:nil 337 queue:nil
340 usingBlock:^(NSNotification* notification) { 338 usingBlock:^(NSNotification* notification) {
341 OnDeviceChanged(); 339 OnDeviceChanged();
342 }]; 340 }];
343 suspend_observer_delegate_->StartObserver(device_task_runner_); 341 suspend_observer_delegate_->StartObserver(device_task_runner_);
344 } 342 }
345 343
346 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() { 344 AVFoundationMonitorImpl::~AVFoundationMonitorImpl() {
347 DCHECK(main_thread_checker_.CalledOnValidThread()); 345 DCHECK(main_thread_checker_.CalledOnValidThread());
(...skipping 16 matching lines...) Expand all
364 DCHECK(mainThreadChecker_.CalledOnValidThread()); 362 DCHECK(mainThreadChecker_.CalledOnValidThread());
365 if ((self = [super init])) { 363 if ((self = [super init])) {
366 DCHECK(!callback.is_null()); 364 DCHECK(!callback.is_null());
367 onDeviceChangedCallback_ = callback; 365 onDeviceChangedCallback_ = callback;
368 } 366 }
369 return self; 367 return self;
370 } 368 }
371 369
372 - (void)dealloc { 370 - (void)dealloc {
373 DCHECK(mainThreadChecker_.CalledOnValidThread()); 371 DCHECK(mainThreadChecker_.CalledOnValidThread());
374 std::set<base::scoped_nsobject<CrAVCaptureDevice>>::iterator it = 372 std::set<base::scoped_nsobject<AVCaptureDevice>>::iterator it =
375 monitoredDevices_.begin(); 373 monitoredDevices_.begin();
376 while (it != monitoredDevices_.end()) 374 while (it != monitoredDevices_.end())
377 [self removeObservers:*(it++)]; 375 [self removeObservers:*(it++)];
378 [super dealloc]; 376 [super dealloc];
379 } 377 }
380 378
381 - (void)startObserving:(base::scoped_nsobject<CrAVCaptureDevice>)device { 379 - (void)startObserving:(base::scoped_nsobject<AVCaptureDevice>)device {
382 DCHECK(mainThreadChecker_.CalledOnValidThread()); 380 DCHECK(mainThreadChecker_.CalledOnValidThread());
383 DCHECK(device != nil); 381 DCHECK(device != nil);
384 // Skip this device if there are already observers connected to it. 382 // Skip this device if there are already observers connected to it.
385 if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) != 383 if (std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device) !=
386 monitoredDevices_.end()) { 384 monitoredDevices_.end()) {
387 return; 385 return;
388 } 386 }
389 [device addObserver:self 387 [device addObserver:self
390 forKeyPath:@"suspended" 388 forKeyPath:@"suspended"
391 options:0 389 options:0
392 context:device.get()]; 390 context:device.get()];
393 [device addObserver:self 391 [device addObserver:self
394 forKeyPath:@"connected" 392 forKeyPath:@"connected"
395 options:0 393 options:0
396 context:device.get()]; 394 context:device.get()];
397 monitoredDevices_.insert(device); 395 monitoredDevices_.insert(device);
398 } 396 }
399 397
400 - (void)stopObserving:(CrAVCaptureDevice*)device { 398 - (void)stopObserving:(AVCaptureDevice*)device {
401 DCHECK(mainThreadChecker_.CalledOnValidThread()); 399 DCHECK(mainThreadChecker_.CalledOnValidThread());
402 DCHECK(device != nil); 400 DCHECK(device != nil);
403 401
404 std::set<base::scoped_nsobject<CrAVCaptureDevice>>::iterator found = 402 std::set<base::scoped_nsobject<AVCaptureDevice>>::iterator found =
405 std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device); 403 std::find(monitoredDevices_.begin(), monitoredDevices_.end(), device);
406 DCHECK(found != monitoredDevices_.end()); 404 DCHECK(found != monitoredDevices_.end());
407 [self removeObservers:*found]; 405 [self removeObservers:*found];
408 monitoredDevices_.erase(found); 406 monitoredDevices_.erase(found);
409 } 407 }
410 408
411 - (void)clearOnDeviceChangedCallback { 409 - (void)clearOnDeviceChangedCallback {
412 DCHECK(mainThreadChecker_.CalledOnValidThread()); 410 DCHECK(mainThreadChecker_.CalledOnValidThread());
413 onDeviceChangedCallback_.Reset(); 411 onDeviceChangedCallback_.Reset();
414 } 412 }
415 413
416 - (void)removeObservers:(CrAVCaptureDevice*)device { 414 - (void)removeObservers:(AVCaptureDevice*)device {
417 DCHECK(mainThreadChecker_.CalledOnValidThread()); 415 DCHECK(mainThreadChecker_.CalledOnValidThread());
418 // Check sanity of |device| via its -observationInfo. http://crbug.com/371271. 416 // Check sanity of |device| via its -observationInfo. http://crbug.com/371271.
419 if ([device observationInfo]) { 417 if ([device observationInfo]) {
420 [device removeObserver:self 418 [device removeObserver:self
421 forKeyPath:@"suspended"]; 419 forKeyPath:@"suspended"];
422 [device removeObserver:self 420 [device removeObserver:self
423 forKeyPath:@"connected"]; 421 forKeyPath:@"connected"];
424 } 422 }
425 } 423 }
426 424
427 - (void)observeValueForKeyPath:(NSString*)keyPath 425 - (void)observeValueForKeyPath:(NSString*)keyPath
428 ofObject:(id)object 426 ofObject:(id)object
429 change:(NSDictionary*)change 427 change:(NSDictionary*)change
430 context:(void*)context { 428 context:(void*)context {
431 DCHECK(mainThreadChecker_.CalledOnValidThread()); 429 DCHECK(mainThreadChecker_.CalledOnValidThread());
432 if ([keyPath isEqual:@"suspended"]) 430 if ([keyPath isEqual:@"suspended"])
433 onDeviceChangedCallback_.Run(); 431 onDeviceChangedCallback_.Run();
434 if ([keyPath isEqual:@"connected"]) 432 if ([keyPath isEqual:@"connected"])
435 [self stopObserving:static_cast<CrAVCaptureDevice*>(context)]; 433 [self stopObserving:static_cast<AVCaptureDevice*>(context)];
436 } 434 }
437 435
438 @end // @implementation CrAVFoundationDeviceObserver 436 @end // @implementation CrAVFoundationDeviceObserver
439 437
440 namespace media { 438 namespace media {
441 439
442 DeviceMonitorMac::DeviceMonitorMac() { 440 DeviceMonitorMac::DeviceMonitorMac() {
443 // AVFoundation do not need to be fired up until the user 441 // AVFoundation do not need to be fired up until the user
444 // exercises a GetUserMedia. Bringing up either library and enumerating the 442 // exercises a GetUserMedia. Bringing up either library and enumerating the
445 // devices in the system is an operation taking in the range of hundred of ms, 443 // devices in the system is an operation taking in the range of hundred of ms,
446 // so it is triggered explicitly from MediaStreamManager::StartMonitoring(). 444 // so it is triggered explicitly from MediaStreamManager::StartMonitoring().
447 } 445 }
448 446
449 DeviceMonitorMac::~DeviceMonitorMac() {} 447 DeviceMonitorMac::~DeviceMonitorMac() {}
450 448
451 void DeviceMonitorMac::StartMonitoring( 449 void DeviceMonitorMac::StartMonitoring(
452 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) { 450 const scoped_refptr<base::SingleThreadTaskRunner>& device_task_runner) {
453 DCHECK(thread_checker_.CalledOnValidThread()); 451 DCHECK(thread_checker_.CalledOnValidThread());
454 452
455 // We're on the UI thread so let's try to initialize AVFoundation.
456 AVFoundationGlue::InitializeAVFoundation();
457
458 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458404 453 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/458404
459 // is fixed. 454 // is fixed.
460 tracked_objects::ScopedTracker tracking_profile( 455 tracked_objects::ScopedTracker tracking_profile(
461 FROM_HERE_WITH_EXPLICIT_FUNCTION( 456 FROM_HERE_WITH_EXPLICIT_FUNCTION(
462 "458404 DeviceMonitorMac::StartMonitoring::AVFoundation")); 457 "458404 DeviceMonitorMac::StartMonitoring::AVFoundation"));
463 DVLOG(1) << "Monitoring via AVFoundation"; 458 DVLOG(1) << "Monitoring via AVFoundation";
464 device_monitor_impl_.reset( 459 device_monitor_impl_.reset(
465 new AVFoundationMonitorImpl(this, device_task_runner)); 460 new AVFoundationMonitorImpl(this, device_task_runner));
466 } 461 }
467 462
468 void DeviceMonitorMac::NotifyDeviceChanged( 463 void DeviceMonitorMac::NotifyDeviceChanged(
469 base::SystemMonitor::DeviceType type) { 464 base::SystemMonitor::DeviceType type) {
470 DCHECK(thread_checker_.CalledOnValidThread()); 465 DCHECK(thread_checker_.CalledOnValidThread());
471 // TODO(xians): Remove the global variable for SystemMonitor. 466 // TODO(xians): Remove the global variable for SystemMonitor.
472 base::SystemMonitor::Get()->ProcessDevicesChanged(type); 467 base::SystemMonitor::Get()->ProcessDevicesChanged(type);
473 } 468 }
474 469
475 } // namespace media 470 } // namespace media
OLDNEW
« no previous file with comments | « media/cast/sender/h264_vt_encoder_unittest.cc ('k') | media/gpu/vt_video_encode_accelerator_mac.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698