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+ }
0 commit comments