aboutsummaryrefslogtreecommitdiff
path: root/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java
diff options
context:
space:
mode:
Diffstat (limited to 'libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java')
-rw-r--r--libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java130
1 files changed, 130 insertions, 0 deletions
diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java
new file mode 100644
index 000000000..72d431a21
--- /dev/null
+++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java
@@ -0,0 +1,130 @@
+package org.wordpress.android.editor;
+
+import android.content.Context;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.webkit.WebView;
+
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * <p>Compatibility <code>EditorWebView</code> for pre-Chromium WebView (API<19). Provides a custom method for executing
+ * JavaScript, {@link #loadJavaScript(String)}, instead of {@link WebView#loadUrl(String)}. This is needed because
+ * <code>WebView#loadUrl(String)</code> on API<19 eventually calls <code>WebViewClassic#hideSoftKeyboard()</code>,
+ * hiding the keyboard whenever JavaScript is executed.</p>
+ * <p/>
+ * <p>This class uses reflection to access the normally inaccessible <code>WebViewCore#sendMessage(Message)</code>
+ * and use it to execute JavaScript, sidestepping <code>WebView#loadUrl(String)</code> and the keyboard issue.</p>
+ */
+@SuppressWarnings("TryWithIdenticalCatches")
+public class EditorWebViewCompatibility extends EditorWebViewAbstract {
+ public interface ReflectionFailureListener {
+ void onReflectionFailure(ReflectionException e);
+ }
+
+ public class ReflectionException extends Exception {
+ public ReflectionException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ private static final int EXECUTE_JS = 194; // WebViewCore internal JS message code
+
+ private Object mWebViewCore;
+ private Method mSendMessageMethod;
+
+ // Dirty static listener, but it's impossible to set the listener during the construction if we want to keep
+ // the xml layout
+ private static ReflectionFailureListener mReflectionFailureListener;
+ private boolean mReflectionSucceed = true;
+
+ public static void setReflectionFailureListener(ReflectionFailureListener reflectionFailureListener) {
+ mReflectionFailureListener = reflectionFailureListener;
+ }
+
+ public EditorWebViewCompatibility(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ try {
+ this.initReflection();
+ } catch (ReflectionException e) {
+ AppLog.e(T.EDITOR, e);
+ handleReflectionFailure(e);
+ }
+ }
+
+ private void initReflection() throws ReflectionException {
+ if (!mReflectionSucceed) {
+ // Reflection failed once already, it won't succeed on a second try
+ return;
+ }
+ Object webViewProvider;
+ try {
+ // On API >= 16, the WebViewCore instance is not defined inside WebView itself but inside a
+ // WebViewClassic (implementation of WebViewProvider), referenced from the WebView as mProvider
+
+ // Access WebViewClassic object
+ Field webViewProviderField = WebView.class.getDeclaredField("mProvider");
+ webViewProviderField.setAccessible(true);
+ webViewProvider = webViewProviderField.get(this);
+
+ // Access WebViewCore object
+ Field webViewCoreField = webViewProvider.getClass().getDeclaredField("mWebViewCore");
+ webViewCoreField.setAccessible(true);
+ mWebViewCore = webViewCoreField.get(webViewProvider);
+
+ // Access WebViewCore#sendMessage(Message) method
+ if (mWebViewCore != null) {
+ mSendMessageMethod = mWebViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
+ mSendMessageMethod.setAccessible(true);
+ }
+ } catch (NoSuchFieldException e) {
+ throw new ReflectionException(e);
+ } catch (NoSuchMethodException e) {
+ throw new ReflectionException(e);
+ } catch (IllegalAccessException e) {
+ throw new ReflectionException(e);
+ }
+ }
+
+ private void loadJavaScript(final String javaScript) throws ReflectionException {
+ if (mSendMessageMethod == null) {
+ initReflection();
+ } else {
+ Message jsMessage = Message.obtain(null, EXECUTE_JS, javaScript);
+ try {
+ mSendMessageMethod.invoke(mWebViewCore, jsMessage);
+ } catch (InvocationTargetException e) {
+ throw new ReflectionException(e);
+ } catch (IllegalAccessException e) {
+ throw new ReflectionException(e);
+ }
+ }
+ }
+
+ public void execJavaScriptFromString(String javaScript) {
+ try {
+ loadJavaScript(javaScript);
+ } catch (ReflectionException e) {
+ AppLog.e(T.EDITOR, e);
+ handleReflectionFailure(e);
+ }
+ }
+
+ private void handleReflectionFailure(ReflectionException e) {
+ if (mReflectionFailureListener != null) {
+ mReflectionFailureListener.onReflectionFailure(e);
+ }
+ mReflectionSucceed = false;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mReflectionFailureListener = null;
+ }
+}