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