it.angrydroids.epub3reader.TextSelectionSupport.java Source code

Java tutorial

Introduction

Here is the source code for it.angrydroids.epub3reader.TextSelectionSupport.java

Source

/*
 * Copyright (C) 2012 - 2014 Brandon Tate, bossturbo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.angrydroids.epub3reader;

import org.json.JSONException;
import org.json.JSONObject;

import com.blahti.drag.DragController;
import com.blahti.drag.DragController.DragBehavior;
import com.blahti.drag.DragLayer;
import com.blahti.drag.DragListener;
import com.blahti.drag.DragSource;
import com.blahti.drag.MyAbsoluteLayout;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.FragmentManager;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.Toast;

@SuppressLint("DefaultLocale")
public class TextSelectionSupport extends SplitPanel
        implements TextSelectionControlListener, OnTouchListener, OnLongClickListener, DragListener {
    public interface SelectionListener {
        void startSelection();

        void selectionChanged(String text);

        void endSelection();
    }

    private enum HandleType {
        START, END, UNKNOWN
    }

    private static final String TAG = "SelectionSupport";
    private static final float CENTERING_SHORTER_MARGIN_RATIO = 12.0f / 48.0f;
    private static final int JACK_UP_PADDING = 2;
    private static final int SCROLLING_THRESHOLD = 10;

    private Activity mActivity;
    private WebView mWebView;
    private SelectionListener mSelectionListener;
    private DragLayer mSelectionDragLayer;
    private DragController mDragController;
    private ImageView mStartSelectionHandle;
    private ImageView mEndSelectionHandle;
    private Rect mSelectionBounds = null;
    private final Rect mSelectionBoundsTemp = new Rect();
    private TextSelectionController mSelectionController = null;
    private int mContentWidth = 0;
    private HandleType mLastTouchedSelectionHandle = HandleType.UNKNOWN;
    private boolean mScrolling = false;
    private float mScrollDiffY = 0;
    private float mLastTouchY = 0;
    private float mScrollDiffX = 0;
    private float mLastTouchX = 0;
    private float mScale = 1.0f;

    private Runnable mStartSelectionModeHandler = new Runnable() {
        public void run() {
            if (mSelectionBounds != null) {
                mWebView.addView(mSelectionDragLayer);
                drawSelectionHandles();
                final int contentHeight = (int) Math
                        .ceil(getDensityDependentValue(mWebView.getContentHeight(), mActivity));
                final int contentWidth = mWebView.getWidth();
                ViewGroup.LayoutParams layerParams = mSelectionDragLayer.getLayoutParams();
                layerParams.height = contentHeight;
                layerParams.width = Math.max(contentWidth, mContentWidth);
                mSelectionDragLayer.setLayoutParams(layerParams);
                if (mSelectionListener != null) {
                    mSelectionListener.startSelection();
                }
            }
        }
    };
    private Runnable endSelectionModeHandler = new Runnable() {
        public void run() {
            mWebView.removeView(mSelectionDragLayer);
            mSelectionBounds = null;
            mLastTouchedSelectionHandle = HandleType.UNKNOWN;
            mWebView.loadUrl("javascript: android.selection.clearSelection();");
            if (mSelectionListener != null) {
                mSelectionListener.endSelection();
            }
        }
    };

    private TextSelectionSupport(Activity activity, WebView webview) {
        mActivity = activity;
        mWebView = webview;
    }

    public static TextSelectionSupport support(Activity activity, WebView webview) {
        final TextSelectionSupport selectionSupport = new TextSelectionSupport(activity, webview);
        selectionSupport.setup();
        return selectionSupport;
    }

    public void onScaleChanged(float oldScale, float newScale) {
        mScale = newScale;
    }

    public void setSelectionListener(SelectionListener listener) {
        mSelectionListener = listener;
    }

    //
    // Interfaces of TextSelectionControlListener
    //
    @Override
    public void jsError(String error) {
        Log.e(TAG, "JSError: " + error);
    }

    @Override
    public void jsLog(String message) {
        Log.d(TAG, "JSLog: " + message);
    }

    @Override
    public void startSelectionMode() {
        mActivity.runOnUiThread(mStartSelectionModeHandler);
    }

    @Override
    public void endSelectionMode() {
        mActivity.runOnUiThread(endSelectionModeHandler);
    }

    @Override
    public void setContentWidth(float contentWidth) {
        mContentWidth = (int) getDensityDependentValue(contentWidth, mActivity);
    }

    @Override
    public void selectionChanged(String range, String text, String handleBounds, boolean isReallyChanged) {
        final Context ctx = mActivity;
        try {
            final JSONObject selectionBoundsObject = new JSONObject(handleBounds);
            final float scale = getDensityIndependentValue(mScale, ctx);
            Rect rect = mSelectionBoundsTemp;
            rect.left = (int) (getDensityDependentValue(selectionBoundsObject.getInt("left"), ctx) * scale);
            rect.top = (int) (getDensityDependentValue(selectionBoundsObject.getInt("top"), ctx) * scale);
            rect.right = (int) (getDensityDependentValue(selectionBoundsObject.getInt("right"), ctx) * scale);
            rect.bottom = (int) (getDensityDependentValue(selectionBoundsObject.getInt("bottom"), ctx) * scale);
            mSelectionBounds = rect;
            if (!isInSelectionMode()) {
                startSelectionMode();
            }
            drawSelectionHandles();
            if (mSelectionListener != null && isReallyChanged) {
                mSelectionListener.selectionChanged(text);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    //
    // Interface of OnTouchListener
    //
    @Override
    public boolean onTouch(View v, MotionEvent event) {

        final Context ctx = mActivity;
        float xPoint = getDensityIndependentValue(event.getX(), ctx) / getDensityIndependentValue(mScale, ctx);
        float yPoint = getDensityIndependentValue(event.getY(), ctx) / getDensityIndependentValue(mScale, ctx);

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            final String startTouchUrl = String.format("javascript:android.selection.startTouch(%f, %f);", xPoint,
                    yPoint);
            mLastTouchX = xPoint;
            mLastTouchY = yPoint;
            mWebView.loadUrl(startTouchUrl);
            break;
        case MotionEvent.ACTION_UP:
            if (!mScrolling) {
                endSelectionMode();
                //
                // Fixes 4.4 double selection
                // See:
                // http://stackoverflow.com/questions/20391783/how-to-avoid-default-selection-on-long-press-in-android-kitkat-4-4
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    return false;
                }
            }
            mScrollDiffX = 0;
            mScrollDiffY = 0;
            mScrolling = false;
            //
            // Fixes 4.4 double selection
            // See:
            // http://stackoverflow.com/questions/20391783/how-to-avoid-default-selection-on-long-press-in-android-kitkat-4-4
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                return true;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            mScrollDiffX += (xPoint - mLastTouchX);
            mScrollDiffY += (yPoint - mLastTouchY);
            mLastTouchX = xPoint;
            mLastTouchY = yPoint;
            if (Math.abs(mScrollDiffX) > SCROLLING_THRESHOLD || Math.abs(mScrollDiffY) > SCROLLING_THRESHOLD) {
                mScrolling = true;
            }
            break;
        }

        return false;
    }

    //
    // Interface of OnLongClickListener
    //
    @Override
    public boolean onLongClick(View v) {

        if (!isInSelectionMode()) {
            mWebView.loadUrl("javascript:android.selection.longTouch();");
            mScrolling = true;
        }
        Message msg = new Message();
        msg.setTarget(new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                String url = msg.getData().getString("url");
                if (url != null)
                    navigator.setNote(url, index);
            }
        });
        mWebView.requestFocusNodeHref(msg);
        return true;
    }

    //
    // Interface of DragListener
    //
    @Override
    public void onDragStart(DragSource source, Object info, DragBehavior dragBehavior) {
    }

    @Override
    public void onDragEnd() {
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                MyAbsoluteLayout.LayoutParams startHandleParams = (MyAbsoluteLayout.LayoutParams) mStartSelectionHandle
                        .getLayoutParams();
                MyAbsoluteLayout.LayoutParams endHandleParams = (MyAbsoluteLayout.LayoutParams) mEndSelectionHandle
                        .getLayoutParams();
                final Context ctx = mActivity;
                final float scale = getDensityIndependentValue(mScale, ctx);
                float startX = startHandleParams.x - mWebView.getScrollX()
                        + mStartSelectionHandle.getWidth() * (1 - CENTERING_SHORTER_MARGIN_RATIO);
                float startY = startHandleParams.y - mWebView.getScrollY() - JACK_UP_PADDING;
                float endX = endHandleParams.x - mWebView.getScrollX()
                        + mEndSelectionHandle.getWidth() * CENTERING_SHORTER_MARGIN_RATIO;
                float endY = endHandleParams.y - mWebView.getScrollY() - JACK_UP_PADDING;
                startX = getDensityIndependentValue(startX, ctx) / scale;
                startY = getDensityIndependentValue(startY, ctx) / scale;
                endX = getDensityIndependentValue(endX, ctx) / scale;
                endY = getDensityIndependentValue(endY, ctx) / scale;
                if (mLastTouchedSelectionHandle == HandleType.START && startX > 0 && startY > 0) {
                    String saveStartString = String.format("javascript: android.selection.setStartPos(%f, %f);",
                            startX, startY);
                    mWebView.loadUrl(saveStartString);
                } else if (mLastTouchedSelectionHandle == HandleType.END && endX > 0 && endY > 0) {
                    String saveEndString = String.format("javascript: android.selection.setEndPos(%f, %f);", endX,
                            endY);
                    mWebView.loadUrl(saveEndString);
                } else {
                    mWebView.loadUrl("javascript: android.selection.restoreStartEndPos();");
                }
            }
        });

    }

    @SuppressLint("SetJavaScriptEnabled")
    private void setup() {
        mScale = mActivity.getResources().getDisplayMetrics().density;
        mWebView.setOnLongClickListener(this);
        mWebView.setOnTouchListener(this);
        mSelectionController = new TextSelectionController(this);
        mWebView.addJavascriptInterface(mSelectionController, TextSelectionController.INTERFACE_NAME);
        createSelectionLayer(mActivity);
    }

    private void createSelectionLayer(Context context) {
        final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mSelectionDragLayer = (DragLayer) inflater.inflate(R.layout.selection_drag_layer, null);
        mDragController = new DragController(context);
        mDragController.setDragListener(this);
        mDragController.addDropTarget(mSelectionDragLayer);
        mSelectionDragLayer.setDragController(mDragController);
        mStartSelectionHandle = (ImageView) mSelectionDragLayer.findViewById(R.id.startHandle);
        mStartSelectionHandle.setTag(HandleType.START);
        mEndSelectionHandle = (ImageView) mSelectionDragLayer.findViewById(R.id.endHandle);
        mEndSelectionHandle.setTag(HandleType.END);
        final OnTouchListener handleTouchListener = new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean handledHere = false;
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    handledHere = startDrag(v);
                    mLastTouchedSelectionHandle = (HandleType) v.getTag();
                }
                return handledHere;
            }
        };
        mStartSelectionHandle.setOnTouchListener(handleTouchListener);
        mEndSelectionHandle.setOnTouchListener(handleTouchListener);
    }

    private void drawSelectionHandles() {
        mActivity.runOnUiThread(drawSelectionHandlesHandler);
    }

    private Runnable drawSelectionHandlesHandler = new Runnable() {
        public void run() {
            MyAbsoluteLayout.LayoutParams startParams = (com.blahti.drag.MyAbsoluteLayout.LayoutParams) mStartSelectionHandle
                    .getLayoutParams();
            final int startWidth = mStartSelectionHandle.getDrawable().getIntrinsicWidth();
            startParams.x = (int) (mSelectionBounds.left - startWidth * (1.0f - CENTERING_SHORTER_MARGIN_RATIO));
            startParams.y = (int) (mSelectionBounds.top);
            final int startMinLeft = -(int) (startWidth * (1 - CENTERING_SHORTER_MARGIN_RATIO));
            startParams.x = (startParams.x < startMinLeft) ? startMinLeft : startParams.x;
            startParams.y = (startParams.y < 0) ? 0 : startParams.y;
            mStartSelectionHandle.setLayoutParams(startParams);

            MyAbsoluteLayout.LayoutParams endParams = (com.blahti.drag.MyAbsoluteLayout.LayoutParams) mEndSelectionHandle
                    .getLayoutParams();
            final int endWidth = mEndSelectionHandle.getDrawable().getIntrinsicWidth();
            endParams.x = (int) (mSelectionBounds.right - endWidth * CENTERING_SHORTER_MARGIN_RATIO);
            endParams.y = (int) (mSelectionBounds.bottom);
            final int endMinLeft = -(int) (endWidth * (1 - CENTERING_SHORTER_MARGIN_RATIO));
            endParams.x = (endParams.x < endMinLeft) ? endMinLeft : endParams.x;
            endParams.y = (endParams.y < 0) ? 0 : endParams.y;
            mEndSelectionHandle.setLayoutParams(endParams);
        }
    };

    private boolean isInSelectionMode() {
        return this.mSelectionDragLayer.getParent() != null;
    }

    private boolean startDrag(View v) {
        Object dragInfo = v;
        mDragController.startDrag(v, mSelectionDragLayer, dragInfo, DragBehavior.MOVE);
        return true;
    }

    private float getDensityDependentValue(float val, Context ctx) {
        Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        return val * (metrics.densityDpi / 160f);
    }

    private float getDensityIndependentValue(float val, Context ctx) {
        Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        display.getMetrics(metrics);
        return val / (metrics.densityDpi / 160f);
    }
}