| 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; |
| } |
| } |