Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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.
Expand All @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions WordPress/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,8 @@
<string name="new_editor_promo_title">Brand new editor</string>
<string name="new_editor_promo_desc">The WordPress app for Android now includes a beautiful new visual
editor. Try it out by creating a new post.</string>
<string name="new_editor_reflection_error">Visual editor is not compatible with your device. It was
automatically disabled.</string>

<!-- editor post settings -->
<string name="editor_post_settings_featured_image">Featured Image</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,52 @@
* 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();
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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}
}