diff options
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.java | 130 |
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; + } +} |