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

Side by Side Diff: chrome/browser/chromeos/input_method/input_method_manager_impl.cc

Issue 9999018: chrome/browser/chromeos/input_method/ refactoring [part 6 of 6] (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: review fix Created 8 years, 8 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/chromeos/input_method/input_method_manager_impl.h"
6
7 #include <algorithm> // std::find
8
9 #include "base/basictypes.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/string_util.h"
12 #include "base/stringprintf.h"
13 #include "chrome/browser/chromeos/input_method/browser_state_monitor.h"
14 #include "chrome/browser/chromeos/input_method/candidate_window.h"
15 #include "chrome/browser/chromeos/input_method/input_method_util.h"
16 #include "chrome/browser/chromeos/input_method/xkeyboard.h"
17 #include "chrome/browser/chromeos/language_preferences.h"
18 #include "ui/base/accelerators/accelerator.h"
19 #include "unicode/uloc.h"
20
21 namespace chromeos {
22 namespace input_method {
23
24 namespace {
25
26 template<typename T>
27 typename T::iterator Find(T& container, const typename T::value_type& value) {
28 return std::find(container.begin(), container.end(), value);
29 }
30
31 template<typename T>
32 typename T::const_iterator Find(const T& container,
33 const typename T::value_type& value) {
34 return std::find(container.begin(), container.end(), value);
35 }
36
37 template<typename T>
38 bool Contains(const T& container, const typename T::value_type& value) {
39 return Find(container, value) != container.end();
40 }
41
42 } // namespace
43
44 InputMethodManagerImpl::InputMethodManagerImpl()
45 : should_hide_properties_(true),
46 ignore_hotkeys_(false),
47 state_(STATE_LOGIN_SCREEN),
48 util_(GetSupportedInputMethods()) {
49 }
50
51 InputMethodManagerImpl::~InputMethodManagerImpl() {
52 if (ibus_controller_.get())
53 ibus_controller_->RemoveObserver(this);
54 if (candidate_window_controller_.get())
55 candidate_window_controller_->RemoveObserver(this);
56 }
57
58 void InputMethodManagerImpl::AddObserver(
59 InputMethodManager::Observer* observer) {
60 observers_.AddObserver(observer);
61 }
62
63 void InputMethodManagerImpl::AddCandidateWindowObserver(
64 InputMethodManager::CandidateWindowObserver* observer) {
65 candidate_window_observers_.AddObserver(observer);
66 }
67
68 void InputMethodManagerImpl::RemoveObserver(
69 InputMethodManager::Observer* observer) {
70 observers_.RemoveObserver(observer);
71 }
72
73 void InputMethodManagerImpl::RemoveCandidateWindowObserver(
74 InputMethodManager::CandidateWindowObserver* observer) {
75 candidate_window_observers_.RemoveObserver(observer);
76 }
77
78 void InputMethodManagerImpl::SetState(State new_state) {
79 const State old_state = state_;
80 state_ = new_state;
81 switch (state_) {
82 case STATE_LOGIN_SCREEN:
83 break;
84 case STATE_BROWSER_SCREEN:
85 if (old_state == STATE_LOCK_SCREEN)
86 OnScreenUnlocked();
87 break;
88 case STATE_LOCK_SCREEN:
89 OnScreenLocked();
90 break;
91 case STATE_TERMINATING:
92 ibus_controller_->Stop();
93 browser_state_monitor_.reset(); // For crbug.com/120183.
94 candidate_window_controller_.reset();
95 break;
96 }
97 }
98
99 InputMethodDescriptors*
100 InputMethodManagerImpl::GetSupportedInputMethods() const {
101 return whitelist_.GetSupportedInputMethods();
102 }
103
104 InputMethodDescriptors* InputMethodManagerImpl::GetActiveInputMethods() const {
105 InputMethodDescriptors* result = new InputMethodDescriptors;
106 // Build the active input method descriptors from the active input
107 // methods cache |active_input_method_ids_|.
108 for (size_t i = 0; i < active_input_method_ids_.size(); ++i) {
109 const std::string& input_method_id = active_input_method_ids_[i];
110 const InputMethodDescriptor* descriptor =
111 util_.GetInputMethodDescriptorFromId(input_method_id);
112 if (descriptor) {
113 result->push_back(*descriptor);
114 } else {
115 std::map<std::string, InputMethodDescriptor>::const_iterator ix =
116 extra_input_methods_.find(input_method_id);
117 if (ix != extra_input_methods_.end())
118 result->push_back(ix->second);
119 else
120 LOG(ERROR) << "Descriptor is not found for: " << input_method_id;
121 }
122 }
123 if (result->empty()) {
124 // Initially |active_input_method_ids_| is empty. browser_tests might take
125 // this path.
126 result->push_back(
127 InputMethodDescriptor::GetFallbackInputMethodDescriptor());
128 }
129 return result;
130 }
131
132 size_t InputMethodManagerImpl::GetNumActiveInputMethods() const {
133 return active_input_method_ids_.size();
134 }
135
136 void InputMethodManagerImpl::EnableLayouts(const std::string& language_code,
137 const std::string& initial_layout) {
138 if (state_ == STATE_TERMINATING)
139 return;
140
141 std::vector<std::string> candidates;
142 // Add input methods associated with the language.
143 util_.GetInputMethodIdsFromLanguageCode(language_code,
144 kKeyboardLayoutsOnly,
145 &candidates);
146 // Add the hardware keyboard as well. We should always add this so users
147 // can use the hardware keyboard on the login screen and the screen locker.
148 candidates.push_back(util_.GetHardwareInputMethodId());
149
150 std::vector<std::string> layouts;
151 // First, add the initial input method ID, if it's requested, to
152 // layouts, so it appears first on the list of active input
153 // methods at the input language status menu.
154 if (util_.IsValidInputMethodId(initial_layout) &&
155 InputMethodUtil::IsKeyboardLayout(initial_layout)) {
156 layouts.push_back(initial_layout);
157 } else if (!initial_layout.empty()) {
158 LOG(ERROR) << "EnableLayouts: ignoring non-keyboard or invalid ID: "
159 << initial_layout;
160 }
161
162 // Add candidates to layouts, while skipping duplicates.
163 for (size_t i = 0; i < candidates.size(); ++i) {
164 const std::string& candidate = candidates[i];
165 // Not efficient, but should be fine, as the two vectors are very
166 // short (2-5 items).
167 if (!Contains(layouts, candidate))
168 layouts.push_back(candidate);
169 }
170
171 active_input_method_ids_.swap(layouts);
172 ChangeInputMethod(initial_layout); // you can pass empty |initial_layout|.
173 }
174
175 bool InputMethodManagerImpl::EnableInputMethods(
176 const std::vector<std::string>& new_active_input_method_ids) {
177 if (state_ == STATE_TERMINATING)
178 return false;
179
180 // Filter unknown or obsolete IDs.
181 std::vector<std::string> new_active_input_method_ids_filtered;
182
183 for (size_t i = 0; i < new_active_input_method_ids.size(); ++i) {
184 const std::string& input_method_id = new_active_input_method_ids[i];
185 if (util_.IsValidInputMethodId(input_method_id))
186 new_active_input_method_ids_filtered.push_back(input_method_id);
187 else
188 LOG(ERROR) << "EnableInputMethods: Invalid ID: " << input_method_id;
189 }
190
191 if (new_active_input_method_ids_filtered.empty()) {
192 LOG(ERROR) << "EnableInputMethods: No valid input method ID";
193 return false;
194 }
195
196 // Copy extension IDs to |new_active_input_method_ids_filtered|. We have to
197 // keep relative order of the extension input method IDs.
198 for (size_t i = 0; i < active_input_method_ids_.size(); ++i) {
199 const std::string& input_method_id = active_input_method_ids_[i];
200 if (InputMethodUtil::IsExtensionInputMethod(input_method_id))
201 new_active_input_method_ids_filtered.push_back(input_method_id);
202 }
203 active_input_method_ids_.swap(new_active_input_method_ids_filtered);
204
205 if (ContainOnlyKeyboardLayout(active_input_method_ids_)) {
206 // Do NOT call ibus_controller_->Stop(); here to work around a crash issue
207 // at crosbug.com/27051.
208 // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443
209 // is implemented.
210 } else {
211 MaybeInitializeCandidateWindowController();
212 ibus_controller_->Start(active_input_method_ids_);
213 }
214
215 // If |current_input_method| is no longer in |active_input_method_ids_|,
216 // ChangeInputMethod() picks the first one in |active_input_method_ids_|.
217 ChangeInputMethod(current_input_method_.id());
218 return true;
219 }
220
221 bool InputMethodManagerImpl::SetInputMethodConfig(
222 const std::string& section,
223 const std::string& config_name,
224 const InputMethodConfigValue& value) {
225 DCHECK(section != language_prefs::kGeneralSectionName ||
226 config_name != language_prefs::kPreloadEnginesConfigName);
227
228 if (state_ == STATE_TERMINATING)
229 return false;
230 return ibus_controller_->SetInputMethodConfig(section, config_name, value);
231 }
232
233 void InputMethodManagerImpl::ChangeInputMethod(
234 const std::string& input_method_id) {
235 if (state_ == STATE_TERMINATING)
236 return;
237
238 std::string input_method_id_to_switch = input_method_id;
239
240 // Sanity check.
241 if (!InputMethodIsActivated(input_method_id)) {
242 scoped_ptr<InputMethodDescriptors> input_methods(GetActiveInputMethods());
243 DCHECK(!input_methods->empty());
244 input_method_id_to_switch = input_methods->at(0).id();
245 if (!input_method_id.empty()) {
246 VLOG(1) << "Can't change the current input method to "
247 << input_method_id << " since the engine is not enabled. "
248 << "Switch to " << input_method_id_to_switch << " instead.";
249 }
250 }
251
252 if (InputMethodUtil::IsKeyboardLayout(input_method_id_to_switch)) {
253 should_hide_properties_ = true;
254 FOR_EACH_OBSERVER(InputMethodManager::Observer,
255 observers_,
256 InputMethodPropertyChanged(this));
257 } else {
Seigo Nonaka 2012/04/13 09:44:22 I'm not sure about should_hide_properties_ variabl
Yusuke Sato 2012/04/16 02:11:25 probably it's possible to remove should_hide_prope
Yusuke Sato 2012/04/16 05:54:31 Removed the variable. Slightly modified IBusContro
258 ibus_controller_->ChangeInputMethod(input_method_id_to_switch);
259 }
260
261 if (current_input_method_.id() != input_method_id_to_switch) {
262 const InputMethodDescriptor* descriptor = NULL;
263 if (!InputMethodUtil::IsExtensionInputMethod(input_method_id_to_switch)) {
264 descriptor =
265 util_.GetInputMethodDescriptorFromId(input_method_id_to_switch);
266 } else {
267 std::map<std::string, InputMethodDescriptor>::const_iterator i =
268 extra_input_methods_.find(input_method_id_to_switch);
269 DCHECK(i != extra_input_methods_.end());
270 descriptor = &(i->second);
271 }
272 DCHECK(descriptor);
273
274 previous_input_method_ = current_input_method_;
275 current_input_method_ = *descriptor;
276
277 // Change the keyboard layout to a preferred layout for the input method.
278 if (!xkeyboard_->SetCurrentKeyboardLayoutByName(
279 current_input_method_.keyboard_layout())) {
280 LOG(ERROR) << "Failed to change keyboard layout to "
281 << current_input_method_.keyboard_layout();
282 }
283 }
284
285 // Update input method indicators (e.g. "US", "DV") in Chrome windows.
286 FOR_EACH_OBSERVER(InputMethodManager::Observer,
287 observers_,
288 InputMethodChanged(this));
289 }
290
291 void InputMethodManagerImpl::ActivateInputMethodProperty(
292 const std::string& key) {
293 DCHECK(!key.empty());
294 ibus_controller_->ActivateInputMethodProperty(key);
295 }
296
297 void InputMethodManagerImpl::AddInputMethodExtension(
298 const std::string& id,
299 const std::string& name,
300 const std::vector<std::string>& layouts,
301 const std::string& language) {
302 if (state_ == STATE_TERMINATING)
303 return;
304
305 if (!InputMethodUtil::IsExtensionInputMethod(id)) {
306 LOG(ERROR) << id << " is not a valid extension input method ID.";
307 return;
308 }
309
310 const std::string virtual_layouts = JoinString(layouts, ',');
311 extra_input_methods_[id] = InputMethodDescriptor(
312 whitelist_, id, name, virtual_layouts, language);
313
314 if (!Contains(active_input_method_ids_, id)) {
315 active_input_method_ids_.push_back(id);
316 } else {
317 LOG(ERROR) << "AddInputMethodExtension: alread added: "
318 << id << ", " << name;
319 // Call Start() anyway, just in case.
320 }
321
322 // Ensure that the input method daemon is running.
323 MaybeInitializeCandidateWindowController();
324 ibus_controller_->Start(active_input_method_ids_);
325 }
326
327 void InputMethodManagerImpl::RemoveInputMethodExtension(const std::string& id) {
328 if (!InputMethodUtil::IsExtensionInputMethod(id))
329 LOG(ERROR) << id << " is not a valid extension input method ID.";
330
331 std::vector<std::string>::iterator i = Find(active_input_method_ids_, id);
332 if (i != active_input_method_ids_.end())
333 active_input_method_ids_.erase(i);
334 extra_input_methods_.erase(id);
335
336 if (ContainOnlyKeyboardLayout(active_input_method_ids_)) {
337 // Do NOT call ibus_controller_->Stop(); here to work around a crash issue
338 // at crosbug.com/27051.
339 // TODO(yusukes): We can safely call Stop(); here once crosbug.com/26443
340 // is implemented.
341 }
342
343 // If |current_input_method| is no longer in |active_input_method_ids_|,
344 // switch to the first one in |active_input_method_ids_|.
345 ChangeInputMethod(current_input_method_.id());
346 }
347
348 void InputMethodManagerImpl::EnableHotkeys() {
349 ignore_hotkeys_ = false;
350 }
351
352 void InputMethodManagerImpl::DisableHotkeys() {
353 ignore_hotkeys_ = true;
354 }
355
356 bool InputMethodManagerImpl::SwitchToNextInputMethod() {
357 if (ignore_hotkeys_)
358 return false;
359
360 // Sanity checks.
361 if (active_input_method_ids_.empty()) {
362 LOG(ERROR) << "active input method is empty";
363 return false;
364 }
365 if (current_input_method_.id().empty()) {
366 LOG(ERROR) << "current_input_method_ is unknown";
367 return false;
368 }
369
370 // Find the next input method.
371 std::vector<std::string>::const_iterator iter =
372 Find(active_input_method_ids_, current_input_method_.id());
373 if (iter != active_input_method_ids_.end())
374 ++iter;
375 if (iter == active_input_method_ids_.end())
376 iter = active_input_method_ids_.begin();
377 ChangeInputMethod(*iter);
378 return true;
379 }
380
381 bool InputMethodManagerImpl::SwitchToPreviousInputMethod() {
382 if (ignore_hotkeys_)
383 return false;
384
385 // Sanity check.
386 if (active_input_method_ids_.empty()) {
387 LOG(ERROR) << "active input method is empty";
388 return false;
389 }
390
391 if (previous_input_method_.id().empty() ||
392 previous_input_method_.id() == current_input_method_.id()) {
393 return SwitchToNextInputMethod();
394 }
395
396 std::vector<std::string>::const_iterator iter =
397 Find(active_input_method_ids_, previous_input_method_.id());
398 if (iter == active_input_method_ids_.end()) {
399 // previous_input_method_ is not supported.
400 return SwitchToNextInputMethod();
401 }
402 ChangeInputMethod(*iter);
403 return true;
404 }
405
406 bool InputMethodManagerImpl::SwitchInputMethod(
407 const ui::Accelerator& accelerator) {
408 if (ignore_hotkeys_)
409 return false;
410
411 // Sanity check.
412 if (active_input_method_ids_.empty()) {
413 LOG(ERROR) << "active input method is empty";
414 return false;
415 }
416
417 // Get the list of input method ids for the |accelerator|. For example, get
418 // { "mozc-hangul", "xkb:kr:kr104:kor" } for ui::VKEY_DBE_SBCSCHAR.
419 std::vector<std::string> input_method_ids_to_switch;
420 switch (accelerator.key_code()) {
421 case ui::VKEY_CONVERT: // Henkan key on JP106 keyboard
422 input_method_ids_to_switch.push_back("mozc-jp");
423 break;
424 case ui::VKEY_NONCONVERT: // Muhenkan key on JP106 keyboard
425 input_method_ids_to_switch.push_back("xkb:jp::jpn");
426 break;
427 case ui::VKEY_DBE_SBCSCHAR: // ZenkakuHankaku key on JP106 keyboard
428 case ui::VKEY_DBE_DBCSCHAR:
429 input_method_ids_to_switch.push_back("mozc-jp");
430 input_method_ids_to_switch.push_back("xkb:jp::jpn");
431 break;
432 case ui::VKEY_HANGUL: // Hangul (or right Alt) key on Korean keyboard
433 case ui::VKEY_SPACE: // Shift+Space
434 input_method_ids_to_switch.push_back("mozc-hangul");
435 input_method_ids_to_switch.push_back("xkb:kr:kr104:kor");
436 break;
437 default:
438 NOTREACHED();
439 break;
440 }
441 if (input_method_ids_to_switch.empty()) {
442 LOG(ERROR) << "Unexpected VKEY: " << accelerator.key_code();
443 return false;
444 }
445
446 // Obtain the intersection of input_method_ids_to_switch and
447 // active_input_method_ids_. The order of IDs in active_input_method_ids_ is
448 // preserved.
449 std::vector<std::string> ids;
450 for (size_t i = 0; i < input_method_ids_to_switch.size(); ++i) {
451 const std::string& id = input_method_ids_to_switch[i];
452 if (Contains(active_input_method_ids_, id))
453 ids.push_back(id);
454 }
455 if (ids.empty()) {
456 // No input method for the accelerator is active. For example, we should
457 // just ignore VKEY_HANGUL when mozc-hangul is not active.
458 return false;
459 }
460
461 // If |current_input_method_| is not in ids, switch to ids[0]. If
462 // |current_input_method_| is ids[N], switch to ids[N+1].
463 std::vector<std::string>::const_iterator iter =
464 Find(ids, current_input_method_.id());
465 if (iter != ids.end())
466 ++iter;
467 if (iter == ids.end())
468 iter = ids.begin();
469 ChangeInputMethod(*iter);
470 return true; // consume the accelerator.
471 }
472
473 InputMethodDescriptor InputMethodManagerImpl::GetCurrentInputMethod() const {
474 if (current_input_method_.id().empty())
475 return InputMethodDescriptor::GetFallbackInputMethodDescriptor();
476 return current_input_method_;
477 }
478
479 InputMethodPropertyList
480 InputMethodManagerImpl::GetCurrentInputMethodProperties() const {
481 if (should_hide_properties_ ||
482 // This check is necessary since an IME property (e.g. for Pinyin) might
483 // be sent from ibus-daemon AFTER the current input method is switched
484 // to XKB.
485 InputMethodUtil::IsKeyboardLayout(GetCurrentInputMethod().id())) {
486 return InputMethodPropertyList();
487 }
488 return ibus_controller_->GetCurrentProperties();
489 }
490
491 XKeyboard* InputMethodManagerImpl::GetXKeyboard() {
492 return xkeyboard_.get();
493 }
494
495 InputMethodUtil* InputMethodManagerImpl::GetInputMethodUtil() {
496 return &util_;
497 }
498
499 void InputMethodManagerImpl::Init() {
500 DCHECK(!ibus_controller_.get());
501
502 browser_state_monitor_.reset(new BrowserStateMonitor(this));
503 ibus_controller_.reset(IBusController::Create());
504 xkeyboard_.reset(XKeyboard::Create(util_));
505 ibus_controller_->AddObserver(this);
506 }
507
508 void InputMethodManagerImpl::SetIBusControllerForTesting(
509 IBusController* ibus_controller) {
510 ibus_controller_.reset(ibus_controller);
511 ibus_controller_->AddObserver(this);
512 }
513
514 void InputMethodManagerImpl::SetCandidateWindowControllerForTesting(
515 CandidateWindowController* candidate_window_controller) {
516 candidate_window_controller_.reset(candidate_window_controller);
517 candidate_window_controller_->Init();
518 candidate_window_controller_->AddObserver(this);
519 }
520
521 void InputMethodManagerImpl::SetXKeyboardForTesting(XKeyboard* xkeyboard) {
522 xkeyboard_.reset(xkeyboard);
523 }
524
525 void InputMethodManagerImpl::PropertyChanged() {
526 should_hide_properties_ = ibus_controller_->GetCurrentProperties().empty();
527 FOR_EACH_OBSERVER(InputMethodManager::Observer,
528 observers_,
529 InputMethodPropertyChanged(this));
530 }
531
532 void InputMethodManagerImpl::CandidateWindowOpened() {
533 FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
534 candidate_window_observers_,
535 CandidateWindowOpened(this));
536 }
537
538 void InputMethodManagerImpl::CandidateWindowClosed() {
539 FOR_EACH_OBSERVER(InputMethodManager::CandidateWindowObserver,
540 candidate_window_observers_,
541 CandidateWindowClosed(this));
542 }
543
544 void InputMethodManagerImpl::OnScreenLocked() {
545 saved_previous_input_method_ = previous_input_method_;
546 saved_current_input_method_ = current_input_method_;
547 saved_active_input_method_ids_ = active_input_method_ids_;
548
549 const std::string hardware_keyboard_id = util_.GetHardwareInputMethodId();
550 // We'll add the hardware keyboard if it's not included in
551 // |active_input_method_list| so that the user can always use the hardware
552 // keyboard on the screen locker.
553 bool should_add_hardware_keyboard = true;
554
555 active_input_method_ids_.clear();
556 for (size_t i = 0; i < saved_active_input_method_ids_.size(); ++i) {
557 const std::string& input_method_id = saved_active_input_method_ids_[i];
558 // Skip if it's not a keyboard layout. Drop input methods including
559 // extension ones.
560 if (!InputMethodUtil::IsKeyboardLayout(input_method_id))
561 continue;
562 active_input_method_ids_.push_back(input_method_id);
563 if (input_method_id == hardware_keyboard_id)
564 should_add_hardware_keyboard = false;
565 }
566 if (should_add_hardware_keyboard)
567 active_input_method_ids_.push_back(hardware_keyboard_id);
568
569 ChangeInputMethod(current_input_method_.id());
570 }
571
572 void InputMethodManagerImpl::OnScreenUnlocked() {
573 previous_input_method_ = saved_previous_input_method_;
574 current_input_method_ = saved_current_input_method_;
575 active_input_method_ids_ = saved_active_input_method_ids_;
576
577 ChangeInputMethod(current_input_method_.id());
578 }
579
580 bool InputMethodManagerImpl::InputMethodIsActivated(
581 const std::string& input_method_id) {
582 return Contains(active_input_method_ids_, input_method_id);
583 }
584
585 bool InputMethodManagerImpl::ContainOnlyKeyboardLayout(
586 const std::vector<std::string>& value) {
587 for (size_t i = 0; i < value.size(); ++i) {
588 if (!InputMethodUtil::IsKeyboardLayout(value[i]))
589 return false;
590 }
591 return true;
592 }
593
594 void InputMethodManagerImpl::MaybeInitializeCandidateWindowController() {
595 #if !defined(USE_VIRTUAL_KEYBOARD)
596 if (candidate_window_controller_.get())
597 return;
598
599 candidate_window_controller_.reset(
600 CandidateWindowController::CreateCandidateWindowController());
601 if (candidate_window_controller_->Init())
602 candidate_window_controller_->AddObserver(this);
603 else
604 LOG(WARNING) << "Failed to initialize the candidate window controller";
605 #endif
606 }
607
608 // static
609 InputMethodManagerImpl* InputMethodManagerImpl::GetInstanceForTesting() {
610 return new InputMethodManagerImpl;
611 }
612
613 } // namespace input_method
614 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698