From ef8b078dc220ab3fb30e786b58efc13af406f9dd Mon Sep 17 00:00:00 2001 From: Alex Forcier Date: Fri, 24 Apr 2015 18:14:05 -0400 Subject: [PATCH 1/6] Added EditorWebViewAbstract and replaced EditorWebView usage in EditorFragment --- .../android/editor/EditorFragment.java | 4 ++-- .../android/editor/EditorWebView.java | 16 +--------------- .../android/editor/EditorWebViewAbstract.java | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java index 51b044749bb0..dcd03bff9769 100644 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java @@ -40,7 +40,7 @@ public class EditorFragment extends EditorFragmentAbstract implements View.OnCli private String mParamContent; private Activity mActivity; - private EditorWebView mWebView; + private EditorWebViewAbstract mWebView; private final Map mTagToggleButtonMap = new HashMap<>(); @@ -69,7 +69,7 @@ public void onCreate(Bundle savedInstanceState) { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_editor, container, false); - mWebView = (EditorWebView) view.findViewById(R.id.webview); + mWebView = (EditorWebViewAbstract) view.findViewById(R.id.webview); initWebView(); ToggleButton boldButton = (ToggleButton) view.findViewById(R.id.bold); diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java index 67dc35c2ddc4..80bbc67e69b1 100644 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java @@ -2,27 +2,13 @@ import android.content.Context; import android.util.AttributeSet; -import android.webkit.WebView; -public class EditorWebView extends WebView { - - public EditorWebView(Context context) { - super(context); - } +public class EditorWebView extends EditorWebViewAbstract { public EditorWebView(Context context, AttributeSet attrs) { super(context, attrs); } - public EditorWebView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public boolean onCheckIsTextEditor() { - return true; - } - public void execJavaScriptFromString(String javaScript) { this.loadUrl("javascript:" + javaScript); } diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java new file mode 100644 index 000000000000..24a3f6604ab9 --- /dev/null +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java @@ -0,0 +1,18 @@ +package org.wordpress.android.editor; + +import android.content.Context; +import android.util.AttributeSet; +import android.webkit.WebView; + +public abstract class EditorWebViewAbstract extends WebView { + public abstract void execJavaScriptFromString(String javaScript); + + public EditorWebViewAbstract(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } +} From b4a21f47fe534bc50d6a1fe7e3358c6611f5bc89 Mon Sep 17 00:00:00 2001 From: Alex Forcier Date: Fri, 24 Apr 2015 18:27:45 -0400 Subject: [PATCH 2/6] Modified EditorWebView to use WebView.evaluateJavascript on API>=19 --- .../java/org/wordpress/android/editor/EditorWebView.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java index 80bbc67e69b1..0d67adbb0645 100644 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java @@ -1,6 +1,7 @@ package org.wordpress.android.editor; import android.content.Context; +import android.os.Build; import android.util.AttributeSet; public class EditorWebView extends EditorWebViewAbstract { @@ -10,7 +11,11 @@ public EditorWebView(Context context, AttributeSet attrs) { } public void execJavaScriptFromString(String javaScript) { - this.loadUrl("javascript:" + javaScript); + if (Build.VERSION.SDK_INT >= 19) { + this.evaluateJavascript(javaScript, null); + } else { + this.loadUrl("javascript:" + javaScript); + } } } \ No newline at end of file From ba3c137c00607aa5c8413c922b12a02dfaedb55a Mon Sep 17 00:00:00 2001 From: Alex Forcier Date: Sat, 25 Apr 2015 07:13:56 -0400 Subject: [PATCH 3/6] Pulled the EditorWebView layout element into a separate file - Now using distinct editor resources for API 19+ and for older APIs --- WordPressEditor/src/main/res/layout-v19/editor_webview.xml | 6 ++++++ WordPressEditor/src/main/res/layout/editor_webview.xml | 6 ++++++ WordPressEditor/src/main/res/layout/fragment_editor.xml | 7 +++---- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 WordPressEditor/src/main/res/layout-v19/editor_webview.xml create mode 100644 WordPressEditor/src/main/res/layout/editor_webview.xml diff --git a/WordPressEditor/src/main/res/layout-v19/editor_webview.xml b/WordPressEditor/src/main/res/layout-v19/editor_webview.xml new file mode 100644 index 000000000000..fb826c2ff754 --- /dev/null +++ b/WordPressEditor/src/main/res/layout-v19/editor_webview.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/WordPressEditor/src/main/res/layout/editor_webview.xml b/WordPressEditor/src/main/res/layout/editor_webview.xml new file mode 100644 index 000000000000..b02cf21daeff --- /dev/null +++ b/WordPressEditor/src/main/res/layout/editor_webview.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/WordPressEditor/src/main/res/layout/fragment_editor.xml b/WordPressEditor/src/main/res/layout/fragment_editor.xml index a573dfe74b40..76eb9ec29e2b 100644 --- a/WordPressEditor/src/main/res/layout/fragment_editor.xml +++ b/WordPressEditor/src/main/res/layout/fragment_editor.xml @@ -4,10 +4,9 @@ android:layout_height="match_parent" tools:context="org.wordpress.android.editor.EditorFragment"> - + Date: Sun, 26 Apr 2015 22:13:54 -0400 Subject: [PATCH 4/6] Added compatibility WebView for API<19 with custom method for executing JS --- .../editor/EditorWebViewCompatibility.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java new file mode 100644 index 000000000000..714f87ac8763 --- /dev/null +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java @@ -0,0 +1,115 @@ +package org.wordpress.android.editor; + +import android.content.Context; +import android.os.Build; +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; + +/** + *

Compatibility EditorWebView 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 + * WebView#loadUrl(String) on API<19 eventually calls WebViewClassic#hideSoftKeyboard(), + * hiding the keyboard whenever JavaScript is executed.

+ * + *

This class uses reflection to access the normally inaccessible WebViewCore#sendMessage(Message) + * and use it to execute JavaScript, sidestepping WebView#loadUrl(String) and the keyboard issue.

+ */ +@SuppressWarnings("TryWithIdenticalCatches") +public class EditorWebViewCompatibility extends EditorWebViewAbstract { + private static final int EXECUTE_JS = 194; // WebViewCore internal JS message code + + private Object mWebViewCore; + private Method mSendMessageMethod; + + public EditorWebViewCompatibility(Context context, AttributeSet attrs) { + super(context, attrs); + try { + this.initReflection(); + } catch (ReflectionException e) { + AppLog.e(T.EDITOR, e); + handleReflectionFailure(); + } + } + + private void initReflection() throws ReflectionException { + Object webViewProvider; + + try { + if (Build.VERSION.SDK_INT >= 16) { + // 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); + } else { + // On API < 16, the WebViewCore is directly accessible from the WebView + + // Access WebViewCore object + Field webViewCoreField = WebView.class.getDeclaredField("mWebViewCore"); + webViewCoreField.setAccessible(true); + mWebViewCore = webViewCoreField.get(this); + } + + // 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(); + } + } + + private void handleReflectionFailure() { + // TODO: Fallback to legacy editor and pass the error to analytics + } + + public class ReflectionException extends Exception { + public ReflectionException(Throwable cause) { + super(cause); + } + } +} \ No newline at end of file From 0699bb64ef523c7047d0d8bb89b12daea86d7427 Mon Sep 17 00:00:00 2001 From: Alex Forcier Date: Sun, 26 Apr 2015 22:26:11 -0400 Subject: [PATCH 5/6] Suppressed NewApi lint check for EditorWebView.execJavaScriptFromString --- .../main/java/org/wordpress/android/editor/EditorWebView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java index 0d67adbb0645..f9daf0485b09 100644 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebView.java @@ -1,5 +1,6 @@ package org.wordpress.android.editor; +import android.annotation.SuppressLint; import android.content.Context; import android.os.Build; import android.util.AttributeSet; @@ -10,6 +11,7 @@ public EditorWebView(Context context, AttributeSet attrs) { super(context, attrs); } + @SuppressLint("NewApi") public void execJavaScriptFromString(String javaScript) { if (Build.VERSION.SDK_INT >= 19) { this.evaluateJavascript(javaScript, null); From 2d7c9c4d8375227f592182c2ae6e56efb87cf477 Mon Sep 17 00:00:00 2001 From: Alex Forcier Date: Mon, 27 Apr 2015 10:28:36 -0400 Subject: [PATCH 6/6] Moved some WebView config code from EditorFragment to EditorWebViewAbstract --- .../android/editor/EditorFragment.java | 33 +-------------- .../android/editor/EditorWebViewAbstract.java | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java index 1669819cf623..09e3e9dc2d11 100755 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java @@ -4,17 +4,11 @@ import android.app.Activity; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; import android.text.Spanned; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.webkit.ConsoleMessage; -import android.webkit.JsResult; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.ToggleButton; import com.android.volley.toolbox.ImageLoader; @@ -79,7 +73,7 @@ public void onCreate(Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_editor, container, false); mWebView = (EditorWebViewAbstract) view.findViewById(R.id.webview); - initWebView(); + initJsEditor(); ToggleButton mediaButton = (ToggleButton) view.findViewById(R.id.format_bar_button_media); mTagToggleButtonMap.put(TAG_FORMAT_BAR_BUTTON_MEDIA, mediaButton); @@ -117,30 +111,7 @@ public void onDetach() { super.onDetach(); } - @SuppressLint("SetJavaScriptEnabled") - private void initWebView() { - WebSettings webSettings = mWebView.getSettings(); - webSettings.setJavaScriptEnabled(true); - webSettings.setDefaultTextEncodingName("utf-8"); - mWebView.setWebViewClient(new WebViewClient() { - public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - AppLog.e(T.EDITOR, description); - } - }); - mWebView.setWebChromeClient(new WebChromeClient() { - @Override - public boolean onConsoleMessage(@NonNull ConsoleMessage cm) { - AppLog.d(T.EDITOR, cm.message() + " -- From line " + cm.lineNumber() + " of " + cm.sourceId()); - return true; - } - - @Override - public boolean onJsAlert(WebView view, String url, String message, JsResult result) { - AppLog.d(T.EDITOR, message); - return true; - } - }); - + private void initJsEditor() { String htmlEditor = Utils.getHtmlFromFile(mActivity, "android-editor.html"); mWebView.addJavascriptInterface(new JsCallbackReceiver(this), JS_CALLBACK_HANDLER); diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java index 24a3f6604ab9..26973d755f66 100644 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewAbstract.java @@ -1,14 +1,54 @@ package org.wordpress.android.editor; +import android.annotation.SuppressLint; import android.content.Context; +import android.support.annotation.NonNull; import android.util.AttributeSet; +import android.webkit.ConsoleMessage; +import android.webkit.JsResult; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; import android.webkit.WebView; +import android.webkit.WebViewClient; +import org.wordpress.android.util.AppLog; + +/** + * A text editor WebView with support for JavaScript execution. + */ public abstract class EditorWebViewAbstract extends WebView { public abstract void execJavaScriptFromString(String javaScript); public EditorWebViewAbstract(Context context, AttributeSet attrs) { super(context, attrs); + configureWebView(); + } + + @SuppressLint("SetJavaScriptEnabled") + private void configureWebView() { + WebSettings webSettings = this.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setDefaultTextEncodingName("utf-8"); + + this.setWebViewClient(new WebViewClient() { + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + AppLog.e(AppLog.T.EDITOR, description); + } + }); + + this.setWebChromeClient(new WebChromeClient() { + @Override + public boolean onConsoleMessage(@NonNull ConsoleMessage cm) { + AppLog.d(AppLog.T.EDITOR, cm.message() + " -- From line " + cm.lineNumber() + " of " + cm.sourceId()); + return true; + } + + @Override + public boolean onJsAlert(WebView view, String url, String message, JsResult result) { + AppLog.d(AppLog.T.EDITOR, message); + return true; + } + }); } @Override