reader/android: AsyncTask version of the progress-bar
Use AsyncTask to load and store pdf files and display progress.
Now it can be properly cancelled in the middle of loading.
Change-Id: I73558a35070e1f1312660ab170d97a906f152071
diff --git a/android/app/build.gradle b/android/app/build.gradle
index f05cdc9..00a88cc 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -24,6 +24,9 @@
// It's going to use VDL.
apply plugin: 'io.v.vdl'
+// Retrolambda plugin
+apply plugin: 'me.tatarka.retrolambda'
+
// Conditionally apply the google services plugin, depending on existence of the configuration file.
// Also, add conditional source folders as source directories.
@@ -46,8 +49,8 @@
buildToolsVersion "23.0.1"
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
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 f531fd6..ac842b3 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
@@ -8,9 +8,8 @@
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.provider.OpenableColumns;
import android.support.v4.view.GestureDetectorCompat;
import android.util.Log;
@@ -22,14 +21,11 @@
import android.widget.TextView;
import com.google.android.gms.analytics.HitBuilders;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.Executors;
import io.v.android.apps.reader.db.DB;
import io.v.android.apps.reader.db.DB.DBList;
@@ -38,6 +34,7 @@
import io.v.android.apps.reader.vdl.DeviceMeta;
import io.v.android.apps.reader.vdl.DeviceSet;
import io.v.android.apps.reader.vdl.File;
+import io.v.v23.verror.VException;
/**
* Activity that shows the contents of the selected pdf file.
@@ -59,8 +56,7 @@
private DBList<DeviceSet> mDeviceSets;
private DeviceSet mCurrentDS;
- private ListeningExecutorService mThreadPool;
- private Handler mHandler;
+ private CreateAndJoinDeviceSetTask mCreateAndJoinDeviceSetTask;
/**
* Helper methods for creating an intent to start a PdfViewerActivity.
@@ -83,9 +79,6 @@
setContentView(R.layout.activity_pdf_viewer);
- mThreadPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
- mHandler = new Handler(Looper.getMainLooper());
-
mPdfView = (PdfViewWrapper) findViewById(R.id.pdfview);
mPdfView.init();
@@ -206,6 +199,15 @@
}
}
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (mCreateAndJoinDeviceSetTask != null) {
+ mCreateAndJoinDeviceSetTask.cancel(true);
+ }
+ }
+
// TODO(youngseokyoon): generalize these clone methods
private DeviceSet cloneDeviceSet(DeviceSet ds) {
if (ds == null) {
@@ -233,68 +235,8 @@
}
private void createAndJoinDeviceSet(final Uri fileUri) {
- mThreadPool.submit(new Runnable() {
- @Override
- public void run() {
- try {
- byte[] bytes = getBytesFromUri(fileUri);
- File file = createFile(bytes, getTitleFromUri(fileUri));
- final DeviceSet ds = createDeviceSet(file);
-
- // Join the device set from the UI thread.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- joinDeviceSet(ds);
- }
- });
- } catch (Exception e) {
- Log.e(TAG, "Could not create the device set: " + e.getMessage(), e);
-
- // In case of an error, finish this activity and go back to the previous one.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- finish();
- }
- });
- }
- }
- });
- }
-
- private File createFile(final byte[] bytes, final String title) throws IOException {
- initProgress(R.string.progress_writing_pdf, bytes.length);
-
- // Create a vdl File object representing this pdf file and put it in the db.
- DB.FileBuilder builder;
- try {
- builder = getDB().getFileBuilder(title);
- } catch (RuntimeException e) {
- throw new IOException(e);
- }
-
- int cur = 0;
- int available = bytes.length;
- while (available > 0) {
- int numBytes = Math.min(BLOCK_SIZE, available);
- builder.write(bytes, cur, numBytes);
- cur += numBytes;
- available -= numBytes;
-
- updateProgress(cur);
- }
-
- initProgress(R.string.progress_finishing_up_writing, -1);
- File vFile = builder.build();
-
- Log.i(TAG, "vFile created: " + vFile);
- if (vFile == null) {
- throw new IOException("Could not store the file content: " + title);
- }
- getDB().addFile(vFile);
-
- return vFile;
+ mCreateAndJoinDeviceSetTask = new CreateAndJoinDeviceSetTask();
+ mCreateAndJoinDeviceSetTask.execute(fileUri);
}
@Override
@@ -349,8 +291,6 @@
}
private DeviceSet createDeviceSet(File vFile) {
- initProgress(R.string.progress_creating_device_set, 1);
-
String id = IdFactory.getRandomId();
String fileId = vFile.getId();
Map<String, DeviceMeta> devices = new HashMap<>();
@@ -358,14 +298,10 @@
DeviceSet ds = new DeviceSet(id, fileId, devices);
getDB().addDeviceSet(ds);
- updateProgress(1);
-
return ds;
}
private void joinDeviceSet(DeviceSet ds) {
- showProgressWidgets(false);
-
mPdfView.loadPdfFile("/file_id/" + ds.getFileId());
// Create a new device meta, and update the device set with it.
@@ -397,48 +333,6 @@
mCurrentDS = null;
}
- private byte[] getBytesFromUri(final Uri uri) throws IOException {
- Log.i(TAG, "getBytesFromUri: " + uri.toString());
-
- InputStream in = getContentResolver().openInputStream(uri);
- int available = in.available();
-
- initProgress(R.string.progress_reading_source_pdf, available);
-
- byte[] result = new byte[available];
- int cur = 0;
- int bytesRead;
-
- while ((bytesRead = in.read(result, cur, Math.min(BLOCK_SIZE, available))) != -1 &&
- available > 0) {
- cur += bytesRead;
- available -= bytesRead;
- updateProgress(cur);
- }
-
- in.close();
-
- return result;
- }
-
- private String getTitleFromUri(Uri uri) {
- try {
- Cursor cursor = getContentResolver().query(uri, null, null, null, null);
-
- int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
- cursor.moveToFirst();
- return cursor.getString(nameIndex);
- } catch (Exception e) {
- handleException(e);
-
- if (uri != null) {
- return uri.getLastPathSegment();
- }
- }
-
- return null;
- }
-
private DeviceMeta getDeviceMeta() {
return getDeviceMeta(mCurrentDS);
}
@@ -621,13 +515,59 @@
requestCode, resultCode));
}
- private void initProgress(final int progressTextRes, final int maxProgress) {
- showProgressWidgets(true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mProgressText.setText(progressTextRes);
+ private void showProgressWidgets(final boolean showProgress) {
+ if (showProgress) {
+ mProgressText.setVisibility(View.VISIBLE);
+ mProgressBar.setVisibility(View.VISIBLE);
+ mPdfView.setVisibility(View.INVISIBLE);
+ } else {
+ mProgressText.setVisibility(View.INVISIBLE);
+ mProgressBar.setVisibility(View.INVISIBLE);
+ mPdfView.setVisibility(View.VISIBLE);
+ }
+ }
+ private class CreateAndJoinDeviceSetTask extends AsyncTask<Uri, Integer, DeviceSet> {
+
+ @Override
+ protected void onPreExecute() {
+ showProgressWidgets(true);
+ }
+
+ @Override
+ protected DeviceSet doInBackground(Uri... uris) {
+ Uri uri = null;
+ try {
+ uri = uris[0];
+ Log.i(TAG, "CreateAndJoinDeviceSetTask$doInBackground: " + uri.toString());
+
+ byte[] bytes = getBytesFromUri(uri);
+ if (isCancelled()) {
+ return null;
+ }
+
+ File file = createFile(bytes, getTitleFromUri(uri));
+ if (isCancelled()) {
+ return null;
+ }
+
+ publishProgress(R.string.progress_creating_device_set, -1);
+ DeviceSet ds = createDeviceSet(file);
+
+ return ds;
+ } catch (Exception e) {
+ Log.e(TAG, "Could not create the device set for uri: " + uri.toString() + ": "
+ + e.getMessage(), e);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ if (values.length == 2) {
+ mProgressText.setText(values[0]);
+
+ int maxProgress = values[1];
if (maxProgress >= 0) {
mProgressBar.setIndeterminate(false);
mProgressBar.setMax(maxProgress);
@@ -635,34 +575,110 @@
} else {
mProgressBar.setIndeterminate(true);
}
+ } else if (values.length == 1) {
+ mProgressBar.setProgress(values[0]);
}
- });
- }
+ }
- private void updateProgress(final int progress) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mProgressBar.setProgress(progress);
+ @Override
+ protected void onPostExecute(DeviceSet ds) {
+ PdfViewerActivity.this.mCreateAndJoinDeviceSetTask = null;
+
+ // In case of an error, finish this activity and go back to the previous one.
+ if (ds == null) {
+ finish();
+ return;
}
- });
- }
- private void showProgressWidgets(final boolean showProgress) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (showProgress) {
- mProgressText.setVisibility(View.VISIBLE);
- mProgressBar.setVisibility(View.VISIBLE);
- mPdfView.setVisibility(View.INVISIBLE);
- } else {
- mProgressText.setVisibility(View.INVISIBLE);
- mProgressBar.setVisibility(View.INVISIBLE);
- mPdfView.setVisibility(View.VISIBLE);
+ showProgressWidgets(false);
+ joinDeviceSet(ds);
+ }
+
+ private byte[] getBytesFromUri(final Uri uri) throws IOException {
+ Log.i(TAG, "getBytesFromUri: " + uri.toString());
+
+ InputStream in = getContentResolver().openInputStream(uri);
+ int total = in.available();
+ int available = total;
+
+ publishProgress(R.string.progress_reading_source_pdf, total);
+
+ byte[] result = new byte[available];
+ int cur = 0;
+ int bytesRead;
+
+ while ((bytesRead = in.read(result, cur, Math.min(BLOCK_SIZE, available))) != -1 &&
+ available > 0) {
+ cur += bytesRead;
+ available -= bytesRead;
+
+ publishProgress(cur);
+
+ if (isCancelled()) {
+ in.close();
+ return null;
}
}
- });
+
+ in.close();
+
+ return result;
+ }
+
+ private File createFile(final byte[] bytes, final String title) throws Exception {
+
+ publishProgress(R.string.progress_writing_pdf, bytes.length);
+
+ // Create a vdl File object representing this pdf file and put it in the db.
+ DB.FileBuilder builder;
+ builder = getDB().getFileBuilder(title);
+
+ int cur = 0;
+ int available = bytes.length;
+
+ while (available > 0) {
+ int numBytes = Math.min(BLOCK_SIZE, available);
+ builder.write(bytes, cur, numBytes);
+ cur += numBytes;
+ available -= numBytes;
+
+ publishProgress(cur);
+
+ if (isCancelled()) {
+ builder.cancel();
+ return null;
+ }
+ }
+
+ publishProgress(R.string.progress_finishing_up_writing, -1);
+ File vFile = builder.build();
+
+ Log.i(TAG, "vFile created: " + vFile);
+ if (vFile == null) {
+ throw new VException("Could not store the file content: " + title);
+ }
+ getDB().addFile(vFile);
+
+ return vFile;
+ }
+
+ private String getTitleFromUri(Uri uri) {
+ try {
+ Cursor cursor = getContentResolver().query(uri, null, null, null, null);
+
+ int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ cursor.moveToFirst();
+ return cursor.getString(nameIndex);
+ } catch (Exception e) {
+ handleException(e);
+
+ if (uri != null) {
+ return uri.getLastPathSegment();
+ }
+ }
+
+ return null;
+ }
}
}
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 9ddf29b..30a6ec2 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
@@ -45,6 +45,7 @@
interface FileBuilder extends Closeable {
void write(byte[] b, int off, int len) throws IOException;
+ void cancel();
File build();
}
@@ -159,7 +160,7 @@
* @param title title of this file.
* @return a {@link FileBuilder} object for building the {@link File}.
*/
- FileBuilder getFileBuilder(String title);
+ FileBuilder getFileBuilder(String title) throws Exception;
/**
* Opens an {@link InputStream} for the given file.
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 687e5d7..8cea9a2 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
@@ -14,7 +14,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
@@ -152,14 +151,8 @@
private long mSize;
private ByteArrayOutputStream mOutputStream;
- public FakeFileBuilder(String title) {
- try {
- mDigest = MessageDigest.getInstance("MD5");
- } catch (NoSuchAlgorithmException e) {
- Log.e(TAG, "Could not create md5 digest object: " + e.getMessage(), e);
- throw new RuntimeException(e);
- }
-
+ public FakeFileBuilder(String title) throws Exception {
+ mDigest = MessageDigest.getInstance("MD5");
mTitle = title;
mSize = 0L;
mOutputStream = new ByteArrayOutputStream();
@@ -173,6 +166,15 @@
}
@Override
+ public void cancel() {
+ try {
+ mOutputStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Could not cancel the writing: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
public File build() {
try {
mOutputStream.close();
@@ -257,7 +259,7 @@
}
@Override
- public FileBuilder getFileBuilder(String title) {
+ public FileBuilder getFileBuilder(String title) throws Exception {
return new FakeFileBuilder(title);
}
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 ad3dc21..aaff89a 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
@@ -24,7 +24,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -491,7 +490,7 @@
}
@Override
- public FileBuilder getFileBuilder(String title) {
+ public FileBuilder getFileBuilder(String title) throws Exception {
return new SyncbaseFileBuilder(title);
}
@@ -817,24 +816,13 @@
private BlobWriter mBlobWriter;
private OutputStream mOutputStream;
- public SyncbaseFileBuilder(String title) {
- try {
- mDigest = MessageDigest.getInstance("MD5");
- } catch (NoSuchAlgorithmException e) {
- Log.e(TAG, "Could not create md5 digest object: " + e.getMessage(), e);
- throw new RuntimeException(e);
- }
-
+ public SyncbaseFileBuilder(String title) throws Exception {
+ mDigest = MessageDigest.getInstance("MD5");
mTitle = title;
mSize = 0L;
- try {
- mBlobWriter = sync(mLocalSB.db.writeBlob(mVContext, null));
- mOutputStream = mBlobWriter.stream(mVContext);
- } catch (VException e) {
- Log.e(TAG, "Could not create SyncbaseFileBuilder: " + e.getMessage(), e);
- throw new RuntimeException(e);
- }
+ mBlobWriter = sync(mLocalSB.db.writeBlob(mVContext, null));
+ mOutputStream = mBlobWriter.stream(mVContext);
}
@Override
@@ -845,6 +833,16 @@
}
@Override
+ public void cancel() {
+ try {
+ mOutputStream.close();
+ mBlobWriter.delete(mVContext);
+ } catch (IOException e) {
+ Log.e(TAG, "Could not cancel the writing: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
public File build() {
try {
Log.i(TAG, "build() method called.");
diff --git a/android/build.gradle b/android/build.gradle
index cfecc13..0b9fd0a 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -10,6 +10,8 @@
// This is for sending log data to Google Analytics
classpath 'com.google.gms:google-services:1.5.0-beta2'
+ classpath 'me.tatarka:gradle-retrolambda:3.2.3'
+
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}