reader/android: pass the blob stream directly to pdf.js
Instead of creating a local cache file every time, the blob reader
stream is passed directly to pdf.js.
Also fixes some async logic bug in PdfViewWrapper with SettableFuture.
Change-Id: I519400632e46539ebfbed6a0b00c3581501171ca
diff --git a/android/app/src/main/java/io/v/android/apps/reader/PdfViewWrapper.java b/android/app/src/main/java/io/v/android/apps/reader/PdfViewWrapper.java
index 7ceb503..3a6ee81 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/PdfViewWrapper.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/PdfViewWrapper.java
@@ -15,11 +15,14 @@
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.SettableFuture;
+
import java.io.InputStream;
+import io.v.android.apps.reader.db.DB;
+
/**
* Wrapper class for the PDF Viewer library.
*
@@ -29,40 +32,21 @@
private static final String TAG = PdfViewWrapper.class.getSimpleName();
+ private SettableFuture<Boolean> mPageLoaded;
private int mPageCount;
public PdfViewWrapper(Context context, AttributeSet attrs) {
super(context, attrs);
+
+ mPageLoaded = SettableFuture.create();
}
public void init() {
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(true);
settings.setAllowUniversalAccessFromFileURLs(true);
-
setWebChromeClient(new WebChromeClient());
-
- setWebViewClient(new WebViewClient() {
- @Override
- public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
- Log.i(TAG, "shouldInterceptRequest called");
-
- File file = new File(request.getUrl().getPath());
- Log.i(TAG, "file path: " + file.getPath());
-
- try {
- // Should NOT close the stream here, so that the stream can be read by WebView.
- InputStream inputStream = new FileInputStream(file);
-
- Log.i(TAG, "returning a custom WebResourceResponse");
- return new WebResourceResponse("application/pdf", "binary", inputStream);
- } catch (IOException e) {
- Log.i(TAG, "falling back to super.shouldInterceptRequest");
- return super.shouldInterceptRequest(view, request);
- }
- }
- });
-
+ setWebViewClient(new PdfViewClient());
addJavascriptInterface(new JSInterface(), "android");
loadUrl("file:///android_asset/pdfjs/pdf-web-view.html");
@@ -70,13 +54,23 @@
/**
* Loads the PDF file at the given path into the pdf.js component within WebView.
- * NOTE: must be called after the page loading is finished.
*/
- public void loadPdfFile(String filePath) {
- evaluateJavascript("window.client.open(\"" + filePath + "\");", null);
+ public void loadPdfFile(final String filePath) {
+ Futures.addCallback(mPageLoaded, new FutureCallback<Boolean>() {
+ @Override
+ public void onSuccess(Boolean result) {
+ Log.i(TAG, "loadPdfFile called: " + filePath);
+ evaluateJavascript("window.client.open(\"" + filePath + "\");", null);
- // leave the page count as 0 until the page count value is properly set from JS side.
- mPageCount = 0;
+ // leave the page count as 0 until the page count value is properly set from JS side.
+ mPageCount = 0;
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ // Nothing to do.
+ }
+ });
}
/**
@@ -106,4 +100,38 @@
}
}
+ private class PdfViewClient extends WebViewClient {
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
+ Log.i(TAG, "shouldInterceptRequest called");
+
+ String path = request.getUrl().getPath();
+ if (!path.startsWith("/file_id/")) {
+ Log.i(TAG, "Not a file id path. Falling back to super.shouldInterceptRequest");
+ return super.shouldInterceptRequest(view, request);
+ }
+
+ String fileId = request.getUrl().getLastPathSegment();
+ Log.i(TAG, "File ID: " + fileId);
+
+ // Should NOT close the stream here, so that the stream can be read by WebView.
+ InputStream in = DB.Singleton.get(getContext()).getInputStreamForFile(fileId);
+
+ if (in != null) {
+ Log.i(TAG, "returning a custom WebResourceResponse");
+ return new WebResourceResponse("application/pdf", "binary", in);
+ } else {
+ Log.i(TAG, "Could not open an input stream. " +
+ "Falling back to super.shouldInterceptRequest");
+ return super.shouldInterceptRequest(view, request);
+ }
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ mPageLoaded.set(true);
+ }
+ }
+
}
diff --git a/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java b/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
index 2dfe9b7..ab57c61 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
@@ -16,14 +16,10 @@
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.Toast;
import com.google.android.gms.analytics.HitBuilders;
import com.google.common.io.ByteStreams;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
@@ -297,41 +293,12 @@
}
private void joinDeviceSet(DeviceSet ds) {
- // Get the file contents from the database
- // TODO(youngseokyoon): get the blob asynchronously. right now, it's blocking the UI thread.
- File file = getDB().getFileList().getItemById(ds.getFileId());
- byte[] bytes = getDB().readBytes(file);
- if (bytes == null) {
- Toast.makeText(this, "Could not load the file contents.", Toast.LENGTH_LONG).show();
- return;
- }
-
- // The pdf viewer widget requires the file to be an actual java.io.File object.
- // Create a temporary file and write the contents.
- final java.io.File jFile = new java.io.File(getCacheDir(), ds.getFileId());
- try (FileOutputStream out = new FileOutputStream(jFile)) {
- out.write(bytes);
- } catch (IOException e) {
- handleException(e);
- }
-
- // Initialize the pdf viewer widget with the file content.
- Log.i(TAG, "File path: " + jFile.getPath());
-
- // TODO(youngseokyoon): move this logic to PdfViewWrapper
- mPdfView.setWebViewClient(new WebViewClient() {
- @Override
- public void onPageFinished(WebView view, String url) {
- super.onPageFinished(view, url);
- mPdfView.loadPdfFile(jFile.getPath());
-
- writeNavigationAction("Page Changed", 1);
- }
- });
+ mPdfView.loadPdfFile("/file_id/" + ds.getFileId());
// Create a new device meta, and update the device set with it.
Log.i(TAG, "Joining device set: " + ds.getId());
DeviceMeta dm = createDeviceMeta();
+ // TODO(youngseokyoon): don't wait till these operations are finished.
ds.getDevices().put(dm.getDeviceId(), dm);
getDB().updateDeviceSet(ds);
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
index 1dd7aad..28b0b69 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
@@ -7,6 +7,8 @@
import android.app.Activity;
import android.content.Context;
+import java.io.InputStream;
+
import io.v.android.apps.reader.model.Listener;
import io.v.android.apps.reader.vdl.Device;
import io.v.android.apps.reader.vdl.DeviceSet;
@@ -156,11 +158,18 @@
File storeBytes(byte[] bytes, String title);
/**
- * Reads the bytes from the File object.
+ * Opens an {@link InputStream} for the given file.
*
* @param file the file to be read.
- * @return the file contents as a byte array.
+ * @return the input stream for the given file, or null if an error occurs.
*/
- byte[] readBytes(File file);
+ InputStream getInputStreamForFile(File file);
+
+ /**
+ * Opens an {@link InputStream} for the given file id.
+ * @param fileId the id of the file to be read.
+ * @return the input stream for the given file id, or null if an error occurs.
+ */
+ InputStream getInputStreamForFile(String fileId);
}
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
index e0e55a4..c7f2497 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
@@ -8,11 +8,10 @@
import android.content.Context;
import android.util.Log;
-import com.google.common.io.ByteStreams;
-
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
@@ -215,14 +214,20 @@
}
@Override
- public byte[] readBytes(File file) {
- java.io.File jFile = new java.io.File(mContext.getCacheDir(), file.getId());
- try (FileInputStream in = new FileInputStream(jFile)) {
- return ByteStreams.toByteArray(in);
+ public InputStream getInputStreamForFile(File file) {
+ return getInputStreamForFile(file.getId());
+ }
+
+ @Override
+ public InputStream getInputStreamForFile(String fileId) {
+ java.io.File jFile = new java.io.File(mContext.getCacheDir(), fileId);
+ try {
+ return new FileInputStream(jFile);
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
}
return null;
}
+
}
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
index 6959473..6216a39 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
@@ -14,7 +14,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
-import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -22,6 +21,7 @@
import org.apache.commons.io.FileUtils;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
@@ -518,22 +518,32 @@
}
@Override
- public byte[] readBytes(File file) {
+ public InputStream getInputStreamForFile(File file) {
if (file == null || file.getRef() == null) {
return null;
}
try {
BlobReader reader = mLocalSB.db.readBlob(mVContext, file.getRef());
- return ByteStreams.toByteArray(reader.stream(mVContext, 0L));
- } catch (VException | IOException e) {
- handleError("Could not read the blob " + file.getRef().toString()
+ return reader.stream(mVContext, 0L);
+ } catch (VException e) {
+ handleError("Could not open the input stream for file " + file.getRef().toString()
+ ": " + e.getMessage());
}
return null;
}
+ @Override
+ public InputStream getInputStreamForFile(String fileId) {
+ try {
+ File file = (File) sync(mLocalSB.files.get(mVContext, fileId, File.class));
+ return getInputStreamForFile(file);
+ } catch (VException e) {
+ return null;
+ }
+ }
+
private void handleError(String msg) {
Log.e(TAG, msg);
Toast.makeText(mContext, msg, Toast.LENGTH_LONG).show();