diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index e197d5c24b29..56fdd677a06d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -57,6 +57,8 @@ import org.wordpress.android.editor.EditorFragmentAbstract.TrackableEvent; import org.wordpress.android.editor.EditorMediaUploadListener; import org.wordpress.android.editor.EditorWebViewAbstract.ErrorListener; +import org.wordpress.android.editor.EditorWebViewCompatibility; +import org.wordpress.android.editor.EditorWebViewCompatibility.ReflectionException; import org.wordpress.android.editor.ImageSettingsDialogFragment; import org.wordpress.android.editor.LegacyEditorFragment; import org.wordpress.android.models.AccountHelper; @@ -122,7 +124,7 @@ import de.greenrobot.event.EventBus; public class EditPostActivity extends AppCompatActivity implements EditorFragmentListener, - ActivityCompat.OnRequestPermissionsResultCallback { + ActivityCompat.OnRequestPermissionsResultCallback, EditorWebViewCompatibility.ReflectionFailureListener { public static final String EXTRA_POSTID = "postId"; public static final String EXTRA_IS_PAGE = "isPage"; public static final String EXTRA_IS_NEW_POST = "isNewPost"; @@ -866,6 +868,21 @@ private void saveAndFinish() { finish(); } + /** + * Disable visual editor mode and log the exception if we get a Reflection failure when the webview is being + * initialized. + */ + @Override + public void onReflectionFailure(ReflectionException e) { + CrashlyticsUtils.logException(e, ExceptionType.SPECIFIC, T.EDITOR, "Reflection Failure on Visual Editor init"); + // Disable visual editor and show an error message + AppPrefs.setVisualEditorEnabled(false); + ToastUtils.showToast(this, R.string.new_editor_reflection_error, Duration.LONG); + // Restart the activity (will start the legacy editor) + finish(); + startActivity(getIntent()); + } + /** * A {@link FragmentPagerAdapter} that returns a fragment corresponding to * one of the sections/tabs/pages. @@ -886,6 +903,7 @@ public Fragment getItem(int position) { case 0: // TODO: switch between legacy and new editor here (AB test?) if (mShowNewEditor) { + EditorWebViewCompatibility.setReflectionFailureListener(EditPostActivity.this); return new EditorFragment(); } else { return new LegacyEditorFragment(); diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 14b79f08d4c2..9156b75a4d01 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -914,6 +914,8 @@ Brand new editor The WordPress app for Android now includes a beautiful new visual editor. Try it out by creating a new post. + Visual editor is not compatible with your device. It was + automatically disabled. Featured Image diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java index 714f87ac8763..4f72868b5937 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorWebViewCompatibility.java @@ -18,30 +18,52 @@ * 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 { + 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(); + 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 { if (Build.VERSION.SDK_INT >= 16) { // On API >= 16, the WebViewCore instance is not defined inside WebView itself but inside a @@ -58,7 +80,6 @@ private void initReflection() throws ReflectionException { 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); @@ -97,19 +118,22 @@ private void loadJavaScript(final String javaScript) throws ReflectionException public void execJavaScriptFromString(String javaScript) { try { loadJavaScript(javaScript); - } catch(ReflectionException e) { + } catch (ReflectionException e) { AppLog.e(T.EDITOR, e); - handleReflectionFailure(); + handleReflectionFailure(e); } } - private void handleReflectionFailure() { - // TODO: Fallback to legacy editor and pass the error to analytics + private void handleReflectionFailure(ReflectionException e) { + if (mReflectionFailureListener != null) { + mReflectionFailureListener.onReflectionFailure(e); + } + mReflectionSucceed = false; } - public class ReflectionException extends Exception { - public ReflectionException(Throwable cause) { - super(cause); - } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mReflectionFailureListener = null; } -} \ No newline at end of file +}