org.readium.sdk.android.launcher.WebViewActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.readium.sdk.android.launcher.WebViewActivity.java

Source

/*
 * WebViewActivity.java
 * SDKLauncher-Android
 *
 * Created by Yonathan Teitelbaum (Mantano) on 2013-07-10.
 */
//  Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved.
//  Redistribution and use in source and binary forms, with or without modification, 
//  are permitted provided that the following conditions are met:
//  1. Redistributions of source code must retain the above copyright notice, this 
//  list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright notice, 
//  this list of conditions and the following disclaimer in the documentation and/or 
//  other materials provided with the distribution.
//  3. Neither the name of the organization nor the names of its contributors may be 
//  used to endorse or promote products derived from this software without specific 
//  prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
//  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
//  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
//  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
//  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
//  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
//  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
//  OF THE POSSIBILITY OF SUCH DAMAGE

package org.readium.sdk.android.launcher;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;
import org.readium.sdk.android.Container;
import org.readium.sdk.android.ManifestItem;
import org.readium.sdk.android.Package;
import org.readium.sdk.android.SpineItem;
import org.readium.sdk.android.launcher.model.BookmarkDatabase;
import org.readium.sdk.android.launcher.model.OpenPageRequest;
import org.readium.sdk.android.launcher.model.Page;
import org.readium.sdk.android.launcher.model.PaginationInfo;
import org.readium.sdk.android.launcher.model.ReadiumJSApi;
import org.readium.sdk.android.launcher.model.ViewerSettings;
import org.readium.sdk.android.launcher.util.EpubServer;
import org.readium.sdk.android.launcher.util.EpubServer.DataPreProcessor;
import org.readium.sdk.android.launcher.util.HTMLUtil;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.VideoView;

public class WebViewActivity extends FragmentActivity implements ViewerSettingsDialog.OnViewerSettingsChange {

    private final boolean quiet = false;

    private static final String TAG = "WebViewActivity";
    private static final String ASSET_PREFIX = "file:///android_asset/readium-shared-js/";
    private static final String READER_SKELETON = "file:///android_asset/readium-shared-js/reader.html";

    // Installs "hook" function so that top-level window (application) can later
    // inject the window.navigator.epubReadingSystem into this HTML document's
    // iframe
    private static final String INJECT_EPUB_RSO_SCRIPT_1 = ""
            + "window.readium_set_epubReadingSystem = function (obj) {"
            + "\nwindow.navigator.epubReadingSystem = obj;" + "\nwindow.readium_set_epubReadingSystem = undefined;"
            + "\nvar el1 = document.getElementById(\"readium_epubReadingSystem_inject1\");"
            + "\nif (el1 && el1.parentNode) { el1.parentNode.removeChild(el1); }"
            + "\nvar el2 = document.getElementById(\"readium_epubReadingSystem_inject2\");"
            + "\nif (el2 && el2.parentNode) { el2.parentNode.removeChild(el2); }" + "\n};";

    // Iterate top-level iframes, inject global
    // window.navigator.epubReadingSystem if the expected hook function exists (
    // readium_set_epubReadingSystem() ).
    private static final String INJECT_EPUB_RSO_SCRIPT_2 = "" + "var epubRSInject =\nfunction(win) {"
            + "\nvar ret = '';" + "\nret += win.location.href;" + "\nret += ' ---- ';" +
            // "\nret += JSON.stringify(win.navigator.epubReadingSystem);" +
            // "\nret += ' ---- ';" +
            "\nif (win.frames)" + "\n{" + "\nfor (var i = 0; i < win.frames.length; i++)" + "\n{"
            + "\nvar iframe = win.frames[i];" + "\nret += ' IFRAME ';"
            + "\nif (iframe.readium_set_epubReadingSystem)" + "\n{" + "\nret += ' EPBRS ';"
            + "\niframe.readium_set_epubReadingSystem(window.navigator.epubReadingSystem);" + "\n}"
            + "\nret += epubRSInject(iframe);" + "\n}" + "\n}" + "\nreturn ret;" + "\n};"
            + "\nepubRSInject(window);";

    // Script tag to inject the "hook" function installer script, added to the
    // head of every epub iframe document
    private static final String INJECT_HEAD_EPUB_RSO_1 = ""
            + "<script id=\"readium_epubReadingSystem_inject1\" type=\"text/javascript\">\n" + "//<![CDATA[\n"
            + INJECT_EPUB_RSO_SCRIPT_1 + "\n" + "//]]>\n" + "</script>";
    // Script tag that generates an HTTP request to a fake script => triggers
    // push of window.navigator.epubReadingSystem into this HTML document's
    // iframe
    private static final String INJECT_HEAD_EPUB_RSO_2 = ""
            + "<script id=\"readium_epubReadingSystem_inject2\" type=\"text/javascript\" "
            + "src=\"/%d/readium_epubReadingSystem_inject.js\"> </script>";
    // Script tag to load the mathjax script payload, added to the head of epub
    // iframe documents, only if <math> tags are detected
    private static final String INJECT_HEAD_MATHJAX = "<script type=\"text/javascript\" src=\"/readium_MathJax.js\"> </script>";

    // Location of payloads in the asset folder
    private static final String PAYLOAD_MATHJAX_ASSET = "reader-payloads/MathJax.js";
    private static final String PAYLOAD_ANNOTATIONS_CSS_ASSET = "reader-payloads/annotations.css";

    private final DataPreProcessor dataPreProcessor = new DataPreProcessor() {

        @Override
        public byte[] handle(byte[] data, String mime, String uriPath, ManifestItem item) {
            if (mime == null || (mime != "text/html" && mime != "application/xhtml+xml")) {
                return null;
            }

            if (!quiet)
                Log.d(TAG, "PRE-PROCESSED HTML: " + uriPath);

            String htmlText = new String(data, Charset.forName("UTF-8"));

            // String uuid = mPackage.getUrlSafeUniqueID();
            String newHtml = htmlText; // HTMLUtil.htmlByReplacingMediaURLsInHTML(htmlText,
            // cleanedUrl, uuid);
            // //"PackageUUID"

            // Set up the script tags to add to the head
            String tagsToInjectToHead = INJECT_HEAD_EPUB_RSO_1
                    // Slightly change fake script src url with an
                    // increasing count to prevent caching of the
                    // request
                    + String.format(INJECT_HEAD_EPUB_RSO_2, ++mEpubRsoInjectCounter);
            // Checks for the existance of MathML => request
            // MathJax payload
            if (newHtml.contains("<math") || newHtml.contains("<m:math")) {
                tagsToInjectToHead += INJECT_HEAD_MATHJAX;
            }

            newHtml = HTMLUtil.htmlByInjectingIntoHead(newHtml, tagsToInjectToHead);

            // Log.d(TAG, "HTML head inject: " + newHtml);

            return newHtml.getBytes();
        }
    };

    private WebView mWebview;
    private Container mContainer;
    private Package mPackage;
    private OpenPageRequest mOpenPageRequestData;
    private TextView mPageInfo;
    private ViewerSettings mViewerSettings;
    private ReadiumJSApi mReadiumJSApi;
    private EpubServer mServer;

