Commit f36abfa4 authored by Anupam Snigdha's avatar Anupam Snigdha Committed by Commit Bot

[VirtualKeyboard] Android VK API implementation Part 1.

This patch has the initial implementation of the VirtualKeyboard APIs
on Android. It uses the new virtualKeyboardPolicy that will let web
authors control the virtual keyboard behavior through JS by calling
virtual keyboard's show/hide APIs. In the next patch I'll implement
the geometrychange event that will let web authors control whether
to scroll the editable element into view by default or not and also
send the size of the virtual keyboard rectangle.
It is currently behind a runtime flag - VirtualKeyboard.
Explainers and Design Doc:
https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/VirtualKeyboardPolicy/explainer.md
https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/VirtualKeyboardAPI/explainer.md
https://docs.google.com/document/d/1I0LUNxK_gP5IaNQsbYN6gL6Zpm71XzduCKkublU5o5Q/edit?usp=sharing

Bug: 856269

Change-Id: I2424e016cad34e52f9c59503f1918f5c51e4bcd3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2386461
Commit-Queue: Anupam Snigdha <snianu@microsoft.com>
Reviewed-by: default avatarChangwan Ryu <changwan@chromium.org>
Reviewed-by: default avatarMustaq Ahmed <mustaq@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#804324}
parent 1f3e9fb9
......@@ -192,7 +192,9 @@ void ImeAdapterAndroid::UpdateState(const ui::mojom::TextInputState& state) {
state.selection.end(),
state.composition ? state.composition.value().start() : -1,
state.composition ? state.composition.value().end() : -1,
state.reply_to_request);
state.reply_to_request,
static_cast<int>(state.last_vk_visibility_request),
static_cast<int>(state.vk_policy));
}
void ImeAdapterAndroid::UpdateOnTouchDown() {
......
......@@ -111,6 +111,7 @@ android_library("content_java") {
"//third_party/blink/public/mojom:mojom_platform_java",
"//ui/android:ui_no_recycler_view_java",
"//ui/android:ui_utils_java",
"//ui/base/ime/mojom:mojom_java",
"//ui/gfx/geometry/mojom:mojom_java",
"//url:gurl_java",
"//url:origin_java",
......@@ -475,6 +476,7 @@ android_library("content_javatests") {
"//ui/android:ui_java",
"//ui/android:ui_java_test_support",
"//ui/base/cursor/mojom:cursor_type_java",
"//ui/base/ime/mojom:mojom_java",
"//ui/gfx/geometry/mojom:mojom_java",
"//url:gurl_java",
]
......@@ -528,6 +530,7 @@ android_library("content_javatests") {
"javatests/src/org/chromium/content/browser/input/ImeAutocapitalizeTest.java",
"javatests/src/org/chromium/content/browser/input/ImeInputActionTest.java",
"javatests/src/org/chromium/content/browser/input/ImeInputModeTest.java",
"javatests/src/org/chromium/content/browser/input/ImeInputVKApiTest.java",
"javatests/src/org/chromium/content/browser/input/ImeLollipopTest.java",
"javatests/src/org/chromium/content/browser/input/ImePasswordTest.java",
"javatests/src/org/chromium/content/browser/input/ImeTest.java",
......
......@@ -56,6 +56,8 @@ import org.chromium.ui.base.ViewUtils;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.base.ime.TextInputAction;
import org.chromium.ui.base.ime.TextInputType;
import org.chromium.ui.mojom.VirtualKeyboardPolicy;
import org.chromium.ui.mojom.VirtualKeyboardVisibilityRequest;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
......@@ -423,12 +425,15 @@ public class ImeAdapterImpl
* @param compositionEnd The character offset of the composition end, or -1 if there is no
* selection.
* @param replyToRequest True when the update was requested by IME.
* @param lastVkVisibilityRequest VK visibility request type if show/hide APIs are called
* from JS.
* @param vkPolicy VK policy type whether it is manual or automatic.
*/
@CalledByNative
private void updateState(int textInputType, int textInputFlags, int textInputMode,
int textInputAction, boolean showIfNeeded, boolean alwaysHide, String text,
int selectionStart, int selectionEnd, int compositionStart, int compositionEnd,
boolean replyToRequest) {
boolean replyToRequest, int lastVkVisibilityRequest, int vkPolicy) {
TraceEvent.begin("ImeAdapter.updateState");
try {
if (DEBUG_LOGS) {
......@@ -487,15 +492,25 @@ public class ImeAdapterImpl
mLastCompositionStart = compositionStart;
mLastCompositionEnd = compositionEnd;
if (hide || alwaysHide) {
hideKeyboard();
} else {
if (needsRestart) restartInput();
if (showIfNeeded && focusedNodeAllowsSoftKeyboard()) {
// There is no API for us to get notified of user's dismissal of keyboard.
// Therefore, we should try to show keyboard even when text input type hasn't
// changed.
// Check for the visibility request and policy if VK APIs are enabled.
if (vkPolicy == VirtualKeyboardPolicy.MANUAL) {
// policy is manual.
if (lastVkVisibilityRequest == VirtualKeyboardVisibilityRequest.SHOW) {
showSoftKeyboard();
} else if (lastVkVisibilityRequest == VirtualKeyboardVisibilityRequest.HIDE) {
hideKeyboard();
}
} else {
if (hide || alwaysHide) {
hideKeyboard();
} else {
if (needsRestart) restartInput();
if (showIfNeeded && focusedNodeAllowsSoftKeyboard()) {
// There is no API for us to get notified of user's dismissal of keyboard.
// Therefore, we should try to show keyboard even when text input type
// hasn't changed.
showSoftKeyboard();
}
}
}
......
......@@ -56,6 +56,8 @@ class ImeActivityTestRule extends ContentShellActivityTestRule {
static final String PASSWORD_FORM_HTML = "content/test/data/android/input/password_form.html";
static final String INPUT_MODE_HTML = "content/test/data/android/input/input_mode.html";
static final String INPUT_ACTION_HTML = "content/test/data/android/input/input_action.html";
static final String INPUT_VK_API_HTML =
"content/test/data/android/input/virtual_keyboard_api.html";
private SelectionPopupControllerImpl mSelectionPopupController;
private TestCallbackHelperContainer mCallbackContainer;
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser.input;
import androidx.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
/**
* IME (input method editor) and text input tests for VK policy and show/hide APIs.
*/
@RunWith(ContentJUnit4ClassRunner.class)
@CommandLineFlags.Add({"enable-blink-features=VirtualKeyboard", "expose-internals-for-testing"})
public class ImeInputVKApiTest {
@Rule
public ImeActivityTestRule mRule = new ImeActivityTestRule();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setUp() throws Exception {
mRule.setUpForUrl(ImeActivityTestRule.INPUT_VK_API_HTML);
}
@Test
@MediumTest
@Feature({"VKShowAndHide", "Main"})
public void testKeyboardShowAndHide() throws Throwable {
mRule.assertWaitForKeyboardStatus(true);
// Hide keyboard when loading a new Url.
mRule.fullyLoadUrl(UrlUtils.getIsolatedTestFileUrl(ImeActivityTestRule.INPUT_VK_API_HTML));
mRule.assertWaitForKeyboardStatus(false);
// Show keyboard when manual policy element has focus and show API is called.
DOMUtils.clickNode(mRule.getWebContents(), "txt1");
mRule.assertWaitForKeyboardStatus(true);
// Hide keyboard once keyboard shows up and hide API is called.
final String code = "navigator.virtualKeyboard.hide()";
JavaScriptUtils.executeJavaScriptAndWaitForResult(mRule.getWebContents(), code);
mRule.assertWaitForKeyboardStatus(false);
}
@Test
@MediumTest
@Feature({"VKShowAndDontHide", "Main"})
public void testKeyboardShowAndNotHide() throws Throwable {
mRule.assertWaitForKeyboardStatus(true);
// Hide keyboard when loading a new Url.
mRule.fullyLoadUrl(UrlUtils.getIsolatedTestFileUrl(ImeActivityTestRule.INPUT_VK_API_HTML));
mRule.assertWaitForKeyboardStatus(false);
// Show keyboard when auto policy element has focus.
DOMUtils.clickNode(mRule.getWebContents(), "input_text");
mRule.assertWaitForKeyboardStatus(true);
// Don't change the state of the keyboard when policy is manual.
// Show the keyboard as the state was shown.
DOMUtils.clickNode(mRule.getWebContents(), "txt2");
mRule.assertWaitForKeyboardStatus(true);
}
@Test
@MediumTest
@Feature({"VKManualAndNotShow", "Main"})
public void testKeyboardManualAndNotShow() throws Throwable {
mRule.assertWaitForKeyboardStatus(true);
// Hide keyboard when loading a new Url.
mRule.fullyLoadUrl(UrlUtils.getIsolatedTestFileUrl(ImeActivityTestRule.INPUT_VK_API_HTML));
mRule.assertWaitForKeyboardStatus(false);
// Don't change the state of the keyboard when policy is manual.
// Hide since the original state was hidden.
DOMUtils.clickNode(mRule.getWebContents(), "txt2");
mRule.assertWaitForKeyboardStatus(false);
}
@Test
@MediumTest
@Feature({"VKManualAndNotShowAfterJSFocus", "Main"})
public void testKeyboardManualAndNotShowAfterJSFocus() throws Throwable {
mRule.assertWaitForKeyboardStatus(true);
// Hide keyboard when loading a new Url.
mRule.fullyLoadUrl(UrlUtils.getIsolatedTestFileUrl(ImeActivityTestRule.INPUT_VK_API_HTML));
mRule.assertWaitForKeyboardStatus(false);
// Don't change the state of the keyboard when policy is manual and
// script switches focus to another manual element.
DOMUtils.clickNode(mRule.getWebContents(), "txt3");
mRule.assertWaitForKeyboardStatus(false);
}
}
<!DOCTYPE html>
<!-- Note: if this page gets long (or wide) enough that it can't fit entirely on
the phone's display without scrolling, the test will start being flaky and
it will be very difficult to debug :(. -->
<html>
<head>
<meta name="viewport" content="width=device-width">
</head>
<body>
<textarea id="txt1" virtualkeyboardpolicy="manual" onfocusin="FocusIn1()"></textarea>
<textarea id="txt2" virtualkeyboardpolicy="manual"></textarea>
<textarea id="txt3" virtualkeyboardpolicy="manual" onfocusin="FocusIn2()"></textarea>
<input id="input_text" type="text"></input>
</body>
<script>
function FocusIn1() {
navigator.virtualKeyboard.show();
}
function FocusIn2() {
txt2.focus();
}
var selectionChangeEventLog = "";
var otherEventLog = "";
function addOtherEventLog(type, detail) {
if (otherEventLog.length > 0) {
otherEventLog += ',';
}
if (detail == null) {
otherEventLog += type;
} else {
otherEventLog += type + '(' + detail + ')';
}
}
function addSelectionChangeEventLog(type, detail) {
if (selectionChangeEventLog.length > 0) {
selectionChangeEventLog += ',';
}
if (detail == null) {
selectionChangeEventLog += type;
} else {
selectionChangeEventLog += type + '(' + detail + ')';
}
}
// selectionchange event is queued, so it races with the other events.
// crbug.com/628964
function getEventLogs() {
if (otherEventLog.length > 0 && selectionChangeEventLog.length > 0)
return otherEventLog + ',' + selectionChangeEventLog;
return otherEventLog + selectionChangeEventLog;
}
function clearEventLogs() {
selectionChangeEventLog = '';
otherEventLog = '';
}
function addKeyEventListener(element, event_name) {
element.addEventListener(event_name, function (e) { addOtherEventLog(event_name, e.keyCode); });
}
function addSelectionEventListener(event_name) {
// Note that listeners added to the element are not effective for now.
document.addEventListener(event_name, function (e) { addSelectionChangeEventLog(event_name, e.data); });
}
function registerListeners(element) {
addKeyEventListener(element, "keydown");
addKeyEventListener(element, "keypress");
addKeyEventListener(element, "keyup");
}
var inputText = document.getElementById("input_text");
var contenteditableEvent1 = document.getElementById("txt1");
var contenteditableEvent2 = document.getElementById("txt2");
// SelectionEventListener should be outside registerListenersAndObserver() to avoid duplication.
addSelectionEventListener("selectionchange");
registerListeners(inputText);
registerListeners(contenteditableEvent1);
registerListeners(contenteditableEvent2);
</script>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment