Skip to content

Commit 64b83d3

Browse files
committed
Merge pull request #136 from wordpress-mobile/issue/132-formatbar-keyboard-old-api
Issue #132: format bar buttons hide keyboard
2 parents 4478cf3 + 2d7c9c4 commit 64b83d3

File tree

7 files changed

+201
-53
lines changed

7 files changed

+201
-53
lines changed

WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,11 @@
44
import android.app.Activity;
55
import android.os.Build;
66
import android.os.Bundle;
7-
import android.support.annotation.NonNull;
87
import android.text.Spanned;
98
import android.view.LayoutInflater;
109
import android.view.View;
1110
import android.view.ViewGroup;
12-
import android.webkit.ConsoleMessage;
13-
import android.webkit.JsResult;
14-
import android.webkit.WebChromeClient;
15-
import android.webkit.WebSettings;
1611
import android.webkit.WebView;
17-
import android.webkit.WebViewClient;
1812
import android.widget.ToggleButton;
1913

2014
import com.android.volley.toolbox.ImageLoader;
@@ -49,7 +43,7 @@ public class EditorFragment extends EditorFragmentAbstract implements View.OnCli
4943
private String mParamContent;
5044

5145
private Activity mActivity;
52-
private EditorWebView mWebView;
46+
private EditorWebViewAbstract mWebView;
5347

5448
private final Map<String, ToggleButton> mTagToggleButtonMap = new HashMap<>();
5549