    private boolean mIsMoAvailable;
    private boolean mIsMoPlaying;
    private int mEpubRsoInjectCounter = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view);

        mWebview = (WebView) findViewById(R.id.webview);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
                && 0 != (getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) {
            WebView.setWebContentsDebuggingEnabled(true);
        }

        mPageInfo = (TextView) findViewById(R.id.page_info);
        initWebView();

        Intent intent = getIntent();
        if (intent.getFlags() == Intent.FLAG_ACTIVITY_NEW_TASK) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                mContainer = ContainerHolder.getInstance().get(extras.getLong(Constants.CONTAINER_ID));
                if (mContainer == null) {
                    finish();
                    return;
                }
                mPackage = mContainer.getDefaultPackage();

                String rootUrl = "http://" + EpubServer.HTTP_HOST + ":" + EpubServer.HTTP_PORT + "/";
                mPackage.setRootUrls(rootUrl, null);

                try {
                    mOpenPageRequestData = OpenPageRequest
                            .fromJSON(extras.getString(Constants.OPEN_PAGE_REQUEST_DATA));
                } catch (JSONException e) {
                    Log.e(TAG, "Constants.OPEN_PAGE_REQUEST_DATA must be a valid JSON object: " + e.getMessage(),
                            e);
                }
            }
        }

        // No need, EpubServer already launchers its own thread
        // new AsyncTask<Void, Void, Void>() {
        // @Override
        // protected Void doInBackground(Void... params) {
        // //xxx
        // return null;
        // }
        // }.execute();

        mServer = new EpubServer(EpubServer.HTTP_HOST, EpubServer.HTTP_PORT, mPackage, quiet, dataPreProcessor);
        mServer.startServer();

        // Load the page skeleton
        mWebview.loadUrl(READER_SKELETON);
        mViewerSettings = new ViewerSettings(ViewerSettings.SyntheticSpreadMode.AUTO,
                ViewerSettings.ScrollMode.AUTO, 100, 20);

        mReadiumJSApi = new ReadiumJSApi(new ReadiumJSApi.JSLoader() {
            @Override
            public void loadJS(String javascript) {
                mWebview.loadUrl(javascript);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mServer.stop();
        mWebview.loadUrl(READER_SKELETON);
        ((ViewGroup) mWebview.getParent()).removeView(mWebview);
        mWebview.removeAllViews();
        mWebview.clearCache(true);
        mWebview.clearHistory();
        mWebview.destroy();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            mWebview.onPause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            mWebview.onResume();
        }
    }

    @SuppressLint({ "SetJavaScriptEnabled", "NewApi" })
    private void initWebView() {
        mWebview.getSettings().setJavaScriptEnabled(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            mWebview.getSettings().setAllowUniversalAccessFromFileURLs(true);
        }
        mWebview.setWebViewClient(new EpubWebViewClient());
        mWebview.setWebChromeClient(new EpubWebChromeClient());

        mWebview.addJavascriptInterface(new EpubInterface(), "LauncherUI");
    }

    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        int itemId = item.getItemId();
        switch (itemId) {
        case R.id.add_bookmark:
            if (!quiet)
                Log.d(TAG, "Add a bookmark");
            mReadiumJSApi.bookmarkCurrentPage();
            return true;
        case R.id.settings:
            if (!quiet)
                Log.d(TAG, "Show settings");
            showSettings();
            return true;
        case R.id.mo_previous:
            mReadiumJSApi.previousMediaOverlay();
            return true;
        case R.id.mo_play:
            mReadiumJSApi.toggleMediaOverlay();
            return true;
        case R.id.mo_pause:
            mReadiumJSApi.toggleMediaOverlay();
            return true;
        case R.id.mo_next:
            mReadiumJSApi.nextMediaOverlay();
            return true;
        }
        return false;
    }

    public void onClick(View v) {
        if (v.getId() == R.id.left) {
            mReadiumJSApi.openPageLeft();
        } else if (v.getId() == R.id.right) {
            mReadiumJSApi.openPageRight();
        }
    }

    private void showSettings() {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fm.beginTransaction();
        DialogFragment dialog = new ViewerSettingsDialog(this, mViewerSettings);
        dialog.show(fm, "dialog");
        fragmentTransaction.commit();
    }

    @Override
    public void onViewerSettingsChange(ViewerSettings viewerSettings) {
        updateSettings(viewerSettings);
    }

    private void updateSettings(ViewerSettings viewerSettings) {
        mViewerSettings = viewerSettings;
        mReadiumJSApi.updateSettings(viewerSettings);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.web_view, menu);

        MenuItem mo_previous = menu.findItem(R.id.mo_previous);
        MenuItem mo_next = menu.findItem(R.id.mo_next);
        MenuItem mo_play = menu.findItem(R.id.mo_play);
        MenuItem mo_pause = menu.findItem(R.id.mo_pause);

        // show menu only when its reasonable

        mo_previous.setVisible(mIsMoAvailable);
        mo_next.setVisible(mIsMoAvailable);

        if (mIsMoAvailable) {
            mo_play.setVisible(!mIsMoPlaying);
            mo_pause.setVisible(mIsMoPlaying);
        }

        return true;
    }

    public final class EpubWebViewClient extends WebViewClient {

        private static final String HTTP = "http";
        private static final String UTF_8 = "utf-8";

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            if (!quiet)
                Log.d(TAG, "onPageStarted: " + url);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            if (!quiet)
                Log.d(TAG, "onPageFinished: " + url);
        }

        @Override
        public void onLoadResource(WebView view, String url) {
            if (!quiet)
                Log.d(TAG, "onLoadResource: " + url);
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (!quiet)
                Log.d(TAG, "shouldOverrideUrlLoading: " + url);
            return false;
        }

        private void evaluateJavascript(final String script) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    if (!quiet)
                        Log.d(TAG, "WebView evaluateJavascript: " + script + "");

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

                        if (!quiet)
                            Log.d(TAG, "WebView evaluateJavascript KitKat+ API");

                        mWebview.evaluateJavascript(script, new ValueCallback<String>() {
                            @Override
                            public void onReceiveValue(String str) {
                                if (!quiet)
                                    Log.d(TAG, "WebView evaluateJavascript RETURN: " + str);
                            }
                        });
                    } else {

                        if (!quiet)
                            Log.d(TAG, "WebView loadUrl() API");

                        mWebview.loadUrl("javascript:var exec = function(){\n" + script + "\n}; exec();");
                    }
                }
            });
        }

        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            if (!quiet)
                Log.d(TAG, "-------- shouldInterceptRequest: " + url);

            if (url != null && url != "undefined") {

                String localHttpUrlPrefix = "http://" + EpubServer.HTTP_HOST + ":" + EpubServer.HTTP_PORT;
                boolean isLocalHttp = url.startsWith(localHttpUrlPrefix);

                // Uri uri = Uri.parse(url);
                // uri.getScheme()

                if (url.startsWith("http") && !isLocalHttp) {
                    if (!quiet)
                        Log.d(TAG, "HTTP (NOT LOCAL): " + url);
                    return super.shouldInterceptRequest(view, url);
                }

                String cleanedUrl = cleanResourceUrl(url, false);
                if (!quiet)
                    Log.d(TAG, url + " => " + cleanedUrl);

                if (cleanedUrl.matches("\\/?\\d*\\/readium_epubReadingSystem_inject.js")) {
                    if (!quiet)
                        Log.d(TAG, "navigator.epubReadingSystem inject ...");

                    // Fake script requested, this is immediately invoked after
                    // epubReadingSystem hook is in place,
                    // => execute js on the reader.html context to push the
                    // global window.navigator.epubReadingSystem into the
                    // iframe(s)

                    evaluateJavascript(INJECT_EPUB_RSO_SCRIPT_2);

                    return new WebResourceResponse("text/javascript", UTF_8,
                            new ByteArrayInputStream("(function(){})()".getBytes()));
                }

                if (cleanedUrl.matches("\\/?readium_MathJax.js")) {
                    if (!quiet)
                        Log.d(TAG, "MathJax.js inject ...");

                    InputStream is = null;
                    try {
                        is = getAssets().open(PAYLOAD_MATHJAX_ASSET);
                    } catch (IOException e) {

                        Log.e(TAG, "MathJax.js asset fail!");

                        return new WebResourceResponse(null, UTF_8, new ByteArrayInputStream("".getBytes()));
                    }

                    return new WebResourceResponse("text/javascript", UTF_8, is);
                }

                if (cleanedUrl.matches("\\/?readium_Annotations.css")) {
                    if (!quiet)
                        Log.d(TAG, "annotations.css inject ...");

                    InputStream is = null;
                    try {
                        is = getAssets().open(PAYLOAD_ANNOTATIONS_CSS_ASSET);
                    } catch (IOException e) {

                        Log.e(TAG, "annotations.css asset fail!");

                        return new WebResourceResponse(null, UTF_8, new ByteArrayInputStream("".getBytes()));
                    }

                    return new WebResourceResponse("text/css", UTF_8, is);
                }

                String mime = null;
                int dot = cleanedUrl.lastIndexOf('.');
                if (dot >= 0) {
                    mime = EpubServer.MIME_TYPES.get(cleanedUrl.substring(dot + 1).toLowerCase());
                }
                if (mime == null) {
                    mime = "application/octet-stream";
                }

                ManifestItem item = mPackage.getManifestItem(cleanedUrl);
                String contentType = item != null ? item.getMediaType() : null;
                if (mime != "application/xhtml+xml" && mime != "application/xml" // FORCE
                        && contentType != null && contentType.length() > 0) {
                    mime = contentType;
                }

                if (url.startsWith("file:")) {
                    if (item == null) {
                        Log.e(TAG, "NO MANIFEST ITEM ... " + url);
                        return super.shouldInterceptRequest(view, url);
                    }

                    String cleanedUrlWithQueryFragment = cleanResourceUrl(url, true);
                    String httpUrl = "http://" + EpubServer.HTTP_HOST + ":" + EpubServer.HTTP_PORT + "/"
                            + cleanedUrlWithQueryFragment;
                    Log.e(TAG, "FILE to HTTP REDIRECT: " + httpUrl);

                    try {
                        URLConnection c = new URL(httpUrl).openConnection();
                        ((HttpURLConnection) c).setUseCaches(false);
                        if (mime == "application/xhtml+xml" || mime == "text/html") {
                            ((HttpURLConnection) c).setRequestProperty("Accept-Ranges", "none");
                        }
                        InputStream is = c.getInputStream();
                        return new WebResourceResponse(mime, null, is);
                    } catch (Exception ex) {
                        Log.e(TAG, "FAIL: " + httpUrl + " -- " + ex.getMessage(), ex);
                    }
                }
                if (!quiet)
                    Log.d(TAG, "RESOURCE FETCH ... " + url);
                return super.shouldInterceptRequest(view, url);
            }

            Log.e(TAG, "NULL URL RESPONSE: " + url);
            return new WebResourceResponse(null, UTF_8, new ByteArrayInputStream("".getBytes()));
        }
    }

    private String cleanResourceUrl(String url, boolean preserveQueryFragment) {

        String cleanUrl = null;

        String httpUrl = "http://" + EpubServer.HTTP_HOST + ":" + EpubServer.HTTP_PORT;
        if (url.startsWith(httpUrl)) {
            cleanUrl = url.replaceFirst(httpUrl, "");
        } else {
            cleanUrl = (url.startsWith(ASSET_PREFIX)) ? url.replaceFirst(ASSET_PREFIX, "")
                    : url.replaceFirst("file://", "");
        }

        String basePath = mPackage.getBasePath();
        if (basePath.charAt(0) != '/') {
            basePath = '/' + basePath;
        }
        if (cleanUrl.charAt(0) != '/') {
            cleanUrl = '/' + cleanUrl;
        }
        cleanUrl = (cleanUrl.startsWith(basePath)) ? cleanUrl.replaceFirst(basePath, "") : cleanUrl;

        if (cleanUrl.charAt(0) == '/') {
            cleanUrl = cleanUrl.substring(1);
        }

        if (!preserveQueryFragment) {
            int indexOfQ = cleanUrl.indexOf('?');
            if (indexOfQ >= 0) {
                cleanUrl = cleanUrl.substring(0, indexOfQ);
            }

            int indexOfSharp = cleanUrl.indexOf('#');
            if (indexOfSharp >= 0) {
                cleanUrl = cleanUrl.substring(0, indexOfSharp);
            }
        }

        return cleanUrl;
    }

    public class EpubWebChromeClient extends WebChromeClient
            implements MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {
            if (!quiet)
                Log.d(TAG, "here in on ShowCustomView: " + view);
            super.onShowCustomView(view, callback);
            if (view instanceof FrameLayout) {
                FrameLayout frame = (FrameLayout) view;
                if (!quiet)
                    Log.d(TAG, "frame.getFocusedChild(): " + frame.getFocusedChild());
                if (frame.getFocusedChild() instanceof VideoView) {
                    VideoView video = (VideoView) frame.getFocusedChild();
                    // frame.removeView(video);
                    // a.setContentView(video);
                    video.setOnCompletionListener(this);
                    video.setOnErrorListener(this);
                    video.start();
                }
            }
        }

        public void onCompletion(MediaPlayer mp) {
            if (!quiet)
                Log.d(TAG, "Video completed");

            // a.setContentView(R.layout.main);
            // WebView wb = (WebView) a.findViewById(R.id.webview);
            // a.initWebView();
        }

        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {

            Log.e(TAG, "MediaPlayer onError: " + what + ", " + extra);
            return false;
        }
    }

    public class EpubInterface {

        @JavascriptInterface
        public void onPaginationChanged(String currentPagesInfo) {
            if (!quiet)
                Log.d(TAG, "onPaginationChanged: " + currentPagesInfo);
            try {
                PaginationInfo paginationInfo = PaginationInfo.fromJson(currentPagesInfo);
                List<Page> openPages = paginationInfo.getOpenPages();
                if (!openPages.isEmpty()) {
                    final Page page = openPages.get(0);
                    runOnUiThread(new Runnable() {
                        public void run() {
                            mPageInfo.setText(getString(R.string.page_x_of_y, page.getSpineItemPageIndex() + 1,
                                    page.getSpineItemPageCount()));
                            SpineItem spineItem = mPackage.getSpineItem(page.getIdref());
                            boolean isFixedLayout = spineItem.isFixedLayout(mPackage);
                            mWebview.getSettings().setBuiltInZoomControls(isFixedLayout);
                            mWebview.getSettings().setDisplayZoomControls(false);
                        }
                    });
                }
            } catch (JSONException e) {
                Log.e(TAG, "" + e.getMessage(), e);
            }
        }

        @JavascriptInterface
        public void onSettingsApplied() {
            if (!quiet)
                Log.d(TAG, "onSettingsApplied");
        }

        @JavascriptInterface
        public void onReaderInitialized() {
            if (!quiet)
                Log.d(TAG, "onReaderInitialized");

            if (!quiet)
                Log.d(TAG, "openPageRequestData: " + mOpenPageRequestData);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mReadiumJSApi.openBook(mPackage, mViewerSettings, mOpenPageRequestData);
                }
            });

        }

        @JavascriptInterface
        public void onContentLoaded() {
            if (!quiet)
                Log.d(TAG, "onContentLoaded");
        }

        @JavascriptInterface
        public void onPageLoaded() {
            if (!quiet)
                Log.d(TAG, "onPageLoaded");
        }

        @JavascriptInterface
        public void onIsMediaOverlayAvailable(String available) {
            if (!quiet)
                Log.d(TAG, "onIsMediaOverlayAvailable:" + available);
            mIsMoAvailable = available.equals("true");

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    invalidateOptionsMenu();
                }
            });
        }

        @JavascriptInterface
        public void onMediaOverlayStatusChanged(String status) {
            if (!quiet)
                Log.d(TAG, "onMediaOverlayStatusChanged:" + status);
            // this should be real json parsing if there will be more data that
            // needs to be extracted

            if (status.indexOf("isPlaying") > -1) {
                mIsMoPlaying = status.indexOf("\"isPlaying\":true") > -1;
            }

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    invalidateOptionsMenu();
                }
            });
        }

        //
        // @JavascriptInterface
        // public void onMediaOverlayTTSSpeak() {
        // Log.d(TAG, "onMediaOverlayTTSSpeak");
        // }
        //
        // @JavascriptInterface
        // public void onMediaOverlayTTSStop() {
        // Log.d(TAG, "onMediaOverlayTTSStop");
        // }

        @JavascriptInterface
        public void getBookmarkData(final String bookmarkData) {
            AlertDialog.Builder builder = new AlertDialog.Builder(WebViewActivity.this)
                    .setTitle(R.string.add_bookmark);

            final EditText editText = new EditText(WebViewActivity.this);
            editText.setId(android.R.id.edit);
            editText.setHint(R.string.title);
            builder.setView(editText);
            builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if (which == DialogInterface.BUTTON_POSITIVE) {
                        String title = editText.getText().toString();
                        try {
                            JSONObject bookmarkJson = new JSONObject(bookmarkData);
                            BookmarkDatabase.getInstance().addBookmark(mContainer.getName(), title,
                                    bookmarkJson.getString("idref"), bookmarkJson.getString("contentCFI"));
                        } catch (JSONException e) {
                            Log.e(TAG, "" + e.getMessage(), e);
                        }
                    }
                }
            });
            builder.setNegativeButton(android.R.string.cancel, null);
            builder.show();
        }
    }
}