Skip to content

Commit d280c0c

Browse files
committed
Fix the bug that keyboard input not working on Meta Quest
1 parent b086d34 commit d280c0c

2 files changed

Lines changed: 143 additions & 125 deletions

File tree

Plugins/Android/WebAppInterface.java

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class WebAppInterface {
1919
public interface WebViewDataListener {
2020
void sendJsonData(String dataType, String data);
2121
}
22-
22+
2323
private static InputMethodManager imm;
2424

2525
private Activity activity;
@@ -32,7 +32,8 @@ public interface WebViewDataListener {
3232

3333
private WebViewDataListener webViewDataListener;
3434

35-
public WebAppInterface(Activity _activity, ViewGroup _rootView, View _defaultFocusView, WebView _webView, WebViewDataListener _webViewDataListener) {
35+
public WebAppInterface(Activity _activity, ViewGroup _rootView, View _defaultFocusView, WebView _webView,
36+
WebViewDataListener _webViewDataListener) {
3637
activity = _activity;
3738
rootView = _rootView;
3839
defaultFocusView = _defaultFocusView;
@@ -44,9 +45,8 @@ public WebAppInterface(Activity _activity, ViewGroup _rootView, View _defaultFoc
4445
public boolean onTouch(View v, MotionEvent event) {
4546
Log.d("WebView", "onTouch: EditText is null?: " + (editText == null));
4647
if (editText != null) {
47-
webView.getHandler().post( () ->
48-
webView.evaluateJavascript(WebViewJavaScriptConstants.SCRIPT__REMOVE_FOCUS, null)
49-
);
48+
webView.getHandler().post(
49+
() -> webView.evaluateJavascript(WebViewJavaScriptConstants.SCRIPT__REMOVE_FOCUS, null));
5050
}
5151
return false;
5252
}
@@ -63,7 +63,7 @@ public void onDestroy() {
6363

6464
public void reset() {
6565
Log.d("WebView", "reset");
66-
if(editText != null) {
66+
if (editText != null) {
6767
setIMMIfNeeded();
6868
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
6969
rootView.removeView(editText);
@@ -74,15 +74,14 @@ public void reset() {
7474

7575
@JavascriptInterface
7676
public void onInputFocusAcquired(String type, String currentValue) {
77-
activity.runOnUiThread( () -> {
77+
activity.runOnUiThread(() -> {
7878
Log.d("WebView", String.format("onInputFocusAcquired(type: %s, value: %s)", type, currentValue));
7979
reset();
8080

8181
int inputType = 0;
8282
String inputValue = currentValue;
8383

84-
switch (type)
85-
{
84+
switch (type) {
8685
case "text":
8786
inputType = InputType.TYPE_CLASS_TEXT;
8887
break;
@@ -118,8 +117,7 @@ public void onInputFocusAcquired(String type, String currentValue) {
118117
editText.setInputType(inputType);
119118
editText.setText(inputValue);
120119
editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
121-
editText.addTextChangedListener(new TextWatcher()
122-
{
120+
editText.addTextChangedListener(new TextWatcher() {
123121
@Override
124122
public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) {
125123
}
@@ -130,17 +128,16 @@ public void onTextChanged(CharSequence charSequence, int start, int before, int
130128

131129
@Override
132130
public void afterTextChanged(Editable editable) {
133-
webView.getHandler().post( () ->
134-
webView.evaluateJavascript(String.format(WebViewJavaScriptConstants.SCRIPT__SET_INPUT_VALUE, editable.toString()), null)
135-
);
131+
webView.getHandler().post(() -> webView.evaluateJavascript(
132+
String.format(WebViewJavaScriptConstants.SCRIPT__SET_INPUT_VALUE, editable.toString()),
133+
null));
136134
}
137135
});
138136
editText.setOnEditorActionListener((v, actionId, event) -> {
139137
boolean handled = false;
140138
if (actionId == EditorInfo.IME_ACTION_DONE) {
141-
webView.getHandler().post( () ->
142-
webView.evaluateJavascript(WebViewJavaScriptConstants.SCRIPT__REMOVE_FOCUS, null)
143-
);
139+
webView.getHandler().post(
140+
() -> webView.evaluateJavascript(WebViewJavaScriptConstants.SCRIPT__REMOVE_FOCUS, null));
144141
handled = true;
145142
}
146143
return handled;
@@ -149,7 +146,7 @@ public void afterTextChanged(Editable editable) {
149146
rootView.addView(editText);
150147
editText.setElevation(-1);
151148
editText.requestFocus();
152-
149+
153150
setIMMIfNeeded();
154151
// imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
155152
imm.showSoftInput(editText, 0);
@@ -158,13 +155,12 @@ public void afterTextChanged(Editable editable) {
158155

159156
@JavascriptInterface
160157
public void onInputFocusLost() {
161-
activity.runOnUiThread( () -> {
158+
activity.runOnUiThread(() -> {
162159
Log.d("WebView", String.format("onInputFocusLost(); EditText is null?: %b", editText == null));
163-
if (editText != null)
164-
{
160+
if (editText != null) {
165161
editText.clearFocus();
166162
defaultFocusView.requestFocus();
167-
163+
168164
setIMMIfNeeded();
169165
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
170166

@@ -180,7 +176,7 @@ public void sendJsonData(String type, String data) {
180176
}
181177

182178
private void setIMMIfNeeded() {
183-
if(imm == null) {
179+
if (imm == null) {
184180
imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
185181
}
186182
}
Lines changed: 124 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,130 @@
11
package com.t34400.webviewtexture;
22

3-
public class WebViewJavaScriptConstants
4-
{
5-
public static final String ANDROID_INTERFACE_INSTANCE_NAME = "Android";
3+
public class WebViewJavaScriptConstants {
4+
public static final String ANDROID_INTERFACE_INSTANCE_NAME = "Android";
65

7-
public static final String SCRIPT__ADD_INPUT_FOCUS_LISTENER =
8-
"if (typeof currentFocusedInput === 'undefined') {" +
9-
" currentFocusedInput = null;" +
10-
"}" +
11-
"(function() {" +
12-
" var inputs = document.querySelectorAll('input, textarea');" +
13-
" inputs.forEach(function(input) {" +
14-
" if(input._eventHandlersAdded) {" +
15-
" return;" +
16-
" }" +
17-
" input.addEventListener('focus', function() {" +
18-
" currentFocusedInput = input;" +
19-
" console.log('onInputFocusAcquired()');" +
20-
" " + ANDROID_INTERFACE_INSTANCE_NAME + ".onInputFocusAcquired(input.type, input.value);" +
21-
" });" +
22-
" input.addEventListener('blur', function() {" +
23-
" currentFocusedInput = null;" +
24-
" console.log('onInputFocusLost()');" +
25-
" " + ANDROID_INTERFACE_INSTANCE_NAME + ".onInputFocusLost();" +
26-
" });" +
27-
" input._eventHandlersAdded = true;" +
28-
" });" +
29-
"" +
30-
" function processNode(node, array) {" +
31-
" if(node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {" +
32-
" array.push(node);" +
33-
" }" +
34-
" if(node.childNodes) {" +
35-
" node.childNodes.forEach((childNode) => {" +
36-
" processNode(childNode, array);" +
37-
" });" +
38-
" }" +
39-
" }" +
40-
"" +
41-
" const mutationCallback = (mutationsList, observer) => {" +
42-
" for (const mutation of mutationsList) {" +
43-
" if (mutation.type === 'childList') {" +
44-
" const addedNodes = Array.from(mutation.addedNodes);" +
45-
" const addedInputs = [];" +
46-
" addedNodes.forEach((addedNode) => processNode(addedNode, addedInputs));" +
47-
" " +
48-
" for (const addedInput of addedInputs) {" +
49-
" addedInput.addEventListener('focus', function() {" +
50-
" currentFocusedInput = input;" +
51-
" console.log('onInputFocusAcquired()');" +
52-
" " + ANDROID_INTERFACE_INSTANCE_NAME + ".onInputFocusAcquired(addedInput.type, addedInput.value);" +
53-
" });" +
54-
" addedInput.addEventListener('blur', function() {" +
55-
" currentFocusedInput = null;" +
56-
" console.log('onInputFocusLost()');" +
57-
" " + ANDROID_INTERFACE_INSTANCE_NAME + ".onInputFocusLost();" +
58-
" });" +
59-
" addedInput._eventHandlersAdded = true;" +
60-
" }" +
61-
" " +
62-
" const removedInputs = Array.from(mutation.removedNodes).filter(node => node.tagName === 'INPUT');" +
63-
" for (const removedInput of removedInputs) {" +
64-
" if(removedInput === currentFocusedInput) {" +
65-
" currentFocusedInput = null;" +
66-
" console.log('onInputFocusLost()');" +
67-
" " + ANDROID_INTERFACE_INSTANCE_NAME + ".onInputFocusLost();" +
68-
" }" +
69-
" }" +
70-
" }" +
71-
" }" +
72-
" };" +
73-
"" +
74-
" const observer = new MutationObserver(mutationCallback);" +
75-
" const config = { childList: true, subtree: true };" +
76-
" var parentElement = document.getElementsByTagName('BODY')[0];" +
77-
" if (typeof parentElement === 'undefined') {" +
78-
" document.addEventListener('DOMContentLoaded', function() {" +
79-
" parentElement = document.getElementsByTagName('BODY')[0];" +
80-
" observer.observe(parentElement, config);" +
81-
" });" +
82-
" } else {" +
83-
" observer.observe(parentElement, config);" +
84-
" }" +
85-
"} ());";
6+
public static final String SCRIPT__ADD_INPUT_FOCUS_LISTENER = "if (typeof currentFocusedInput === 'undefined') {"
7+
+
8+
" currentFocusedInput = null;" +
9+
" canRemoveFocus = true;" +
10+
"}" +
11+
"(function() {" +
12+
" function startCooltime() {" +
13+
" canRemoveFocus = false;" +
14+
" setTimeout(() => {" +
15+
" canRemoveFocus = true;" +
16+
" }, 500);" +
17+
" }" +
18+
" var inputs = document.querySelectorAll('input, textarea');" +
19+
" inputs.forEach(function(input) {" +
20+
" if(input._eventHandlersAdded) {" +
21+
" return;" +
22+
" }" +
23+
" input.addEventListener('focus', function() {" +
24+
" currentFocusedInput = input;" +
25+
" startCooltime();" +
26+
" console.log('onInputFocusAcquired()');" +
27+
" " + ANDROID_INTERFACE_INSTANCE_NAME
28+
+ ".onInputFocusAcquired(input.type, input.value);" +
29+
" });" +
30+
" input.addEventListener('blur', function() {" +
31+
" currentFocusedInput = null;" +
32+
" console.log('onInputFocusLost()');" +
33+
" " + ANDROID_INTERFACE_INSTANCE_NAME + ".onInputFocusLost();" +
34+
" });" +
35+
" input._eventHandlersAdded = true;" +
36+
" });" +
37+
"" +
38+
" function processNode(node, array) {" +
39+
" if(node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {" +
40+
" array.push(node);" +
41+
" }" +
42+
" if(node.childNodes) {" +
43+
" node.childNodes.forEach((childNode) => {" +
44+
" processNode(childNode, array);" +
45+
" });" +
46+
" }" +
47+
" }" +
48+
"" +
49+
" const mutationCallback = (mutationsList, observer) => {" +
50+
" for (const mutation of mutationsList) {" +
51+
" if (mutation.type === 'childList') {" +
52+
" const addedNodes = Array.from(mutation.addedNodes);" +
53+
" const addedInputs = [];" +
54+
" addedNodes.forEach((addedNode) => processNode(addedNode, addedInputs));" +
55+
" " +
56+
" for (const addedInput of addedInputs) {" +
57+
" if(addedInput._eventHandlersAdded) {" +
58+
" return;" +
59+
" }" +
60+
" addedInput.addEventListener('focus', function() {" +
61+
" currentFocusedInput = addedInput;" +
62+
" startCooltime();" +
63+
" console.log('onInputFocusAcquired()');" +
64+
" " + ANDROID_INTERFACE_INSTANCE_NAME
65+
+ ".onInputFocusAcquired(addedInput.type, addedInput.value);" +
66+
" });" +
67+
" addedInput.addEventListener('blur', function() {" +
68+
" if (addedInput != currentFocusedInput) {" +
69+
" return;" +
70+
" }" +
71+
" currentFocusedInput = null;" +
72+
" console.log('onInputFocusLost()');" +
73+
" " + ANDROID_INTERFACE_INSTANCE_NAME + ".onInputFocusLost();" +
74+
" });" +
75+
" addedInput._eventHandlersAdded = true;" +
76+
" }" +
77+
" " +
78+
" const removedInputs = Array.from(mutation.removedNodes).filter(node => node.tagName === 'INPUT');"
79+
+
80+
" for (const removedInput of removedInputs) {" +
81+
" if(removedInput === currentFocusedInput) {" +
82+
" currentFocusedInput = null;" +
83+
" console.log('onInputFocusLost()');" +
84+
" " + ANDROID_INTERFACE_INSTANCE_NAME + ".onInputFocusLost();" +
85+
" }" +
86+
" }" +
87+
" }" +
88+
" }" +
89+
" };" +
90+
"" +
91+
" const observer = new MutationObserver(mutationCallback);" +
92+
" const config = { childList: true, subtree: true };" +
93+
" var parentElement = document.getElementsByTagName('BODY')[0];" +
94+
" if (typeof parentElement === 'undefined') {" +
95+
" document.addEventListener('DOMContentLoaded', function() {" +
96+
" parentElement = document.getElementsByTagName('BODY')[0];" +
97+
" observer.observe(parentElement, config);" +
98+
" });" +
99+
" } else {" +
100+
" observer.observe(parentElement, config);" +
101+
" }" +
102+
"} ());";
86103

87-
public static final String SCRIPT__SET_INPUT_VALUE =
88-
"var newValue = '%s';" +
89-
"var focusedElement = document.activeElement;" +
90-
"if (focusedElement && focusedElement.tagName === 'INPUT') {" +
91-
" var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;" +
92-
" nativeInputValueSetter.call(focusedElement, newValue);" +
93-
" focusedElement.value = newValue;" +
94-
" focusedElement.dispatchEvent(new Event('input', { bubbles: true }));" +
95-
"} else if (focusedElement && focusedElement.tagName === 'TEXTAREA') {" +
96-
" var nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;" +
97-
" nativeTextAreaValueSetter.call(focusedElement, newValue);" +
98-
" focusedElement.value = newValue;" +
99-
" focusedElement.dispatchEvent(new Event('input', { bubbles: true }));" +
100-
"}";
104+
public static final String SCRIPT__SET_INPUT_VALUE = "var newValue = '%s';" +
105+
"var focusedElement = document.activeElement;" +
106+
"if (focusedElement && focusedElement.tagName === 'INPUT') {" +
107+
" var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;"
108+
+
109+
" nativeInputValueSetter.call(focusedElement, newValue);" +
110+
" focusedElement.value = newValue;" +
111+
" focusedElement.dispatchEvent(new Event('input', { bubbles: true }));" +
112+
"} else if (focusedElement && focusedElement.tagName === 'TEXTAREA') {" +
113+
" var nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;"
114+
+
115+
" nativeTextAreaValueSetter.call(focusedElement, newValue);" +
116+
" focusedElement.value = newValue;" +
117+
" focusedElement.dispatchEvent(new Event('input', { bubbles: true }));" +
118+
"}";
101119

102-
public static final String SCRIPT__REMOVE_FOCUS =
103-
"var focusedElement = document.activeElement;" +
104-
"if (focusedElement && (focusedElement.tagName === 'INPUT' || focusedElement.tagName === 'TEXTAREA')) {" +
105-
" document.body.dispatchEvent(new KeyboardEvent('keydown', { bubbles:true, cancelable: true, key: 'Enter' }));" +
106-
" focusedElement.blur();" +
107-
"}";
120+
public static final String SCRIPT__REMOVE_FOCUS = "var focusedElement = document.activeElement;" +
121+
"if (!canRemoveFocus) {" +
122+
" return;" +
123+
"}" +
124+
"if (focusedElement && (focusedElement.tagName === 'INPUT' || focusedElement.tagName === 'TEXTAREA')) {"
125+
+
126+
" document.body.dispatchEvent(new KeyboardEvent('keydown', { bubbles:true, cancelable: true, key: 'Enter' }));"
127+
+
128+
" focusedElement.blur();" +
129+
"}";
108130
}

0 commit comments

Comments
 (0)