blob: b8212fcefae29c359316b5afdd2bb2b179cf9703 [file] [log] [blame]
package org.wordpress.android.editor;
import android.webkit.JavascriptInterface;
import org.json.JSONException;
import org.json.JSONObject;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.JSONUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.wordpress.android.editor.EditorFragmentAbstract.MediaType;
public class JsCallbackReceiver {
private static final String JS_CALLBACK_DELIMITER = "~";
private static final String CALLBACK_DOM_LOADED = "callback-dom-loaded";
private static final String CALLBACK_NEW_FIELD = "callback-new-field";
private static final String CALLBACK_INPUT = "callback-input";
private static final String CALLBACK_SELECTION_CHANGED = "callback-selection-changed";
private static final String CALLBACK_SELECTION_STYLE = "callback-selection-style";
private static final String CALLBACK_FOCUS_IN = "callback-focus-in";
private static final String CALLBACK_FOCUS_OUT = "callback-focus-out";
private static final String CALLBACK_IMAGE_REPLACED = "callback-image-replaced";
private static final String CALLBACK_VIDEO_REPLACED = "callback-video-replaced";
private static final String CALLBACK_IMAGE_TAP = "callback-image-tap";
private static final String CALLBACK_LINK_TAP = "callback-link-tap";
private static final String CALLBACK_MEDIA_REMOVED = "callback-media-removed";
private static final String CALLBACK_VIDEOPRESS_INFO_REQUEST = "callback-videopress-info-request";
private static final String CALLBACK_LOG = "callback-log";
private static final String CALLBACK_RESPONSE_STRING = "callback-response-string";
private static final String CALLBACK_ACTION_FINISHED = "callback-action-finished";
private final OnJsEditorStateChangedListener mListener;
private Set<String> mPreviousStyleSet = new HashSet<>();
public JsCallbackReceiver(EditorFragmentAbstract editorFragmentAbstract) {
mListener = (OnJsEditorStateChangedListener) editorFragmentAbstract;
}
@JavascriptInterface
public void executeCallback(String callbackId, String params) {
switch (callbackId) {
case CALLBACK_DOM_LOADED:
mListener.onDomLoaded();
break;
case CALLBACK_SELECTION_STYLE:
// Compare the new styles to the previous ones, and notify the JsCallbackListener of the changeset
Set<String> rawStyleSet = Utils.splitDelimitedString(params, JS_CALLBACK_DELIMITER);
// Strip link details from active style set
Set<String> newStyleSet = new HashSet<>();
for (String element : rawStyleSet) {
if (element.matches("link:(.*)")) {
newStyleSet.add("link");
} else if (!element.matches("link-title:(.*)")) {
newStyleSet.add(element);
}
}
mListener.onSelectionStyleChanged(Utils.getChangeMapFromSets(mPreviousStyleSet, newStyleSet));
mPreviousStyleSet = newStyleSet;
break;
case CALLBACK_SELECTION_CHANGED:
// Called for changes to the field in current focus and for changes made to selection
// (includes moving the caret without selecting text)
// TODO: Possibly needed for handling WebView scrolling when caret moves (from iOS)
Set<String> selectionKeyValueSet = Utils.splitDelimitedString(params, JS_CALLBACK_DELIMITER);
mListener.onSelectionChanged(Utils.buildMapFromKeyValuePairs(selectionKeyValueSet));
break;
case CALLBACK_INPUT:
// Called on key press
// TODO: Possibly needed for handling WebView scrolling when caret moves (from iOS)
break;
case CALLBACK_FOCUS_IN:
// TODO: Needed to handle displaying/graying the format bar when focus changes between the title and content
AppLog.d(AppLog.T.EDITOR, "Focus in callback received");
break;
case CALLBACK_FOCUS_OUT:
// TODO: Needed to handle displaying/graying the format bar when focus changes between the title and content
AppLog.d(AppLog.T.EDITOR, "Focus out callback received");
break;
case CALLBACK_NEW_FIELD:
// TODO: Used for logging/testing purposes on iOS
AppLog.d(AppLog.T.EDITOR, "New field created, " + params);
break;
case CALLBACK_IMAGE_REPLACED:
AppLog.d(AppLog.T.EDITOR, "Image replaced, " + params);
// Extract the local media id from the callback string (stripping the 'id=' part)
if (params.length() > 3) {
mListener.onMediaReplaced(params.substring(3));
}
break;
case CALLBACK_VIDEO_REPLACED:
AppLog.d(AppLog.T.EDITOR, "Video replaced, " + params);
// Extract the local media id from the callback string (stripping the 'id=' part)
if (params.length() > 3) {
mListener.onMediaReplaced(params.substring(3));
}
break;
case CALLBACK_IMAGE_TAP:
AppLog.d(AppLog.T.EDITOR, "Image tapped, " + params);
String uploadStatus = "";
List<String> mediaIds = new ArrayList<>();
mediaIds.add("id");
mediaIds.add("url");
mediaIds.add("meta");
mediaIds.add("type");
Set<String> mediaDataSet = Utils.splitValuePairDelimitedString(params, JS_CALLBACK_DELIMITER, mediaIds);
Map<String, String> mediaDataMap = Utils.buildMapFromKeyValuePairs(mediaDataSet);
String mediaId = mediaDataMap.get("id");
String mediaUrl = mediaDataMap.get("url");
if (mediaUrl != null) {
mediaUrl = Utils.decodeHtml(mediaUrl);
}
MediaType mediaType = MediaType.fromString(mediaDataMap.get("type"));
String mediaMeta = mediaDataMap.get("meta");
JSONObject mediaMetaJson = new JSONObject();
if (mediaMeta != null) {
mediaMeta = Utils.decodeHtml(mediaMeta);
try {
mediaMetaJson = new JSONObject(mediaMeta);
String classes = JSONUtils.getString(mediaMetaJson, "classes");
Set<String> classesSet = Utils.splitDelimitedString(classes, ", ");
if (classesSet.contains("uploading")) {
uploadStatus = "uploading";
} else if (classesSet.contains("failed")) {
uploadStatus = "failed";
}
} catch (JSONException e) {
e.printStackTrace();
AppLog.d(AppLog.T.EDITOR, "Media meta data from callback-image-tap was not JSON-formatted");
}
}
mListener.onMediaTapped(mediaId, mediaType, mediaMetaJson, uploadStatus);
break;
case CALLBACK_LINK_TAP:
// Extract and HTML-decode the link data from the callback params
AppLog.d(AppLog.T.EDITOR, "Link tapped, " + params);
List<String> linkIds = new ArrayList<>();
linkIds.add("url");
linkIds.add("title");
Set<String> linkDataSet = Utils.splitValuePairDelimitedString(params, JS_CALLBACK_DELIMITER, linkIds);
Map<String, String> linkDataMap = Utils.buildMapFromKeyValuePairs(linkDataSet);
String url = linkDataMap.get("url");
if (url != null) {
url = Utils.decodeHtml(url);
}
String title = linkDataMap.get("title");
if (title != null) {
title = Utils.decodeHtml(title);
}
mListener.onLinkTapped(url, title);
break;
case CALLBACK_MEDIA_REMOVED:
AppLog.d(AppLog.T.EDITOR, "Media removed, " + params);
// Extract the media id from the callback string (stripping the 'id=' part of the callback string)
if (params.length() > 3) {
mListener.onMediaRemoved(params.substring(3));
}
break;
case CALLBACK_VIDEOPRESS_INFO_REQUEST:
// Extract the VideoPress id from the callback string (stripping the 'id=' part of the callback string)
if (params.length() > 3) {
mListener.onVideoPressInfoRequested(params.substring(3));
}
break;
case CALLBACK_LOG:
// Strip 'msg=' from beginning of string
if (params.length() > 4) {
AppLog.d(AppLog.T.EDITOR, callbackId + ": " + params.substring(4));
}
break;
case CALLBACK_RESPONSE_STRING:
AppLog.d(AppLog.T.EDITOR, callbackId + ": " + params);
Set<String> responseDataSet;
if (params.startsWith("function=") && params.contains(JS_CALLBACK_DELIMITER)) {
String functionName = params.substring("function=".length(), params.indexOf(JS_CALLBACK_DELIMITER));
List<String> responseIds = new ArrayList<>();
switch (functionName) {
case "getHTMLForCallback":
responseIds.add("id");
responseIds.add("contents");
break;
case "getSelectedTextToLinkify":
responseIds.add("result");
break;
case "getFailedMedia":
responseIds.add("ids");
}
responseDataSet = Utils.splitValuePairDelimitedString(params, JS_CALLBACK_DELIMITER, responseIds);
} else {
responseDataSet = Utils.splitDelimitedString(params, JS_CALLBACK_DELIMITER);
}
mListener.onGetHtmlResponse(Utils.buildMapFromKeyValuePairs(responseDataSet));
break;
case CALLBACK_ACTION_FINISHED:
mListener.onActionFinished();
break;
default:
AppLog.d(AppLog.T.EDITOR, "Unhandled callback: " + callbackId + ":" + params);
}
}
}