Java tutorial
/* * 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.biblemesh; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.Calendar; import java.util.List; import java.util.TimeZone; import org.json.JSONArray; 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.biblemesh.model.BookmarkDatabase; import org.readium.sdk.android.biblemesh.model.OpenPageRequest; import org.readium.sdk.android.biblemesh.model.Page; import org.readium.sdk.android.biblemesh.model.PaginationInfo; import org.readium.sdk.android.biblemesh.model.ReadiumJSApi; import org.readium.sdk.android.biblemesh.model.ViewerSettings; import org.readium.sdk.android.biblemesh.util.EpubServer; import org.readium.sdk.android.biblemesh.util.EpubServer.DataPreProcessor; import org.readium.sdk.android.biblemesh.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.graphics.Color; import android.graphics.Typeface; import android.media.MediaPlayer; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; 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.text.Layout; import android.text.SpannableStringBuilder; import android.util.Log; import android.util.TypedValue; import android.view.ActionMode; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.Transformation; import android.webkit.CookieManager; 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.ProgressBar; import android.widget.VideoView; import static org.readium.sdk.android.biblemesh.R.menu.container; 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"; private Handler hide2; private Runnable r; // 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 ProgressBar mProgress; private ViewerSettings mViewerSettings; private ReadiumJSApi mReadiumJSApi; private EpubServer mServer; private boolean mIsMoAvailable; private boolean mIsMoPlaying; private int mEpubRsoInjectCounter = 0; class MyGestureListener extends GestureDetector.SimpleOnGestureListener { private static final String DEBUG_TAG = "Gestures"; /*@Override public boolean onDown(MotionEvent event) { Log.d(DEBUG_TAG,"onDown: " + event.toString()); return false; }*/ /*@Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { mIsScrolling = true; // Make sure that mTextView is the text view you want to move around if (!(mWebview.getLayoutParams() instanceof ViewGroup.MarginLayoutParams)) { return false; } Log.v("scroll", "scroll"+distanceX+" "+e2.toString()+" "+e1.toString()); ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mWebview.getLayoutParams(); marginLayoutParams.leftMargin = marginLayoutParams.leftMargin - (int) distanceX; marginLayoutParams.rightMargin = -marginLayoutParams.leftMargin;// marginLayoutParams.rightMargin + (int) distanceX; mWebview.requestLayout(); return true; }*/ /*@Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { //Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString()); if (velocityX > 0) { Log.i("fling", "left"); mReadiumJSApi.openPageLeft(); } else { Log.i("fling", "right"); mReadiumJSApi.openPageRight(); } return true; }*/ } private void snapBack() { if (mWebview.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { final ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mWebview .getLayoutParams(); final int startValueX = marginLayoutParams.leftMargin; final int endValueX = 0; //final float startValueAlpha = mWebview.getAlpha(); mWebview.clearAnimation(); Animation animation = new Animation() { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { int leftMarginInterpolatedValue = (int) (startValueX + (endValueX - startValueX) * interpolatedTime); //float alphaInterpolatedValue = startValueAlpha -startValueAlpha * interpolatedTime; marginLayoutParams.leftMargin = leftMarginInterpolatedValue; marginLayoutParams.rightMargin = -leftMarginInterpolatedValue; //mWebview.setAlpha(alphaInterpolatedValue); mWebview.requestLayout(); } }; animation.setDuration(200); animation.setInterpolator(new DecelerateInterpolator()); mWebview.startAnimation(animation); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); setContentView(R.layout.activity_web_view); final int abTitleId = getResources().getIdentifier("action_bar_title", "id", "android"); findViewById(abTitleId).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mWebview = (WebView) findViewById(R.id.webview); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && 0 != (getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) { WebView.setWebContentsDebuggingEnabled(true); } mProgress = (ProgressBar) findViewById(R.id.progressBar); initWebView(); final GestureDetector gestureDetector = new GestureDetector(this, new MyGestureListener()); mWebview.setOnTouchListener(new View.OnTouchListener() { private final static long MAX_TOUCH_DURATION = 150; float lastEventX; float m_DownTime; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastEventX = event.getX(); m_DownTime = event.getEventTime(); //init time break; case MotionEvent.ACTION_MOVE: { float distanceX = lastEventX - event.getX(); ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mWebview .getLayoutParams(); marginLayoutParams.leftMargin = marginLayoutParams.leftMargin - (int) distanceX; marginLayoutParams.rightMargin = -marginLayoutParams.leftMargin;// marginLayoutParams.rightMargin + (int) distanceX; mWebview.requestLayout(); } break; case MotionEvent.ACTION_UP: { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mWebview .getLayoutParams(); if (marginLayoutParams.leftMargin < 10 && marginLayoutParams.leftMargin > -10) { Log.i("up", "small margin, open menu?"); if (event.getEventTime() - m_DownTime <= MAX_TOUCH_DURATION) { Log.i("up", "quick"); showActionBar(null); } else { Log.i("up", "too long"); } } } case MotionEvent.ACTION_CANCEL: { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mWebview .getLayoutParams(); //Log.i("snap", "snap width: "+mWebview.getWidth()+" left:" + marginLayoutParams.leftMargin + " raw:" + event.getRawX() + " " + event.getX());//+" "+e2.toString()+" "+e1.toString()); //mWebview.getWidth() if (marginLayoutParams.leftMargin < -0.5 * mWebview.getWidth()) { mReadiumJSApi.openPageRight(); mWebview.setAlpha(0.0f); } else if (marginLayoutParams.leftMargin > 0.5 * mWebview.getWidth()) { mReadiumJSApi.openPageLeft(); mWebview.setAlpha(0.0f); } else { snapBack(); //return true; } } break; } ; return gestureDetector.onTouchEvent(event); } }); /*mWebview.setHapticFeedbackEnabled(false);*/ 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.SINGLE, ViewerSettings.ScrollMode.AUTO, 100, 20); mReadiumJSApi = new ReadiumJSApi(new ReadiumJSApi.JSLoader() { @Override public void loadJS(String javascript) { mWebview.loadUrl(javascript); } }); /*Button back = (Button)findViewById(R.id.btnBack); back.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); if(getActionBar.isShowing()) { hideActionBar(); } else { getActionBar.show(); hideActionBar(); } } });*/ r = new Runnable() { @Override public void run() { getActionBar().hide(); //getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); } }; hide2 = null; hideActionBar(); //ActionBar actionBar = getActionBar(); //actionBar.hide(); //getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); } public void showActionBar(View v) { getActionBar().show(); hideActionBar(); } public void hideActionBar() { if (hide2 != null) { hide2.removeCallbacks(r); } hide2 = new Handler(); hide2.postDelayed(r, 3000); // e.g. 3000 milliseconds } @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().setMediaPlaybackRequiresUserGesture(false); } 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.toc: if (!quiet) Log.d(TAG, "Show toc"); showToC(); 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 = ViewerSettingsDialog.newInstance(this, mViewerSettings); /*Bundle args = new Bundle(); args.putParcelable("settings", (Parcelable) mViewerSettings); args.putParcelable("listener", (Parcelable) this); dialog.setArguments(args);*/ dialog.show(fm, "dialog"); fragmentTransaction.commit(); } private void showToC() { Log.v("web", "show ToC"); Intent intent = new Intent(WebViewActivity.this, TableOfContentsActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Constants.BOOK_NAME, ContainerList.container.getName()); intent.putExtra(Constants.CONTAINER_ID, ContainerList.container.getNativePtr()); startActivity(intent); finish(); } @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); } Typeface face = Typeface.createFromAsset(getAssets(), "fonts/fontawesome-webfont.ttf"); TextDrawable faIcon = new TextDrawable(this); faIcon.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); faIcon.setTextAlign(Layout.Alignment.ALIGN_CENTER); faIcon.setTextColor(Color.parseColor("#ffffff")); faIcon.setTypeface(face); faIcon.setText(getResources().getText(R.string.fa_cog)); TextDrawable faIcon2 = new TextDrawable(this); faIcon2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); faIcon2.setTextAlign(Layout.Alignment.ALIGN_CENTER); faIcon2.setTextColor(Color.parseColor("#ffffff")); faIcon2.setTypeface(face); faIcon2.setText(getResources().getText(R.string.fa_toc)); MenuItem menuItem = menu.findItem(R.id.settings); menuItem.setIcon(faIcon); MenuItem menuItem2 = menu.findItem(R.id.toc); menuItem2.setIcon(faIcon2); /*MenuItem menuItem3 = menu.findItem(R.id.toc); item.setOnMenuItemClickListener ( new MenuItem.OnMenuItemClickListener () { public boolean onMenuItemClick(MenuItem item) { return (showDirectory(item)); } } );*/ 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, "onPaginationChanged1: " + currentPagesInfo); try { PaginationInfo paginationInfo = PaginationInfo.fromJson(currentPagesInfo); List<Page> openPages = paginationInfo.getOpenPages(); if (!openPages.isEmpty()) { final Page page = openPages.get(0); final int tpages = mPackage.getSpineItems().size() * page.getSpineItemPageCount(); final int pindex = page.getSpineItemPageIndex() + 1 + page.getSpineItemIndex() * page.getSpineItemPageCount(); runOnUiThread(new Runnable() { public void run() { mProgress.setMax(tpages); mProgress.setProgress(pindex); SpineItem spineItem = mPackage.getSpineItem(page.getIdref()); boolean isFixedLayout = spineItem.isFixedLayout(mPackage); mWebview.getSettings().setBuiltInZoomControls(isFixedLayout); mWebview.getSettings().setDisplayZoomControls(false); //Log.i("openpage", "reset margins"); ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) mWebview .getLayoutParams(); marginLayoutParams.leftMargin = marginLayoutParams.rightMargin = 0;//-marginLayoutParams.leftMargin;// marginLayoutParams.rightMargin + (int) distanceX; mWebview.setAlpha(1.0f); mWebview.requestLayout(); Long unixtime = Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTimeInMillis() + LoginActivity.serverTimeOffset; Integer annotationID = -1; Boolean del = false; mReadiumJSApi.updateLocation(unixtime, annotationID, del); updateHighlights(page.getIdref()); } }); } } 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, "onIsMediaOverlayAvailablee:" + 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 updateLocation(final String bookmarkData, final String unixtimestr, final String annotationIDstr, final String delstr) { if (!quiet) Log.d(TAG, "updateHighlights:" + bookmarkData); try { JSONObject bookmarkJson = new JSONObject(bookmarkData); Long unixtime = Long.parseLong(unixtimestr); Integer annotationID = Integer.parseInt(annotationIDstr); Boolean del = false; if (delstr.equals("1")) { del = true; } //update local values DBHelper dbHelper = DBHelper.getInstance(WebViewActivity.this);//new DBHelper(WebViewActivity.this); if (annotationID == -1) { dbHelper.setLocation(LoginActivity.bookID, bookmarkJson.getString("idref"), bookmarkJson.getString("contentCFI"), unixtime); } else { } //Log.v("webview", "unix:"+unixtime+ "ts:"+ts1+" ts2:"+ts2); HttpURLConnection httpConn = null; try { JSONObject postDict = new JSONObject(); JSONObject latest_location = new JSONObject(); latest_location.put("idref", bookmarkJson.getString("idref")); latest_location.put("elementCfi", bookmarkJson.getString("contentCFI")); String locStr = latest_location.toString(); String locStr2 = locStr.replace("\\/", "/"); postDict.put("latest_location", locStr2); postDict.put("updated_at", unixtime); JSONArray highlights = new JSONArray(); if (annotationID == -1) { } else { DBCursor hc = dbHelper.getHighlight(LoginActivity.bookID, annotationID); JSONObject hld = new JSONObject(); hld.put("spineIdRef", hc.getColIDRef()); hld.put("cfi", hc.getColCFI()); hld.put("color", hc.getColColor()); hld.put("note", hc.getColNote()); hld.put("updated_at", hc.getColLastUpdated()); if (del) { hld.put("_delete", true); } highlights.put(hld); } postDict.put("highlights", highlights); String patch = postDict.toString(); String patch2 = patch.replace("\\/", "/"); String url = "https://read.biblemesh.com/users/" + LoginActivity.userID + "/books/" + LoginActivity.bookID + ".json"; URL resourceUrl = new URL(url); httpConn = (HttpURLConnection) resourceUrl.openConnection(); httpConn.setDoOutput(true); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { /*header override method httpConn.setRequestProperty("X-HTTP-Method-Override", "PATCH"); httpConn.setRequestMethod("POST");*/ httpConn.setRequestMethod("PUT"); } else { httpConn.setRequestMethod("PATCH"); } httpConn.setRequestProperty("Accept", "application/json"); httpConn.setRequestProperty("Content-Type", "application/json"); Integer len = patch2.length(); httpConn.setRequestProperty("Content-Length", len.toString()); //httpConn.setUseCaches(false); //httpConn.setAllowUserInteraction(false); //httpConn.setConnectTimeout(timeout); //httpConn.setReadTimeout(timeout); String cookies = CookieManager.getInstance().getCookie(url); if (cookies != null) { Log.v("getbookdatatask", "have cookies"); httpConn.setRequestProperty("Cookie", cookies); } httpConn.connect(); OutputStream os = httpConn.getOutputStream(); os.write(patch2.getBytes()); os.flush(); int responseCode = httpConn.getResponseCode(); Log.v("webview", "Response: " + responseCode); // always check HTTP response code first if (responseCode == HttpURLConnection.HTTP_OK) { BufferedReader br = new BufferedReader(new InputStreamReader(httpConn.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) { sb.append(line + "\n"); } br.close(); Log.v("webview", "update response:" + sb.toString()); } else if (responseCode == 412) { Log.v("webview", "got 412"); } else { System.out.println("Server replied HTTP code: " + responseCode); } } catch (IOException e) { Log.d("err", "Error: " + e); //vid[0].downloadStatus = 0; } catch (JSONException e) { Log.v("json", e.getMessage()); } finally { if (httpConn != null) { httpConn.disconnect(); } } } catch (JSONException e) { Log.e(TAG, "" + e.getMessage(), e); } } @JavascriptInterface public void updateHighlights(final String idref) {//fix could have more than one idref if (!quiet) Log.d(TAG, "updateLocation:" + idref); //// FIXME: 29/01/2017 runOnUiThread? mReadiumJSApi.removeHighlights(idref); } @JavascriptInterface public void removeHighlights(final String response, final String idref) { if (!quiet) Log.d(TAG, "removeHighlights:" + response); runOnUiThread(new Runnable() { @Override public void run() { DBHelper dbHelper = DBHelper.getInstance(WebViewActivity.this);//new DBHelper(WebViewActivity.this); DBCursor cursor = dbHelper.getHighlights(LoginActivity.bookID); for (int rowNum = 0; rowNum < cursor.getCount(); rowNum++) { cursor.moveToPosition(rowNum); if (idref.equals(cursor.getColIDRef())) { Integer random = (int) (Math.random() * 1000000 + 1); dbHelper.updateHighlight(cursor.getColID(), LoginActivity.bookID, cursor.getColColor(), cursor.getColNote(), cursor.getColLastUpdated(), random); mReadiumJSApi.addHighlight(cursor.getColIDRef(), cursor.getColCFI(), random); } else { Log.v("webview", "skip as idref not matched"); } } } }); } @JavascriptInterface public void highlightSelection(final String response) { if (!quiet) Log.d(TAG, "highlightSelection:" + response); try { final JSONObject selJson = new JSONObject(response); final String selidref = selJson.getString("idref"); final String selcfi = selJson.getString("cfi"); DBHelper dbHelper = DBHelper.getInstance(WebViewActivity.this);//new DBHelper(WebViewActivity.this); //check through current list for a repeat Boolean repeat = dbHelper.isRepeatHighlight(LoginActivity.bookID, selidref, selcfi); if (!repeat) { final Integer random = (int) (Math.random() * 1000000 + 1); final Long unixtime = Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTimeInMillis() + LoginActivity.serverTimeOffset; dbHelper.insertHighlight(LoginActivity.bookID, selidref, selcfi, 1, "", unixtime, random); runOnUiThread(new Runnable() { @Override public void run() { mReadiumJSApi.addHighlight(selidref, selcfi, random); mReadiumJSApi.updateLocation(unixtime, random, false); } }); } else { int hi; hi = 1; } } catch (JSONException e) { Log.v("webview", e.getMessage()); } } /* @JavascriptInterface public void addHighlight(final String response) { if (!quiet) Log.d(TAG, "addHighlight:" + response); runOnUiThread(new Runnable() { @Override public void run() { //if (idref.equals(cursor.getColIDRef())) { mReadiumJSApi.addHighlight(selidref, selcfi, random); //mReadiumJSApi.updateLocation(unixtime, random, false); //} else { // Log.v("webview", "skip as idref not matched"); //} //} } }); }*/ @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(); } @JavascriptInterface public void getHighlight(final String response) { if (!quiet) Log.d(TAG, "getHighlight:" + response); if (!highlightDlgOpen) { try { final JSONObject selJson = new JSONObject(response); final String id = selJson.getString("id"); final String selectedText = selJson.getString("selectedText"); // this should be real json parsing if there will be more data that // needs to be extracted final DBHelper dbHelper = DBHelper.getInstance(WebViewActivity.this);//new DBHelper(WebViewActivity.this); final DBCursor cursor = dbHelper.getHighlight(LoginActivity.bookID, Integer.parseInt(id)); final DBCursor cursor2 = dbHelper.getLocation(LoginActivity.bookID); final EditText editText = new EditText(WebViewActivity.this); editText.setLines(3); editText.setId(android.R.id.edit); editText.setHint("Notes"); editText.setText(cursor.getColNote()); AlertDialog.Builder builder = new AlertDialog.Builder(WebViewActivity.this) //.setTitle("") //.setMessage(id) .setView(editText).setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { // dialog dismiss without button press highlightDlgOpen = false; } }).setCancelable(true) .setNegativeButton("Delete", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { runOnUiThread(new Runnable() { @Override public void run() { mReadiumJSApi.updateLocation(cursor2.getColLastUpdated(), Integer.parseInt(id), true); } }); dialog.dismiss(); highlightDlgOpen = false; } }).setNeutralButton("Share", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try { String shareurl = URLEncoder.encode("{\"idref\":\"" + cursor.getColIDRef() + "\",\"elementCfi\":\"" + cursor.getColCFI() + "\"}", "UTF-8"); Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setType("text/plain"); sharingIntent.putExtra(Intent.EXTRA_TEXT, "https://read.biblemesh.com/book/" + LoginActivity.bookID + "?goto=" + shareurl + "&highlight=" + URLEncoder.encode(selectedText)); startActivity(Intent.createChooser(sharingIntent, "Share via")); dialog.dismiss(); highlightDlgOpen = false; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } }).setPositiveButton("Save", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dbHelper.updateHighlight(cursor.getColID(), LoginActivity.bookID, cursor.getColColor(), editText.getText().toString(), cursor.getColLastUpdated(), Integer.parseInt(id)); runOnUiThread(new Runnable() { @Override public void run() { mReadiumJSApi.updateLocation(cursor2.getColLastUpdated(), Integer.parseInt(id), false); } }); dialog.dismiss(); highlightDlgOpen = false; } }); //builder.setNegativeButton(android.R.string.cancel, null); builder.show(); highlightDlgOpen = true; } catch (JSONException e) { e.printStackTrace(); } } } } private ActionMode mActionMode = null; Boolean highlightDlgOpen = false; @Override public void onActionModeStarted(ActionMode mode) { if (mActionMode == null) { mActionMode = mode; Menu menu = mode.getMenu(); // Remove the default menu items (select all, copy, paste, search) menu.clear(); // If you want to keep any of the defaults, // remove the items you don't want individually: // menu.removeItem(android.R.id.[id_of_item_to_remove]) // Inflate your own menu items mode.getMenuInflater().inflate(R.menu.context_menu, menu); MenuItem item = menu.findItem(R.id.highlight_item); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { mReadiumJSApi.highlightSelection(); return true; } }); } super.onActionModeStarted(mode); } @Override public void onActionModeFinished(ActionMode mode) { mActionMode = null; super.onActionModeFinished(mode); } /* //http://stackoverflow.com/questions/14390908/how-to-control-the-android-webview-history-back-stack @Override public void onBackPressed() { if (mWebview.canGoBack()) { mWebview.goBack(); } else { super.onBackPressed(); } }*/ }