summaryrefslogtreecommitdiffstats
path: root/android/source/src/java/org/libreoffice/InvalidationHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/source/src/java/org/libreoffice/InvalidationHandler.java')
-rw-r--r--android/source/src/java/org/libreoffice/InvalidationHandler.java768
1 files changed, 768 insertions, 0 deletions
diff --git a/android/source/src/java/org/libreoffice/InvalidationHandler.java b/android/source/src/java/org/libreoffice/InvalidationHandler.java
new file mode 100644
index 000000000..0f3f1dd7b
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/InvalidationHandler.java
@@ -0,0 +1,768 @@
+package org.libreoffice;
+
+import android.content.Intent;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.libreoffice.canvas.SelectionHandle;
+import org.libreoffice.kit.Document;
+import org.libreoffice.kit.Office;
+import org.libreoffice.overlay.DocumentOverlay;
+import org.mozilla.gecko.gfx.GeckoLayerClient;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parses (interprets) and handles invalidation messages from LibreOffice.
+ */
+public class InvalidationHandler implements Document.MessageCallback, Office.MessageCallback {
+ private static final String LOGTAG = InvalidationHandler.class.getSimpleName();
+ private final DocumentOverlay mDocumentOverlay;
+ private final GeckoLayerClient mLayerClient;
+ private OverlayState mState;
+ private boolean mKeyEvent = false;
+ private final LibreOfficeMainActivity mContext;
+
+ private int currentTotalPageNumber = 0; // total page number of the current document
+
+ public InvalidationHandler(LibreOfficeMainActivity context) {
+ mContext = context;
+ mDocumentOverlay = mContext.getDocumentOverlay();
+ mLayerClient = mContext.getLayerClient();
+ mState = OverlayState.NONE;
+ }
+
+ /**
+ * Processes callback message
+ *
+ * @param messageID - ID of the message
+ * @param payload - additional invalidation message payload
+ */
+ @Override
+ public void messageRetrieved(int messageID, String payload) {
+ if (!LOKitShell.isEditingEnabled()) {
+ // enable handling of hyperlinks and search result even in the Viewer
+ if (messageID != Document.CALLBACK_INVALIDATE_TILES
+ && messageID != Document.CALLBACK_DOCUMENT_PASSWORD
+ && messageID != Document.CALLBACK_HYPERLINK_CLICKED
+ && messageID != Document.CALLBACK_SEARCH_RESULT_SELECTION
+ && messageID != Document.CALLBACK_SC_FOLLOW_JUMP
+ && messageID != Document.CALLBACK_TEXT_SELECTION
+ && messageID != Document.CALLBACK_TEXT_SELECTION_START
+ && messageID != Document.CALLBACK_TEXT_SELECTION_END)
+ return;
+ }
+ switch (messageID) {
+ case Document.CALLBACK_INVALIDATE_TILES:
+ invalidateTiles(payload);
+ break;
+ case Document.CALLBACK_UNO_COMMAND_RESULT:
+ unoCommandResult(payload);
+ break;
+ case Document.CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+ invalidateCursor(payload);
+ break;
+ case Document.CALLBACK_TEXT_SELECTION:
+ textSelection(payload);
+ break;
+ case Document.CALLBACK_TEXT_SELECTION_START:
+ textSelectionStart(payload);
+ break;
+ case Document.CALLBACK_TEXT_SELECTION_END:
+ textSelectionEnd(payload);
+ break;
+ case Document.CALLBACK_CURSOR_VISIBLE:
+ cursorVisibility(payload);
+ break;
+ case Document.CALLBACK_GRAPHIC_SELECTION:
+ graphicSelection(payload);
+ break;
+ case Document.CALLBACK_HYPERLINK_CLICKED:
+ if (!payload.startsWith("http://") && !payload.startsWith("https://")) {
+ payload = "http://" + payload;
+ }
+ Intent urlIntent = new Intent(Intent.ACTION_VIEW);
+ urlIntent.setData(Uri.parse(payload));
+ mContext.startActivity(urlIntent);
+ break;
+ case Document.CALLBACK_STATE_CHANGED:
+ stateChanged(payload);
+ break;
+ case Document.CALLBACK_SEARCH_RESULT_SELECTION:
+ searchResultSelection(payload);
+ // when doing a search, CALLBACK_SEARCH_RESULT_SELECTION is called in addition
+ // to the CALLBACK_TEXT_SELECTION{,_START,_END} callbacks and the handling of
+ // the previous 3 makes the cursor shown in addition to the selection rectangle,
+ // so hide the cursor again to just show the selection rectangle for the search result
+ mDocumentOverlay.hideCursor();
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.MIDDLE);
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.START);
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.END);
+ break;
+ case Document.CALLBACK_SEARCH_NOT_FOUND:
+ Log.d(LOGTAG, "LOK_CALLBACK: Search not found.");
+ // this callback is never caught. Hope someone fix this.
+ break;
+ case Document.CALLBACK_CELL_CURSOR:
+ invalidateCellCursor(payload);
+ break;
+ case Document.CALLBACK_SC_FOLLOW_JUMP:
+ jumpToCell(payload);
+ break;
+ case Document.CALLBACK_INVALIDATE_HEADER:
+ invalidateHeader();
+ break;
+ case Document.CALLBACK_CELL_ADDRESS:
+ cellAddress(payload);
+ break;
+ case Document.CALLBACK_CELL_FORMULA:
+ cellFormula(payload);
+ break;
+ case Document.CALLBACK_DOCUMENT_PASSWORD:
+ documentPassword();
+ break;
+ case Document.CALLBACK_DOCUMENT_SIZE_CHANGED:
+ pageSizeChanged(payload);
+ default:
+
+ Log.d(LOGTAG, "LOK_CALLBACK uncaught: " + messageID + " : " + payload);
+ }
+ }
+
+ private void unoCommandResult(String payload) {
+ try {
+ JSONObject payloadObject = new JSONObject(payload);
+ if (payloadObject.getString("commandName").equals(".uno:Save")) {
+ if (payloadObject.getString("success").equals("true")) {
+ mContext.saveFileToOriginalSource();
+ }
+ }else if(payloadObject.getString("commandName").equals(".uno:Name") ||
+ payloadObject.getString("commandName").equals(".uno:RenamePage")){
+ //success returns false even though its true for some reason,
+ LOKitShell.getMainHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ mContext.getTileProvider().resetParts();
+ mContext.getDocumentPartViewListAdapter().notifyDataSetChanged();
+ LibreOfficeMainActivity.setDocumentChanged(true);
+ Toast.makeText(mContext, mContext.getString(R.string.part_name_changed), Toast.LENGTH_SHORT).show();
+ }
+ });
+ } else if(payloadObject.getString("commandName").equals(".uno:Remove") ||
+ payloadObject.getString("commandName").equals(".uno:DeletePage") ) {
+ LOKitShell.getMainHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ mContext.getTileProvider().resetParts();
+ mContext.getDocumentPartViewListAdapter().notifyDataSetChanged();
+ LibreOfficeMainActivity.setDocumentChanged(true);
+ Toast.makeText(mContext, mContext.getString(R.string.part_deleted), Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ }catch(JSONException e){
+ e.printStackTrace();
+ }
+ }
+
+ private void cellFormula(final String payload) {
+ LOKitShell.getMainHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ ((EditText)mContext.findViewById(R.id.calc_formula)).setText(payload);
+ }
+ });
+ }
+
+ private void cellAddress(final String payload) {
+ LOKitShell.getMainHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ ((EditText)mContext.findViewById(R.id.calc_address)).setText(payload);
+ }
+ });
+ }
+
+ private void invalidateHeader() {
+ LOKitShell.sendEvent(new LOEvent(LOEvent.UPDATE_CALC_HEADERS));
+ }
+
+ private void documentPassword() {
+ mContext.setPasswordProtected(true);
+ mContext.promptForPassword();
+ synchronized (this) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ mContext.setPassword();
+ }
+
+ private void invalidateCellCursor(String payload) {
+ RectF cellCursorRect = convertPayloadToRectangle(payload);
+
+ if (cellCursorRect != null) {
+ mDocumentOverlay.showCellSelection(cellCursorRect);
+ moveViewportToMakeSelectionVisible(cellCursorRect);
+ }
+ }
+
+ private void jumpToCell(String payload) {
+ RectF cellCursorRect = convertPayloadCellToRectangle(payload);
+
+ if (cellCursorRect != null) {
+ moveViewportToMakeSelectionVisible(cellCursorRect);
+ }
+ }
+
+ /**
+ * Handles the search result selection message, which is a JSONObject
+ *
+ * @param payload
+ */
+ private void searchResultSelection(String payload) {
+ RectF selectionRectangle = null;
+ try {
+ JSONObject collectiveResult = new JSONObject(payload);
+ JSONArray searchResult = collectiveResult.getJSONArray("searchResultSelection");
+ if (searchResult.length() == 1) {
+ String rectangle = searchResult.getJSONObject(0).getString("rectangles");
+ selectionRectangle = convertPayloadToRectangle(rectangle);
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ if (selectionRectangle != null) {
+ moveViewportToMakeSelectionVisible(selectionRectangle);
+ }
+ }
+
+ /**
+ * Move the viewport to show the selection. The selection will appear at the
+ * viewport position depending on where the selection is relative to the
+ * viewport (either selection is above, below, on left or right). The difference
+ * between this method and moveViewportToMakeCursorVisible() is that this method
+ * takes into account the width and height of the selection and zooms out
+ * accordingly.
+ *
+ * @param selectionRectangle - selection position on the document
+ */
+ public void moveViewportToMakeSelectionVisible(RectF selectionRectangle) {
+ RectF moveToRect = mLayerClient.getViewportMetrics().getCssViewport();
+ if (moveToRect.contains(selectionRectangle)) {
+ return;
+ }
+
+ float newLeft = moveToRect.left;
+ float newTop = moveToRect.top;
+
+ // if selection rectangle is wider or taller than current viewport, we need to zoom out
+ float oldZoom = mLayerClient.getViewportMetrics().getZoomFactor();
+ float widthRatio = 1f;
+ float heightRatio = 1f;
+ if (moveToRect.width() < selectionRectangle.width()) {
+ widthRatio = selectionRectangle.width() / moveToRect.width() / 0.85f; // 0.85f gives some margin (must < 0.9)
+ }
+ if (moveToRect.height() < selectionRectangle.height()) {
+ heightRatio = selectionRectangle.height() / moveToRect.height() / 0.45f; // 0.45f gives some margin (must < 0.5)
+ }
+ float newZoom = widthRatio > heightRatio ? oldZoom/widthRatio : oldZoom/heightRatio;
+
+ // if selection is out of viewport we need to adjust accordingly
+ if (selectionRectangle.right < moveToRect.left || selectionRectangle.left < moveToRect.left) {
+ newLeft = selectionRectangle.left - (moveToRect.width() * 0.1f) * oldZoom / newZoom; // 0.1f gives left margin
+ } else if (selectionRectangle.right > moveToRect.right || selectionRectangle.left > moveToRect.right) {
+ newLeft = selectionRectangle.right - (moveToRect.width() * 0.9f) * oldZoom / newZoom; // 0.9f gives right margin
+ }
+
+ if (selectionRectangle.top < moveToRect.top || selectionRectangle.bottom < moveToRect.top) {
+ newTop = selectionRectangle.top - (moveToRect.height() * 0.1f) * oldZoom / newZoom; // 0.1f gives top margin
+ } else if (selectionRectangle.bottom > moveToRect.bottom || selectionRectangle.top > moveToRect.bottom){
+ newTop = selectionRectangle.bottom - (moveToRect.height() * 0.5f) * oldZoom / newZoom; // 0.5 f gives bottom margin
+ }
+
+ LOKitShell.moveViewportTo(mContext, new PointF(newLeft, newTop), newZoom);
+ }
+
+ private void pageSizeChanged(String payload){
+ if(mContext.getTileProvider().isTextDocument()){
+ String[] bounds = payload.split(",");
+ int pageWidth = Integer.parseInt(bounds[0]);
+ int pageHeight = Integer.parseInt(bounds[1].trim());
+ LOKitShell.sendEvent(new LOEvent(LOEvent.PAGE_SIZE_CHANGED, pageWidth, pageHeight));
+ }
+ }
+
+ private void stateChanged(String payload) {
+ String[] parts = payload.split("=");
+ if (parts.length < 2) {
+ Log.e(LOGTAG, "LOK_CALLBACK_STATE_CHANGED unexpected payload: " + payload);
+ return;
+ }
+ final String value = parts[1];
+ boolean pressed = Boolean.parseBoolean(value);
+ if (!mContext.getTileProvider().isReady()) {
+ Log.w(LOGTAG, "tile provider not ready, ignoring payload "+payload);
+ return;
+ }
+ if (parts[0].equals(".uno:Bold")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.BOLD, pressed);
+ } else if (parts[0].equals(".uno:Italic")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.ITALIC, pressed);
+ } else if (parts[0].equals(".uno:Underline")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.UNDERLINE, pressed);
+ } else if (parts[0].equals(".uno:Strikeout")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.STRIKEOUT, pressed);
+ } else if (parts[0].equals(".uno:CharFontName")) {
+ mContext.getFontController().selectFont(value);
+ } else if (parts[0].equals(".uno:FontHeight")) {
+ mContext.getFontController().selectFontSize(value);
+ } else if (parts[0].equals(".uno:LeftPara")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.ALIGN_LEFT, pressed);
+ } else if (parts[0].equals(".uno:CenterPara")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.ALIGN_CENTER, pressed);
+ } else if (parts[0].equals(".uno:RightPara")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.ALIGN_RIGHT, pressed);
+ } else if (parts[0].equals(".uno:JustifyPara")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.ALIGN_JUSTIFY, pressed);
+ } else if (parts[0].equals(".uno:DefaultBullet")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.BULLET_LIST, pressed);
+ } else if (parts[0].equals(".uno:DefaultNumbering")) {
+ mContext.getFormattingController().onToggleStateChanged(Document.NUMBERED_LIST, pressed);
+ } else if (parts[0].equals(".uno:Color")) {
+ mContext.getFontController().colorPaletteListener.updateColorPickerPosition(Integer.parseInt(value));
+ } else if (mContext.getTileProvider().isTextDocument() && parts[0].equals(".uno:BackColor")) {
+ mContext.getFontController().backColorPaletteListener.updateColorPickerPosition(Integer.parseInt(value));
+ } else if (mContext.getTileProvider().isPresentation() && parts[0].equals(".uno:CharBackColor")) {
+ mContext.getFontController().backColorPaletteListener.updateColorPickerPosition(Integer.parseInt(value));
+ } else if (mContext.getTileProvider().isSpreadsheet() && parts[0].equals(".uno:BackgroundColor")) {
+ mContext.getFontController().backColorPaletteListener.updateColorPickerPosition(Integer.parseInt(value));
+ } else if (parts[0].equals(".uno:StatePageNumber")) {
+ // get the total page number and compare to the current value and update accordingly
+ String[] splitStrings = parts[1].split(" ");
+ int totalPageNumber = Integer.valueOf(splitStrings[splitStrings.length - 1]);
+ if (totalPageNumber != currentTotalPageNumber) {
+ currentTotalPageNumber = totalPageNumber;
+ // update part page rectangles stored in DocumentOverlayView object
+ LOKitShell.sendEvent(new LOEvent(LOEvent.UPDATE_PART_PAGE_RECT));
+ }
+ } else {
+ Log.d(LOGTAG, "LOK_CALLBACK_STATE_CHANGED type uncatched: " + payload);
+ }
+ }
+
+ /**
+ * Parses the payload text with rectangle coordinates and converts to rectangle in pixel coordinates
+ *
+ * @param payload - invalidation message payload text
+ * @return rectangle in pixel coordinates
+ */
+ public RectF convertPayloadToRectangle(String payload) {
+ String payloadWithoutWhitespace = payload.replaceAll("\\s", ""); // remove all whitespace from the string
+
+ if (payloadWithoutWhitespace.isEmpty() || payloadWithoutWhitespace.equals("EMPTY")) {
+ return null;
+ }
+
+ String[] coordinates = payloadWithoutWhitespace.split(",");
+
+ if (coordinates.length != 4) {
+ return null;
+ }
+ return convertPayloadToRectangle(coordinates);
+ }
+
+ /**
+ * Parses the payload text with rectangle coordinates and converts to rectangle in pixel coordinates
+ *
+ * @param payload - invalidation message payload text
+ * @return rectangle in pixel coordinates
+ */
+ public RectF convertPayloadCellToRectangle(String payload) {
+ String payloadWithoutWhitespace = payload.replaceAll("\\s", ""); // remove all whitespace from the string
+
+ if (payloadWithoutWhitespace.isEmpty() || payloadWithoutWhitespace.equals("EMPTY")) {
+ return null;
+ }
+
+ String[] coordinates = payloadWithoutWhitespace.split(",");
+
+ if (coordinates.length != 6 ) {
+ return null;
+ }
+ return convertPayloadToRectangle(coordinates);
+ }
+
+ /**
+ * Converts rectangle coordinates to rectangle in pixel coordinates
+ *
+ * @param coordinates - the first four items defines the rectangle
+ * @return rectangle in pixel coordinates
+ */
+ public RectF convertPayloadToRectangle(String[] coordinates) {
+ if (coordinates.length < 4 ) {
+ return null;
+ }
+
+ int x = Integer.decode(coordinates[0]);
+ int y = Integer.decode(coordinates[1]);
+ int width = Integer.decode(coordinates[2]);
+ int height = Integer.decode(coordinates[3]);
+
+ float dpi = LOKitShell.getDpi(mContext);
+
+ return new RectF(
+ LOKitTileProvider.twipToPixel(x, dpi),
+ LOKitTileProvider.twipToPixel(y, dpi),
+ LOKitTileProvider.twipToPixel(x + width, dpi),
+ LOKitTileProvider.twipToPixel(y + height, dpi)
+ );
+ }
+
+ /**
+ * Parses the payload text with more rectangles (separated by ';') and converts to a list of rectangles.
+ *
+ * @param payload - invalidation message payload text
+ * @return list of rectangles
+ */
+ public List<RectF> convertPayloadToRectangles(String payload) {
+ List<RectF> rectangles = new ArrayList<RectF>();
+ String[] rectangleArray = payload.split(";");
+
+ for (String coordinates : rectangleArray) {
+ RectF rectangle = convertPayloadToRectangle(coordinates);
+ if (rectangle != null) {
+ rectangles.add(rectangle);
+ }
+
+ }
+
+ return rectangles;
+ }
+
+ /**
+ * Handles the tile invalidation message
+ *
+ * @param payload
+ */
+ private void invalidateTiles(String payload) {
+ RectF rectangle = convertPayloadToRectangle(payload);
+ if (rectangle != null) {
+ LOKitShell.sendTileInvalidationRequest(rectangle);
+ }
+ }
+
+ /**
+ * Handles the cursor invalidation message
+ *
+ * @param payload
+ */
+ private synchronized void invalidateCursor(String payload) {
+ RectF cursorRectangle = convertPayloadToRectangle(payload);
+ if (cursorRectangle != null) {
+ mDocumentOverlay.positionCursor(cursorRectangle);
+ mDocumentOverlay.positionHandle(SelectionHandle.HandleType.MIDDLE, cursorRectangle);
+
+ if (mState == OverlayState.TRANSITION || mState == OverlayState.CURSOR) {
+ changeStateTo(OverlayState.CURSOR);
+ }
+
+ if (mKeyEvent) {
+ moveViewportToMakeCursorVisible(cursorRectangle);
+ mKeyEvent = false;
+ }
+ }
+ }
+
+ /**
+ * Move the viewport to show the cursor. The cursor will appear at the
+ * viewport position depending on where the cursor is relative to the
+ * viewport (either cursor is above, below, on left or right).
+ *
+ * @param cursorRectangle - cursor position on the document
+ */
+ public void moveViewportToMakeCursorVisible(RectF cursorRectangle) {
+ RectF moveToRect = mLayerClient.getViewportMetrics().getCssViewport();
+ if (moveToRect.contains(cursorRectangle)) {
+ return;
+ }
+
+ float newLeft = moveToRect.left;
+ float newTop = moveToRect.top;
+
+ if (cursorRectangle.right < moveToRect.left || cursorRectangle.left < moveToRect.left) {
+ newLeft = cursorRectangle.left - (moveToRect.width() * 0.1f);
+ } else if (cursorRectangle.right > moveToRect.right || cursorRectangle.left > moveToRect.right) {
+ newLeft = cursorRectangle.right - (moveToRect.width() * 0.9f);
+ }
+
+ if (cursorRectangle.top < moveToRect.top || cursorRectangle.bottom < moveToRect.top) {
+ newTop = cursorRectangle.top - (moveToRect.height() * 0.1f);
+ } else if (cursorRectangle.bottom > moveToRect.bottom || cursorRectangle.top > moveToRect.bottom) {
+ newTop = cursorRectangle.bottom - (moveToRect.height() / 2.0f);
+ }
+
+ LOKitShell.moveViewportTo(mContext, new PointF(newLeft, newTop), null);
+ }
+
+ /**
+ * Handles the text selection start message
+ *
+ * @param payload
+ */
+ private synchronized void textSelectionStart(String payload) {
+ RectF selectionRect = convertPayloadToRectangle(payload);
+ if (selectionRect != null) {
+ mDocumentOverlay.positionHandle(SelectionHandle.HandleType.START, selectionRect);
+ }
+ }
+
+ /**
+ * Handles the text selection end message
+ *
+ * @param payload
+ */
+ private synchronized void textSelectionEnd(String payload) {
+ RectF selectionRect = convertPayloadToRectangle(payload);
+ if (selectionRect != null) {
+ mDocumentOverlay.positionHandle(SelectionHandle.HandleType.END, selectionRect);
+ }
+ }
+
+ /**
+ * Handles the text selection message
+ *
+ * @param payload
+ */
+ private synchronized void textSelection(String payload) {
+ if (payload.isEmpty() || payload.equals("EMPTY")) {
+ if (mState == OverlayState.SELECTION) {
+ changeStateTo(OverlayState.TRANSITION);
+ }
+ mDocumentOverlay.changeSelections(Collections.<RectF>emptyList());
+ if (mContext.getTileProvider().isSpreadsheet()) {
+ mDocumentOverlay.showHeaderSelection(null);
+ }
+ mContext.getToolbarController().showHideClipboardCutAndCopy(false);
+ } else {
+ List<RectF> rectangles = convertPayloadToRectangles(payload);
+ if (mState != OverlayState.SELECTION) {
+ changeStateTo(OverlayState.TRANSITION);
+ }
+ changeStateTo(OverlayState.SELECTION);
+ mDocumentOverlay.changeSelections(rectangles);
+ if (mContext.getTileProvider().isSpreadsheet()) {
+ mDocumentOverlay.showHeaderSelection(rectangles.get(0));
+ }
+ String selectedText = mContext.getTileProvider().getTextSelection("");
+ mContext.getToolbarController().showClipboardActions(selectedText);
+ }
+ }
+
+ /**
+ * Handles the cursor visibility message
+ *
+ * @param payload
+ */
+ private synchronized void cursorVisibility(String payload) {
+ if (payload.equals("true")) {
+ mDocumentOverlay.showCursor();
+ if (mState != OverlayState.SELECTION) {
+ mDocumentOverlay.showHandle(SelectionHandle.HandleType.MIDDLE);
+ }
+ } else if (payload.equals("false")) {
+ mDocumentOverlay.hideCursor();
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.MIDDLE);
+ }
+ }
+
+ /**
+ * Handles the graphic selection change message
+ *
+ * @param payload
+ */
+ private void graphicSelection(String payload) {
+ if (payload.isEmpty() || payload.equals("EMPTY")) {
+ if (mState == OverlayState.GRAPHIC_SELECTION) {
+ changeStateTo(OverlayState.TRANSITION);
+ }
+ } else {
+ RectF rectangle = convertPayloadToRectangle(payload);
+ mDocumentOverlay.changeGraphicSelection(rectangle);
+ if (mState != OverlayState.GRAPHIC_SELECTION) {
+ changeStateTo(OverlayState.TRANSITION);
+ }
+ changeStateTo(OverlayState.GRAPHIC_SELECTION);
+ }
+ }
+
+ /**
+ * Trigger a transition to a new overlay state.
+ *
+ * @param next - new state to transition to
+ */
+ public synchronized void changeStateTo(OverlayState next) {
+ changeState(mState, next);
+ }
+
+ /**
+ * Executes a transition from old overlay state to a new overlay state.
+ *
+ * @param previous - old state
+ * @param next - new state
+ */
+ private synchronized void changeState(OverlayState previous, OverlayState next) {
+ mState = next;
+ handleGeneralChangeState(previous, next);
+ switch (next) {
+ case CURSOR:
+ handleCursorState(previous);
+ break;
+ case SELECTION:
+ handleSelectionState(previous);
+ break;
+ case GRAPHIC_SELECTION:
+ handleGraphicSelectionState(previous);
+ break;
+ case TRANSITION:
+ handleTransitionState(previous);
+ break;
+ case NONE:
+ handleNoneState(previous);
+ break;
+ }
+ }
+
+ /**
+ * Handle a general transition - executed for all transitions.
+ */
+ private void handleGeneralChangeState(OverlayState previous, OverlayState next) {
+ if (previous == OverlayState.NONE &&
+ !mContext.getToolbarController().getEditModeStatus()) {
+ mContext.getToolbarController().switchToEditMode();
+ } else if (next == OverlayState.NONE &&
+ mContext.getToolbarController().getEditModeStatus()) {
+ mContext.getToolbarController().switchToViewMode();
+ }
+ }
+
+ /**
+ * Handle a transition to OverlayState.NONE state.
+ */
+ private void handleNoneState(OverlayState previous) {
+ if (previous == OverlayState.NONE) {
+ return;
+ }
+
+ // Just hide everything
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.START);
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.END);
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.MIDDLE);
+ mDocumentOverlay.hideSelections();
+ mDocumentOverlay.hideCursor();
+ mDocumentOverlay.hideGraphicSelection();
+ mContext.hideSoftKeyboard();
+ }
+
+ /**
+ * Handle a transition to OverlayState.SELECTION state.
+ */
+ private void handleSelectionState(OverlayState previous) {
+ mDocumentOverlay.showHandle(SelectionHandle.HandleType.START);
+ mDocumentOverlay.showHandle(SelectionHandle.HandleType.END);
+ mDocumentOverlay.showSelections();
+ }
+
+ /**
+ * Handle a transition to OverlayState.CURSOR state.
+ */
+ private void handleCursorState(OverlayState previous) {
+ mContext.showSoftKeyboardOrFormattingToolbar();
+ if (previous == OverlayState.TRANSITION) {
+ mDocumentOverlay.showHandle(SelectionHandle.HandleType.MIDDLE);
+ mDocumentOverlay.showCursor();
+ }
+ }
+
+ /**
+ * Handle a transition to OverlayState.TRANSITION state.
+ */
+ private void handleTransitionState(OverlayState previous) {
+ switch (previous) {
+ case SELECTION:
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.START);
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.END);
+ mDocumentOverlay.hideSelections();
+ break;
+ case CURSOR:
+ mDocumentOverlay.hideHandle(SelectionHandle.HandleType.MIDDLE);
+ break;
+ case GRAPHIC_SELECTION:
+ mDocumentOverlay.hideGraphicSelection();
+ break;
+ }
+ }
+
+ /**
+ * Handle a transition to OverlayState.GRAPHIC_SELECTION state.
+ */
+ private void handleGraphicSelectionState(OverlayState previous) {
+ mDocumentOverlay.showGraphicSelection();
+ mContext.hideSoftKeyboard();
+ }
+
+ /**
+ * The current state the overlay is in.
+ */
+ public OverlayState getCurrentState() {
+ return mState;
+ }
+
+ /**
+ * A key event happened (i.e. user started typing).
+ */
+ public void keyEvent() {
+ mKeyEvent = true;
+ }
+
+ /**
+ * The states the overlay.
+ */
+ public enum OverlayState {
+ /**
+ * State where the overlay is empty
+ */
+ NONE,
+ /**
+ * In-between state where we need to transition to a new overlay state.
+ * In this state we properly disable the older state and wait to transition
+ * to a new state triggered by an invalidation.
+ */
+ TRANSITION,
+ /**
+ * State where we operate with the cursor.
+ */
+ CURSOR,
+ /**
+ * State where we operate the graphic selection.
+ */
+ GRAPHIC_SELECTION,
+ /**
+ * State where we operate the text selection.
+ */
+ SELECTION
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */