blob: a97099df59f74ac892622f79594f3efff16be571 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.android.apps.reader;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.OpenableColumns;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.google.common.io.ByteStreams;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import io.v.android.apps.reader.db.DB;
import io.v.android.apps.reader.db.DB.DBList;
import io.v.android.apps.reader.model.DeviceInfoFactory;
import io.v.android.apps.reader.model.IdFactory;
import io.v.android.apps.reader.model.Listener;
import io.v.android.apps.reader.vdl.DeviceMeta;
import io.v.android.apps.reader.vdl.DeviceSet;
import io.v.android.apps.reader.vdl.File;
/**
* Activity that shows the contents of the selected pdf file.
*/
public class PdfViewerActivity extends Activity {
private static final String TAG = PdfViewerActivity.class.getSimpleName();
private static final String EXTRA_DEVICE_SET_ID = "device_set_id";
private PdfViewWrapper mPdfView;
private Button mButtonPrev;
private Button mButtonNext;
private DB mDB;
private DBList<DeviceSet> mDeviceSets;
private DeviceSet mCurrentDS;
/**
* Helper methods for creating an intent to start a PdfViewerActivity.
*/
public static Intent createIntent(Context context, String deviceSetId) {
Intent intent = new Intent(context, PdfViewerActivity.class);
intent.putExtra(EXTRA_DEVICE_SET_ID, deviceSetId);
return intent;
}
public static Intent createIntent(Context context, Uri uri) {
Intent intent = new Intent(context, PdfViewerActivity.class);
intent.setData(uri);
return intent;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initialize the DB
mDB = DB.Singleton.get(this);
mDB.init(this);
setContentView(R.layout.activity_pdf_viewer);
mPdfView = (PdfViewWrapper) findViewById(R.id.pdfview);
mPdfView.init();
mButtonPrev = (Button) findViewById(R.id.button_prev);
mButtonNext = (Button) findViewById(R.id.button_next);
mButtonPrev.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
prevPage();
}
});
mButtonNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
nextPage();
}
});
}
@Override
protected void onStart() {
super.onStart();
/**
* Suppress the start process until the DB initialization is completed.
* onStart() method will be called again after the user selects her blessings.
*/
if (!mDB.isInitialized()) {
return;
}
mDeviceSets = mDB.getDeviceSetList();
mDeviceSets.setListener(new Listener() {
@Override
public void notifyItemChanged(int position) {
if (mCurrentDS == null) {
return;
}
DeviceSet changed = mDeviceSets.getItem(position);
if (!changed.getId().equals(mCurrentDS.getId())) {
return;
}
mCurrentDS = changed;
mPdfView.setPage(getDeviceMeta().getPage());
}
@Override
public void notifyItemInserted(int position) {
// Nothing to do
}
@Override
public void notifyItemRemoved(int position) {
// Nothing to do
}
});
Intent intent = getIntent();
if (intent.hasExtra(EXTRA_DEVICE_SET_ID)) {
/**
* Case #1.
* The EXTRA_DEVICE_SET_ID value is set when this activity is started by touching one of
* the existing device sets from the DeviceSetChooserActivity.
*/
Log.i(TAG, "onStart: Case #1: started by selecting an existing device set.");
// Get the device set from the DB and join it.
DeviceSet ds = mDeviceSets.getItemById(intent.getStringExtra(EXTRA_DEVICE_SET_ID));
joinDeviceSet(ds);
} else if (intent.getData() != null) {
/**
* Case #2.
* The intent.getData() is set as a content Uri when this activity is started by using
* the floating action button from the DeviceSetChooserActivity and selecting one of the
* local PDF files from the browser.
*/
Log.i(TAG, "onStart: Case #2: started by using the floating action button.");
Uri uri = intent.getData();
createAndJoinDeviceSet(uri);
} else if (intent.hasExtra(Intent.EXTRA_STREAM)) {
/**
* Case #3.
* The EXTRA_STREAM value is set when this activity is started by receiving an implicit
* intent from another app by sharing a PDF file to the reader app.
*/
Log.i(TAG, "onStart: Case #3: started by an implicit intent from another app.");
Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
createAndJoinDeviceSet(uri);
}
}
private void createAndJoinDeviceSet(Uri fileUri) {
// Get the file content.
byte[] bytes = getBytesFromUri(fileUri);
// Create a vdl File object representing this pdf file and put it in the db.
File vFile = mDB.storeBytes(bytes, getTitleFromUri(fileUri));
Log.i(TAG, "vFile created: " + vFile);
if (vFile == null) {
Log.e(TAG, "Could not store the file content of Uri: " + fileUri.toString());
}
mDB.addFile(vFile);
// Create a device set object and put it in the db.
DeviceSet ds = createDeviceSet(vFile);
mDB.addDeviceSet(ds);
// Join the device set.
joinDeviceSet(ds);
}
@Override
protected void onStop() {
super.onStop();
if (mDeviceSets != null) {
mDeviceSets.discard();
}
leaveDeviceSet();
}
private DeviceMeta createDeviceMeta() {
String deviceId = DeviceInfoFactory.getDeviceId(this);
int page = 1;
int zoom = 1;
boolean linked = true;
return new DeviceMeta(deviceId, page, zoom, linked);
}
private DeviceSet createDeviceSet(File vFile) {
String id = IdFactory.getRandomId();
String fileId = vFile.getId();
Map<String, DeviceMeta> devices = new HashMap<>();
return new DeviceSet(id, fileId, devices);
}
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 = mDB.getFileList().getItemById(ds.getFileId());
byte[] bytes = mDB.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.
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());
mPdfView.loadUrl("file:///android_asset/pdfjs/index.html?file=" + jFile.getPath());
// Create a new device meta, and update the device set with it.
Log.i(TAG, "Joining device set: " + ds.getId());
DeviceMeta dm = createDeviceMeta();
ds.getDevices().put(dm.getDeviceId(), dm);
mDB.updateDeviceSet(ds);
mCurrentDS = ds;
}
private void leaveDeviceSet() {
if (mCurrentDS == null) {
return;
}
Log.i(TAG, "Leaving device set: " + mCurrentDS.getId());
Map<String, DeviceMeta> devices = mCurrentDS.getDevices();
devices.remove(DeviceInfoFactory.getDeviceId(this));
if (devices.isEmpty()) {
Log.i(TAG, "Last one to leave the device set. Deleting " + mCurrentDS.getId());
mDB.deleteDeviceSet(mCurrentDS.getId());
} else {
mDB.updateDeviceSet(mCurrentDS);
}
mCurrentDS = null;
}
private byte[] getBytesFromUri(Uri uri) {
Log.i(TAG, "getBytesFromUri: " + uri.toString());
try (InputStream in = getContentResolver().openInputStream(uri)) {
// Get the entire file contents as a byte array.
return ByteStreams.toByteArray(in);
} catch (IOException e) {
handleException(e);
}
return null;
}
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);
}
private DeviceMeta getDeviceMeta(DeviceSet ds) {
String deviceId = DeviceInfoFactory.getDeviceId(this);
if (ds == null || !ds.getDevices().containsKey(deviceId)) {
return null;
}
return ds.getDevices().get(deviceId);
}
/**
* Move all the linked pages to their previous pages.
*/
private void prevPage() {
if (mCurrentDS == null) {
return;
}
// First, check if this device is linked or not.
// If not, simply move the page of the current device.
if (!getDeviceMeta().getLinked()) {
DeviceMeta dm = getDeviceMeta();
if (dm.getPage() > 1) {
dm.setPage(dm.getPage() - 1);
}
return;
}
// Move all the linked pages
Map<String, DeviceMeta> linkedDevices = getLinkedDevices();
int smallestPage = getSmallestPage(linkedDevices);
if (smallestPage > 1) {
for (String deviceId : linkedDevices.keySet()) {
DeviceMeta dm = linkedDevices.get(deviceId);
dm.setPage(dm.getPage() - 1);
}
mDB.updateDeviceSet(mCurrentDS);
}
}
/**
* Move all the linked pages to their next pages.
*/
private void nextPage() {
if (mCurrentDS == null) {
return;
}
// First, check if this device is linked or not.
// If not, simply move the page of the current device.
if (!getDeviceMeta().getLinked()) {
DeviceMeta dm = getDeviceMeta();
if (dm.getPage() < mPdfView.getPageCount()) {
dm.setPage(dm.getPage() + 1);
}
return;
}
// Move all the linked pages
Map<String, DeviceMeta> linkedDevices = getLinkedDevices();
int largestPage = getLargestPage(linkedDevices);
if (largestPage < mPdfView.getPageCount()) {
for (String deviceId : linkedDevices.keySet()) {
DeviceMeta dm = linkedDevices.get(deviceId);
dm.setPage(dm.getPage() + 1);
}
mDB.updateDeviceSet(mCurrentDS);
}
}
private Map<String, DeviceMeta> getLinkedDevices() {
if (mCurrentDS == null) {
return null;
}
Map<String, DeviceMeta> devices = mCurrentDS.getDevices();
Map<String, DeviceMeta> result = new HashMap<>();
for (String deviceId : devices.keySet()) {
DeviceMeta dm = devices.get(deviceId);
if (dm.getLinked()) {
result.put(deviceId, dm);
}
}
return result;
}
private int getSmallestPage(Map<String, DeviceMeta> devices) {
int result = -1;
for (String deviceId : devices.keySet()) {
DeviceMeta dm = devices.get(deviceId);
if (result == -1 || dm.getPage() < result) {
result = dm.getPage();
}
}
return result;
}
private int getLargestPage(Map<String, DeviceMeta> devices) {
int result = -1;
for (String deviceId : devices.keySet()) {
DeviceMeta dm = devices.get(deviceId);
if (result == -1 || dm.getPage() > result) {
result = dm.getPage();
}
}
return result;
}
private static void handleException(Exception e) {
Log.e(TAG, e.getMessage(), e);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.i(TAG, String.format("onActivityResult(%d, %d, data) called", requestCode, resultCode));
if (mDB.onActivityResult(requestCode, resultCode, data)) {
return;
}
// Any other activity results would be handled here.
Log.w(TAG, String.format(
"Unhandled activity result. (requestCode: %d, resultCode: %d)",
requestCode, resultCode));
}
}