blob: 72d431a2183495f33b40e63162a16a438bd5d8ce [file] [log] [blame]
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;
}
}