@@ -78,8 +72,8 @@ public void onCreate(Bundle savedInstanceState) {
7872
@Override
7973
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
8074
View view = inflater.inflate(R.layout.fragment_editor, container, false);
81-
mWebView = (EditorWebView) view.findViewById(R.id.webview);
82-
initWebView();
75+
mWebView = (EditorWebViewAbstract) view.findViewById(R.id.webview);
76+
initJsEditor();
8377

8478
ToggleButton mediaButton = (ToggleButton) view.findViewById(R.id.format_bar_button_media);
8579
mTagToggleButtonMap.put(TAG_FORMAT_BAR_BUTTON_MEDIA, mediaButton);
@@ -117,30 +111,7 @@ public void onDetach() {
117111
super.onDetach();
118112
}
119113

120-
@SuppressLint("SetJavaScriptEnabled")
121-
private void initWebView() {
122-
WebSettings webSettings = mWebView.getSettings();
123-
webSettings.setJavaScriptEnabled(true);
124-
webSettings.setDefaultTextEncodingName("utf-8");
125-
mWebView.setWebViewClient(new WebViewClient() {
126-
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
127-
AppLog.e(T.EDITOR, description);
128-
}
129-
});
130-
mWebView.setWebChromeClient(new WebChromeClient() {
131-
@Override
132-
public boolean onConsoleMessage(@NonNull ConsoleMessage cm) {
133-
AppLog.d(T.EDITOR, cm.message() + " -- From line " + cm.lineNumber() + " of " + cm.sourceId());
134-
return true;
135-
}
136-
137-
@Override
138-
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
139-
AppLog.d(T.EDITOR, message);
140-
return true;
141-
}
142-
});
143-
114+
private void initJsEditor() {
144115
String htmlEditor = Utils.getHtmlFromFile(mActivity, "android-editor.html");
145116

146117
mWebView.addJavascriptInterface(new JsCallbackReceiver(this), JS_CALLBACK_HANDLER);
Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
11
package org.wordpress.android.editor;
22

3+
import android.annotation.SuppressLint;
34
import android.content.Context;
5+
import android.os.Build;
46
import android.util.AttributeSet;
5-
import android.webkit.WebView;
67

7-
public class EditorWebView extends WebView {
8-
9-
public EditorWebView(Context context) {
10-
super(context);
11-
}
8+
public class EditorWebView extends EditorWebViewAbstract {
129

1310
public EditorWebView(Context context, AttributeSet attrs) {
1411
super(context, attrs);
1512
}
1613

17-
public EditorWebView(Context context, AttributeSet attrs, int defStyle) {
18-
super(context, attrs, defStyle);
19-
}
20-
21-
@Override
22-
public boolean onCheckIsTextEditor() {
23-
return true;
24-
}
25-
14+
@SuppressLint("NewApi")
2615
public void execJavaScriptFromString(String javaScript) {
27-
this.loadUrl("javascript:" + javaScript);
16+
if (Build.VERSION.SDK_INT >= 19) {
17+
this.evaluateJavascript(javaScript, null);
18+
} else {
19+
this.loadUrl("javascript:" + javaScript);
20+
}
2821
}
2922

3023
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.wordpress.android.editor;
2+
3+
import android.annotation.SuppressLint;
4+
import android.content.Context;
5+
import android.support.annotation.NonNull;
6+
import android.util.AttributeSet;
7+
import android.webkit.ConsoleMessage;
8+
import android.webkit.JsResult;
9+
import android.webkit.WebChromeClient;
10+
import android.webkit.WebSettings;
11+
import android.webkit.WebView;
12+
import android.webkit.WebViewClient;
13+
14+
import org.wordpress.android.util.AppLog;
15+
16+
/**
17+
* A text editor WebView with support for JavaScript execution.
18+
*/
19+
public abstract class EditorWebViewAbstract extends WebView {
20+
public abstract void execJavaScriptFromString(String javaScript);
21+
22+
public EditorWebViewAbstract(Context context, AttributeSet attrs) {
23+
super(context, attrs);
24+
configureWebView();
25+
}
26+
27+
@SuppressLint("SetJavaScriptEnabled")
28+
private void configureWebView() {
29+
WebSettings webSettings = this.getSettings();
30+
webSettings.setJavaScriptEnabled(true);
31+
webSettings.setDefaultTextEncodingName("utf-8");
32+
33+
this.setWebViewClient(new WebViewClient() {
34+
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
35+
AppLog.e(AppLog.T.EDITOR, description);
36+
}
37+
});
38+
39+
this.setWebChromeClient(new WebChromeClient() {
40+
@Override
41+
public boolean onConsoleMessage(@NonNull ConsoleMessage cm) {
42+
AppLog.d(AppLog.T.EDITOR, cm.message() + " -- From line " + cm.lineNumber() + " of " + cm.sourceId());
43+
return true;
44+
}
45+
46+
@Override
47+
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
48+
AppLog.d(AppLog.T.EDITOR, message);
49+
return true;
50+
}
51+
});
52+
}
53+
54+
@Override
55+
public boolean onCheckIsTextEditor() {
56+
return true;
57+
}
58+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.wordpress.android.editor;
2+
3+
import android.content.Context;
4+
import android.os.Build;
5+
import android.os.Message;
6+
import android.util.AttributeSet;
7+
import android.webkit.WebView;
8+
9+
import org.wordpress.android.util.AppLog;
10+
import org.wordpress.android.util.AppLog.T;
11+
12+
import java.lang.reflect.Field;
13+
import java.lang.reflect.InvocationTargetException;
14+
import java.lang.reflect.Method;
15+
16+
/**
17+
* <p>Compatibility <code>EditorWebView</code> for pre-Chromium WebView (API<19). Provides a custom method for executing
18+
* JavaScript, {@link #loadJavaScript(String)}, instead of {@link WebView#loadUrl(String)}. This is needed because
19+
* <code>WebView#loadUrl(String)</code> on API<19 eventually calls <code>WebViewClassic#hideSoftKeyboard()</code>,
20+
* hiding the keyboard whenever JavaScript is executed.</p>
21+
*
22+
* <p>This class uses reflection to access the normally inaccessible <code>WebViewCore#sendMessage(Message)</code>
23+
* and use it to execute JavaScript, sidestepping <code>WebView#loadUrl(String)</code> and the keyboard issue.</p>
24+
*/
25+
@SuppressWarnings("TryWithIdenticalCatches")
26+
public class EditorWebViewCompatibility extends EditorWebViewAbstract {
27+
private static final int EXECUTE_JS = 194; // WebViewCore internal JS message code
28+
29+
private Object mWebViewCore;
30+
private Method mSendMessageMethod;
31+
32+
public EditorWebViewCompatibility(Context context, AttributeSet attrs) {
33+
super(context, attrs);
34+
try {
35+
this.initReflection();
36+
} catch (ReflectionException e) {
37+
AppLog.e(T.EDITOR, e);
38+
handleReflectionFailure();
39+
}
40+
}
41+
42+
private void initReflection() throws ReflectionException {
43+
Object webViewProvider;
44+
45+
try {
46+
if (Build.VERSION.SDK_INT >= 16) {
47+
// On API >= 16, the WebViewCore instance is not defined inside WebView itself but inside a
48+
// WebViewClassic (implementation of WebViewProvider), referenced from the WebView as mProvider
49+
50+
// Access WebViewClassic object
51+
Field webViewProviderField = WebView.class.getDeclaredField("mProvider");
52+
webViewProviderField.setAccessible(true);
53+
webViewProvider = webViewProviderField.get(this);
54+
55+
// Access WebViewCore object
56+
Field webViewCoreField = webViewProvider.getClass().getDeclaredField("mWebViewCore");
57+
webViewCoreField.setAccessible(true);
58+
mWebViewCore = webViewCoreField.get(webViewProvider);
59+
} else {
60+
// On API < 16, the WebViewCore is directly accessible from the WebView
61+
62+
// Access WebViewCore object
63+
Field webViewCoreField = WebView.class.getDeclaredField("mWebViewCore");
64+
webViewCoreField.setAccessible(true);
65+
mWebViewCore = webViewCoreField.get(this);
66+
}
67+
68+
// Access WebViewCore#sendMessage(Message) method
69+
if (mWebViewCore != null) {
70+
mSendMessageMethod = mWebViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
71+
mSendMessageMethod.setAccessible(true);
72+
}
73+
} catch (NoSuchFieldException e) {
74+
throw new ReflectionException(e);
75+
} catch (NoSuchMethodException e) {
76+
throw new ReflectionException(e);
77+
} catch (IllegalAccessException e) {
78+
throw new ReflectionException(e);
79+
}
80+
}
81+
82+
private void loadJavaScript(final String javaScript) throws ReflectionException {
83+
if (mSendMessageMethod == null) {
84+
initReflection();
85+
} else {
86+
Message jsMessage = Message.obtain(null, EXECUTE_JS, javaScript);
87+
try {
88+
mSendMessageMethod.invoke(mWebViewCore, jsMessage);
89+
} catch (InvocationTargetException e) {
90+
throw new ReflectionException(e);
91+
} catch (IllegalAccessException e) {
92+
throw new ReflectionException(e);
93+
}
94+
}
95+
}
96+
97+
public void execJavaScriptFromString(String javaScript) {
98+
try {
99+
loadJavaScript(javaScript);
100+
} catch(ReflectionException e) {
101+
AppLog.e(T.EDITOR, e);
102+
handleReflectionFailure();
103+
}
104+
}
105+
106+
private void handleReflectionFailure() {
107+
// TODO: Fallback to legacy editor and pass the error to analytics
108+
}
109+
110+
public class ReflectionException extends Exception {
111+
public ReflectionException(Throwable cause) {
112+
super(cause);
113+
}
114+
}
115+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<org.wordpress.android.editor.EditorWebView
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
/>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<org.wordpress.android.editor.EditorWebViewCompatibility
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
/>

WordPressEditor/src/main/res/layout/fragment_editor.xml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
android:layout_height="match_parent"
55
tools:context="org.wordpress.android.editor.EditorFragment">
66

7-
<org.wordpress.android.editor.EditorWebView
8-
android:layout_width="match_parent"
9-
android:layout_height="match_parent"
10-
android:id="@+id/webview"/>
7+
<include
8+
layout="@layout/editor_webview"
9+
android:id="@+id/webview" />
1110

1211
<LinearLayout
1312
android:id="@+id/format_bar"

0 commit comments

Comments
 (0)