| 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); |
| } |
| } |
| } |