Skip to content

Commit 8d5b424

Browse files
committed
Added compatibility WebView for API<19 with custom method for executing JS
1 parent ba3c137 commit 8d5b424

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.wordpress.android.editor;
2+
3+
import android.content.Context;
4+
import android.os.Build;
5+
import android.os.Message;
6+
import android.util.AttributeSet;
7+
import android.webkit.WebView;
8+
9+
import org.wordpress.android.util.AppLog;
10+
import org.wordpress.android.util.AppLog.T;
11+
12+
import java.lang.reflect.Field;
13+
import java.lang.reflect.InvocationTargetException;
14+
import java.lang.reflect.Method;
15+
16+
/**
17+
* <p>Compatibility <code>EditorWebView</code> for pre-Chromium WebView (API<19). Provides a custom method for executing
18+
* JavaScript, {@link #loadJavaScript(String)}, instead of {@link WebView#loadUrl(String)}. This is needed because
19+
* <code>WebView#loadUrl(String)</code> on API<19 eventually calls <code>WebViewClassic#hideSoftKeyboard()</code>,
20+
* hiding the keyboard whenever JavaScript is executed.</p>
21+
*
22+
* <p>This class uses reflection to access the normally inaccessible <code>WebViewCore#sendMessage(Message)</code>
23+
* and use it to execute JavaScript, sidestepping <code>WebView#loadUrl(String)</code> and the keyboard issue.</p>
24+
*/
25+
@SuppressWarnings("TryWithIdenticalCatches")
26+
public class EditorWebViewCompatibility extends EditorWebViewAbstract {
27+
private static final int EXECUTE_JS = 194; // WebViewCore internal JS message code
28+
29+
private Object mWebViewCore;
30+
private Method mSendMessageMethod;
31+
32+
public EditorWebViewCompatibility(Context context, AttributeSet attrs) {
33+
super(context, attrs);
34+
try {
35+
this.initReflection();
36+
} catch (ReflectionException e) {
37+
AppLog.e(T.EDITOR, e);
38+
handleReflectionFailure();
39+
}
40+
}
41+
42+
private void initReflection() throws ReflectionException {
43+
Object webViewProvider;
44+
45+
try {
46+
if (Build.VERSION.SDK_INT >= 16) {
47+
// On API >= 16, the WebViewCore instance is not defined inside WebView itself but inside a
48+
// WebViewClassic (implementation of WebViewProvider), referenced from the WebView as mProvider
49+
50+
// Access WebViewClassic object
51+
Field webViewProviderField = WebView.class.getDeclaredField("mProvider");
52+
webViewProviderField.setAccessible(true);
53+
webViewProvider = webViewProviderField.get(this);
54+
55+
// Access WebViewCore object
56+
Field webViewCoreField = webViewProvider.getClass().getDeclaredField("mWebViewCore");
57+
webViewCoreField.setAccessible(true);
58+
mWebViewCore = webViewCoreField.get(webViewProvider);
59+
} else {
60+
// On API < 16, the WebViewCore is directly accessible from the WebView
61+
62+
// Access WebViewCore object
63+
Field webViewCoreField = WebView.class.getDeclaredField("mWebViewCore");
64+
webViewCoreField.setAccessible(true);
65+
mWebViewCore = webViewCoreField.get(this);
66+
}
67+
68+
// Access WebViewCore#sendMessage(Message) method
69+
if (mWebViewCore != null) {
70+
mSendMessageMethod = mWebViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
71+
mSendMessageMethod.setAccessible(true);
72+
}
73+
} catch (NoSuchFieldException e) {
74+
throw new ReflectionException(e);
75+
} catch (NoSuchMethodException e) {
76+
throw new ReflectionException(e);
77+
} catch (IllegalAccessException e) {
78+
throw new ReflectionException(e);
79+
}
80+
}
81+
82+
private void loadJavaScript(final String javaScript) throws ReflectionException {
83+
if (mSendMessageMethod == null) {
84+
initReflection();
85+
} else {
86+
Message jsMessage = Message.obtain(null, EXECUTE_JS, javaScript);
87+
try {
88+
mSendMessageMethod.invoke(mWebViewCore, jsMessage);
89+
} catch (InvocationTargetException e) {
90+
throw new ReflectionException(e);
91+
} catch (IllegalAccessException e) {
92+
throw new ReflectionException(e);
93+
}
94+
}
95+
}
96+
97+
public void execJavaScriptFromString(String javaScript) {
98+
try {
99+
loadJavaScript(javaScript);
100+
} catch(ReflectionException e) {
101+
AppLog.e(T.EDITOR, e);
102+
handleReflectionFailure();
103+
}
104+
}
105+
106+
private void handleReflectionFailure() {
107+
// TODO: Fallback to legacy editor and pass the error to analytics
108+
}
109+
110+
public class ReflectionException extends Exception {
111+
public ReflectionException(Throwable cause) {
112+
super(cause);
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)