1 // Copyright (c) 2013 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 "ui/keyboard/keyboard_util.h"
6
7 #include <string>
8
9 #include "base/command_line.h"
10 #include "base/lazy_instance.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/string16.h"
14 #include "grit/keyboard_resources.h"
15 #include "grit/keyboard_resources_map.h"
16 #include "ui/aura/client/aura_constants.h"
17 #include "ui/aura/window_tree_host.h"
18 #include "ui/base/ime/input_method.h"
19 #include "ui/base/ime/text_input_client.h"
20 #include "ui/events/event_processor.h"
21 #include "ui/keyboard/keyboard_switches.h"
22 #include "url/gurl.h"
23
24 namespace {
25
26 const char kKeyDown[] ="keydown";
27 const char kKeyUp[] = "keyup";
28
SendProcessKeyEvent(ui::EventType type,aura::WindowTreeHost * host)29 void SendProcessKeyEvent(ui::EventType type,
30 aura::WindowTreeHost* host) {
31 ui::KeyEvent event(type, ui::VKEY_PROCESSKEY, ui::EF_NONE);
32 event.SetTranslated(true);
33 ui::EventDispatchDetails details =
34 host->event_processor()->OnEventFromSource(&event);
35 CHECK(!details.dispatcher_destroyed);
36 }
37
38 base::LazyInstance<base::Time> g_keyboard_load_time_start =
39 LAZY_INSTANCE_INITIALIZER;
40
41 bool g_accessibility_keyboard_enabled = false;
42
43 base::LazyInstance<GURL> g_override_content_url = LAZY_INSTANCE_INITIALIZER;
44
45 bool g_touch_keyboard_enabled = false;
46
47 keyboard::KeyboardOverscrolOverride g_keyboard_overscroll_override =
48 keyboard::KEYBOARD_OVERSCROLL_OVERRIDE_NONE;
49
50 keyboard::KeyboardShowOverride g_keyboard_show_override =
51 keyboard::KEYBOARD_SHOW_OVERRIDE_NONE;
52
53 } // namespace
54
55 namespace keyboard {
56
DefaultKeyboardBoundsFromWindowBounds(const gfx::Rect & window_bounds)57 gfx::Rect DefaultKeyboardBoundsFromWindowBounds(
58 const gfx::Rect& window_bounds) {
59 // Initialize default keyboard height to 0. The keyboard window height should
60 // only be set by window.resizeTo in virtual keyboard web contents. Otherwise,
61 // the default height may conflict with the new height and causing some
62 // strange animation issues.
63 return KeyboardBoundsFromWindowBounds(window_bounds, 0);
64 }
65
KeyboardBoundsFromWindowBounds(const gfx::Rect & window_bounds,int keyboard_height)66 gfx::Rect KeyboardBoundsFromWindowBounds(const gfx::Rect& window_bounds,
67 int keyboard_height) {
68 return gfx::Rect(
69 window_bounds.x(),
70 window_bounds.bottom() - keyboard_height,
71 window_bounds.width(),
72 keyboard_height);
73 }
74
SetAccessibilityKeyboardEnabled(bool enabled)75 void SetAccessibilityKeyboardEnabled(bool enabled) {
76 g_accessibility_keyboard_enabled = enabled;
77 }
78
GetAccessibilityKeyboardEnabled()79 bool GetAccessibilityKeyboardEnabled() {
80 return g_accessibility_keyboard_enabled;
81 }
82
SetTouchKeyboardEnabled(bool enabled)83 void SetTouchKeyboardEnabled(bool enabled) {
84 g_touch_keyboard_enabled = enabled;
85 }
86
GetTouchKeyboardEnabled()87 bool GetTouchKeyboardEnabled() {
88 return g_touch_keyboard_enabled;
89 }
90
GetKeyboardLayout()91 std::string GetKeyboardLayout() {
92 // TODO(bshe): layout string is currently hard coded. We should use more
93 // standard keyboard layouts.
94 return GetAccessibilityKeyboardEnabled() ? "system-qwerty" : "qwerty";
95 }
96
IsKeyboardEnabled()97 bool IsKeyboardEnabled() {
98 // Accessibility setting prioritized over policy setting.
99 if (g_accessibility_keyboard_enabled)
100 return true;
101 // Policy strictly disables showing a virtual keyboard.
102 if (g_keyboard_show_override == keyboard::KEYBOARD_SHOW_OVERRIDE_DISABLED)
103 return false;
104 // Check if any of the flags are enabled.
105 return CommandLine::ForCurrentProcess()->HasSwitch(
106 switches::kEnableVirtualKeyboard) ||
107 g_touch_keyboard_enabled ||
108 (g_keyboard_show_override == keyboard::KEYBOARD_SHOW_OVERRIDE_ENABLED);
109 }
110
IsKeyboardOverscrollEnabled()111 bool IsKeyboardOverscrollEnabled() {
112 if (!IsKeyboardEnabled())
113 return false;
114
115 // Users of the accessibility on-screen keyboard are likely to be using mouse
116 // input, which may interfere with overscrolling.
117 if (g_accessibility_keyboard_enabled)
118 return false;
119
120 // If overscroll enabled override is set, use it instead. Currently
121 // login / out-of-box disable keyboard overscroll. http://crbug.com/363635
122 if (g_keyboard_overscroll_override != KEYBOARD_OVERSCROLL_OVERRIDE_NONE) {
123 return g_keyboard_overscroll_override ==
124 KEYBOARD_OVERSCROLL_OVERRIDE_ENABLED;
125 }
126
127 if (CommandLine::ForCurrentProcess()->HasSwitch(
128 switches::kDisableVirtualKeyboardOverscroll)) {
129 return false;
130 }
131 return true;
132 }
133
SetKeyboardOverscrollOverride(KeyboardOverscrolOverride override)134 void SetKeyboardOverscrollOverride(KeyboardOverscrolOverride override) {
135 g_keyboard_overscroll_override = override;
136 }
137
SetKeyboardShowOverride(KeyboardShowOverride override)138 void SetKeyboardShowOverride(KeyboardShowOverride override) {
139 g_keyboard_show_override = override;
140 }
141
IsInputViewEnabled()142 bool IsInputViewEnabled() {
143 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableInputView))
144 return true;
145 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableInputView))
146 return false;
147 // Default value if no command line flags specified.
148 return true;
149 }
150
IsExperimentalInputViewEnabled()151 bool IsExperimentalInputViewEnabled() {
152 if (CommandLine::ForCurrentProcess()->HasSwitch(
153 switches::kEnableExperimentalInputViewFeatures)) {
154 return true;
155 }
156 return false;
157 }
158
InsertText(const base::string16 & text,aura::Window * root_window)159 bool InsertText(const base::string16& text, aura::Window* root_window) {
160 if (!root_window)
161 return false;
162
163 ui::InputMethod* input_method = root_window->GetProperty(
164 aura::client::kRootWindowInputMethodKey);
165 if (!input_method)
166 return false;
167
168 ui::TextInputClient* tic = input_method->GetTextInputClient();
169 if (!tic || tic->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE)
170 return false;
171
172 tic->InsertText(text);
173
174 return true;
175 }
176
177 // TODO(varunjain): It would be cleaner to have something in the
178 // ui::TextInputClient interface, say MoveCaretInDirection(). The code in
179 // here would get the ui::InputMethod from the root_window, and the
180 // ui::TextInputClient from that (see above in InsertText()).
MoveCursor(int swipe_direction,int modifier_flags,aura::WindowTreeHost * host)181 bool MoveCursor(int swipe_direction,
182 int modifier_flags,
183 aura::WindowTreeHost* host) {
184 if (!host)
185 return false;
186 ui::KeyboardCode codex = ui::VKEY_UNKNOWN;
187 ui::KeyboardCode codey = ui::VKEY_UNKNOWN;
188 if (swipe_direction & kCursorMoveRight)
189 codex = ui::VKEY_RIGHT;
190 else if (swipe_direction & kCursorMoveLeft)
191 codex = ui::VKEY_LEFT;
192
193 if (swipe_direction & kCursorMoveUp)
194 codey = ui::VKEY_UP;
195 else if (swipe_direction & kCursorMoveDown)
196 codey = ui::VKEY_DOWN;
197
198 // First deal with the x movement.
199 if (codex != ui::VKEY_UNKNOWN) {
200 ui::KeyEvent press_event(ui::ET_KEY_PRESSED, codex, modifier_flags);
201 ui::EventDispatchDetails details =
202 host->event_processor()->OnEventFromSource(&press_event);
203 CHECK(!details.dispatcher_destroyed);
204 ui::KeyEvent release_event(ui::ET_KEY_RELEASED, codex, modifier_flags);
205 details = host->event_processor()->OnEventFromSource(&release_event);
206 CHECK(!details.dispatcher_destroyed);
207 }
208
209 // Then deal with the y movement.
210 if (codey != ui::VKEY_UNKNOWN) {
211 ui::KeyEvent press_event(ui::ET_KEY_PRESSED, codey, modifier_flags);
212 ui::EventDispatchDetails details =
213 host->event_processor()->OnEventFromSource(&press_event);
214 CHECK(!details.dispatcher_destroyed);
215 ui::KeyEvent release_event(ui::ET_KEY_RELEASED, codey, modifier_flags);
216 details = host->event_processor()->OnEventFromSource(&release_event);
217 CHECK(!details.dispatcher_destroyed);
218 }
219 return true;
220 }
221
SendKeyEvent(const std::string type,int key_value,int key_code,std::string key_name,int modifiers,aura::WindowTreeHost * host)222 bool SendKeyEvent(const std::string type,
223 int key_value,
224 int key_code,
225 std::string key_name,
226 int modifiers,
227 aura::WindowTreeHost* host) {
228 ui::EventType event_type = ui::ET_UNKNOWN;
229 if (type == kKeyDown)
230 event_type = ui::ET_KEY_PRESSED;
231 else if (type == kKeyUp)
232 event_type = ui::ET_KEY_RELEASED;
233 if (event_type == ui::ET_UNKNOWN)
234 return false;
235
236 ui::KeyboardCode code = static_cast<ui::KeyboardCode>(key_code);
237
238 if (code == ui::VKEY_UNKNOWN) {
239 // Handling of special printable characters (e.g. accented characters) for
240 // which there is no key code.
241 if (event_type == ui::ET_KEY_RELEASED) {
242 ui::InputMethod* input_method = host->window()->GetProperty(
243 aura::client::kRootWindowInputMethodKey);
244 if (!input_method)
245 return false;
246
247 ui::TextInputClient* tic = input_method->GetTextInputClient();
248
249 SendProcessKeyEvent(ui::ET_KEY_PRESSED, host);
250 tic->InsertChar(static_cast<uint16>(key_value), ui::EF_NONE);
251 SendProcessKeyEvent(ui::ET_KEY_RELEASED, host);
252 }
253 } else {
254 if (event_type == ui::ET_KEY_RELEASED) {
255 // The number of key press events seen since the last backspace.
256 static int keys_seen = 0;
257 if (code == ui::VKEY_BACK) {
258 // Log the rough lengths of characters typed between backspaces. This
259 // metric will be used to determine the error rate for the keyboard.
260 UMA_HISTOGRAM_CUSTOM_COUNTS(
261 "VirtualKeyboard.KeystrokesBetweenBackspaces",
262 keys_seen, 1, 1000, 50);
263 keys_seen = 0;
264 } else {
265 ++keys_seen;
266 }
267 }
268
269 ui::KeyEvent event(event_type, code, key_name, modifiers);
270 ui::EventDispatchDetails details =
271 host->event_processor()->OnEventFromSource(&event);
272 CHECK(!details.dispatcher_destroyed);
273 }
274 return true;
275 }
276
MarkKeyboardLoadStarted()277 void MarkKeyboardLoadStarted() {
278 if (!g_keyboard_load_time_start.Get().ToInternalValue())
279 g_keyboard_load_time_start.Get() = base::Time::Now();
280 }
281
MarkKeyboardLoadFinished()282 void MarkKeyboardLoadFinished() {
283 // Possible to get a load finished without a start if navigating directly to
284 // chrome://keyboard.
285 if (!g_keyboard_load_time_start.Get().ToInternalValue())
286 return;
287
288 // It should not be possible to finish loading the keyboard without starting
289 // to load it first.
290 DCHECK(g_keyboard_load_time_start.Get().ToInternalValue());
291
292 static bool logged = false;
293 if (!logged) {
294 // Log the delta only once.
295 UMA_HISTOGRAM_TIMES(
296 "VirtualKeyboard.FirstLoadTime",
297 base::Time::Now() - g_keyboard_load_time_start.Get());
298 logged = true;
299 }
300 }
301
GetKeyboardExtensionResources(size_t * size)302 const GritResourceMap* GetKeyboardExtensionResources(size_t* size) {
303 // This looks a lot like the contents of a resource map; however it is
304 // necessary to have a custom path for the extension path, so the resource
305 // map cannot be used directly.
306 static const GritResourceMap kKeyboardResources[] = {
307 {"keyboard/layouts/function-key-row.html", IDR_KEYBOARD_FUNCTION_KEY_ROW},
308 {"keyboard/images/back.svg", IDR_KEYBOARD_IMAGES_BACK},
309 {"keyboard/images/backspace.png", IDR_KEYBOARD_IMAGES_BACKSPACE},
310 {"keyboard/images/brightness-down.svg",
311 IDR_KEYBOARD_IMAGES_BRIGHTNESS_DOWN},
312 {"keyboard/images/brightness-up.svg", IDR_KEYBOARD_IMAGES_BRIGHTNESS_UP},
313 {"keyboard/images/change-window.svg", IDR_KEYBOARD_IMAGES_CHANGE_WINDOW},
314 {"keyboard/images/down.svg", IDR_KEYBOARD_IMAGES_DOWN},
315 {"keyboard/images/forward.svg", IDR_KEYBOARD_IMAGES_FORWARD},
316 {"keyboard/images/fullscreen.svg", IDR_KEYBOARD_IMAGES_FULLSCREEN},
317 {"keyboard/images/hide-keyboard.png", IDR_KEYBOARD_IMAGES_HIDE_KEYBOARD},
318 {"keyboard/images/keyboard.svg", IDR_KEYBOARD_IMAGES_KEYBOARD},
319 {"keyboard/images/left.svg", IDR_KEYBOARD_IMAGES_LEFT},
320 {"keyboard/images/microphone.svg", IDR_KEYBOARD_IMAGES_MICROPHONE},
321 {"keyboard/images/microphone-green.svg",
322 IDR_KEYBOARD_IMAGES_MICROPHONE_GREEN},
323 {"keyboard/images/mute.svg", IDR_KEYBOARD_IMAGES_MUTE},
324 {"keyboard/images/reload.svg", IDR_KEYBOARD_IMAGES_RELOAD},
325 {"keyboard/images/return.png", IDR_KEYBOARD_IMAGES_RETURN},
326 {"keyboard/images/right.svg", IDR_KEYBOARD_IMAGES_RIGHT},
327 {"keyboard/images/search.png", IDR_KEYBOARD_IMAGES_SEARCH},
328 {"keyboard/images/shift.png", IDR_KEYBOARD_IMAGES_SHIFT},
329 {"keyboard/images/shutdown.svg", IDR_KEYBOARD_IMAGES_SHUTDOWN},
330 {"keyboard/images/tab.png", IDR_KEYBOARD_IMAGES_TAB},
331 {"keyboard/images/up.svg", IDR_KEYBOARD_IMAGES_UP},
332 {"keyboard/images/volume-down.svg", IDR_KEYBOARD_IMAGES_VOLUME_DOWN},
333 {"keyboard/images/volume-up.svg", IDR_KEYBOARD_IMAGES_VOLUME_UP},
334 {"keyboard/index.html", IDR_KEYBOARD_INDEX},
335 {"keyboard/keyboard.js", IDR_KEYBOARD_JS},
336 {"keyboard/keyboard_mojo.js", IDR_KEYBOARD_MOJO_JS},
337 {"keyboard/layouts/numeric.html", IDR_KEYBOARD_LAYOUTS_NUMERIC},
338 {"keyboard/layouts/qwerty.html", IDR_KEYBOARD_LAYOUTS_QWERTY},
339 {"keyboard/layouts/system-qwerty.html",
340 IDR_KEYBOARD_LAYOUTS_SYSTEM_QWERTY},
341 {"keyboard/layouts/spacebar-row.html", IDR_KEYBOARD_SPACEBAR_ROW},
342 {"keyboard/manifest.json", IDR_KEYBOARD_MANIFEST},
343 {"keyboard/main.css", IDR_KEYBOARD_MAIN_CSS},
344 {"keyboard/polymer_loader.js", IDR_KEYBOARD_POLYMER_LOADER},
345 {"keyboard/roboto_bold.ttf", IDR_KEYBOARD_ROBOTO_BOLD_TTF},
346 {"keyboard/sounds/keypress-delete.wav",
347 IDR_KEYBOARD_SOUNDS_KEYPRESS_DELETE},
348 {"keyboard/sounds/keypress-return.wav",
349 IDR_KEYBOARD_SOUNDS_KEYPRESS_RETURN},
350 {"keyboard/sounds/keypress-spacebar.wav",
351 IDR_KEYBOARD_SOUNDS_KEYPRESS_SPACEBAR},
352 {"keyboard/sounds/keypress-standard.wav",
353 IDR_KEYBOARD_SOUNDS_KEYPRESS_STANDARD},
354 };
355 static const size_t kKeyboardResourcesSize = arraysize(kKeyboardResources);
356 *size = kKeyboardResourcesSize;
357 return kKeyboardResources;
358 }
359
SetOverrideContentUrl(const GURL & url)360 void SetOverrideContentUrl(const GURL& url) {
361 g_override_content_url.Get() = url;
362 }
363
GetOverrideContentUrl()364 const GURL& GetOverrideContentUrl() {
365 return g_override_content_url.Get();
366 }
367
LogKeyboardControlEvent(KeyboardControlEvent event)368 void LogKeyboardControlEvent(KeyboardControlEvent event) {
369 UMA_HISTOGRAM_ENUMERATION(
370 "VirtualKeyboard.KeyboardControlEvent",
371 event,
372 keyboard::KEYBOARD_CONTROL_MAX);
373 }
374
375 } // namespace keyboard
376