summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorRobin Watts <robin.watts@artifex.com>2013-01-24 19:26:26 +0000
committerRobin Watts <robin.watts@artifex.com>2013-01-26 17:26:54 +0000
commit0cbf73e579846395c7b9f7ebd3f3f229d99104ac (patch)
treead21e1587baabc1316989fa2bcbcd4b7f7851378 /android
parente6f5e24b5d145cb571f82b2e01178be4c3eff662 (diff)
downloadmupdf-0cbf73e579846395c7b9f7ebd3f3f229d99104ac.tar.xz
Android: Fix NullPointerException seen on Google Play
According to Google Plays automated crash detection, we get a NullPointerException when trying to parse a null string as a Uri. This turns out to be caused by us trying to open a PDF attachment from gmail. This is because MuPDF is invoked with a content:// URL that does not have a file associated with it. Instead we can open that URL as an InputStream. Here we amend MuPDF to spot that case, and to open the InputStream, suck the data into a byteArray, and then to use that to open the file from.
Diffstat (limited to 'android')
-rw-r--r--android/jni/mupdf.c131
-rw-r--r--android/res/values/strings.xml3
-rw-r--r--android/src/com/artifex/mupdfdemo/MuPDFActivity.java74
-rw-r--r--android/src/com/artifex/mupdfdemo/MuPDFCore.java12
4 files changed, 213 insertions, 7 deletions
diff --git a/android/jni/mupdf.c b/android/jni/mupdf.c
index ecb9f20b..a4180e54 100644
--- a/android/jni/mupdf.c
+++ b/android/jni/mupdf.c
@@ -13,6 +13,7 @@
#endif
#include "fitz.h"
+#include "fitz-internal.h"
#include "mupdf.h"
#define JNI_FN(A) Java_com_artifex_mupdfdemo_ ## A
@@ -89,9 +90,15 @@ struct globals_s
int alert_reply;
pthread_cond_t alert_request_cond;
pthread_cond_t alert_reply_cond;
+
+ // For the buffer reading mode, we need to implement stream reading, which
+ // needs access to the following.
+ JNIEnv *env;
+ jclass thiz;
};
static jfieldID global_fid;
+static jfieldID buffer_fid;
static void drop_page_cache(globals *glo, page_cache *pc)
{
@@ -225,7 +232,10 @@ static void alerts_fin(globals *glo)
static globals *get_globals(JNIEnv *env, jobject thiz)
{
- return (globals *)(void *)((*env)->GetLongField(env, thiz, global_fid));
+ globals *glo = (globals *)(void *)((*env)->GetLongField(env, thiz, global_fid));
+ glo->env = env;
+ glo->thiz = thiz;
+ return glo;
}
JNIEXPORT jlong JNICALL
@@ -281,7 +291,7 @@ JNI_FN(MuPDFCore_openFile)(JNIEnv * env, jobject thiz, jstring jfilename)
}
fz_catch(ctx)
{
- fz_throw(ctx, "Cannot open document: '%s'\n", filename);
+ fz_throw(ctx, "Cannot open document: '%s'", filename);
}
LOGE("Done!");
}
@@ -301,6 +311,123 @@ JNI_FN(MuPDFCore_openFile)(JNIEnv * env, jobject thiz, jstring jfilename)
return (jlong)(void *)glo;
}
+static int bufferStreamRead(fz_stream *stream, unsigned char *buf, int len)
+{
+ globals *glo = (globals *)stream->state;
+ JNIEnv *env = glo->env;
+ jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid));
+ int arrayLength = (*env)->GetArrayLength(env, array);
+
+ if (stream->pos > arrayLength)
+ stream->pos = arrayLength;
+ if (stream->pos < 0)
+ stream->pos = 0;
+ if (len + stream->pos > arrayLength)
+ len = arrayLength - stream->pos;
+
+ (*env)->GetByteArrayRegion(env, array, stream->pos, len, buf);
+ (*env)->DeleteLocalRef(env, array);
+ return len;
+}
+
+static void bufferStreamClose(fz_context *ctx, void *state)
+{
+ /* Nothing to do */
+}
+
+static void bufferStreamSeek(fz_stream *stream, int offset, int whence)
+{
+ globals *glo = (globals *)stream->state;
+ JNIEnv *env = glo->env;
+ jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid));
+ int arrayLength = (*env)->GetArrayLength(env, array);
+
+ (*env)->DeleteLocalRef(env, array);
+
+ if (whence == 0) /* SEEK_SET */
+ stream->pos = offset;
+ else if (whence == 1) /* SEEK_CUR */
+ stream->pos += offset;
+ else if (whence == 2) /* SEEK_END */
+ stream->pos = arrayLength + offset;
+
+ if (stream->pos > arrayLength)
+ stream->pos = arrayLength;
+ if (stream->pos < 0)
+ stream->pos = 0;
+
+ stream->rp = stream->bp;
+ stream->wp = stream->bp;
+}
+
+JNIEXPORT jlong JNICALL
+JNI_FN(MuPDFCore_openBuffer)(JNIEnv * env, jobject thiz)
+{
+ globals *glo;
+ fz_context *ctx;
+ jclass clazz;
+ fz_stream *stream;
+
+#ifdef NDK_PROFILER
+ monstartup("libmupdf.so");
+#endif
+
+ clazz = (*env)->GetObjectClass(env, thiz);
+ global_fid = (*env)->GetFieldID(env, clazz, "globals", "J");
+
+ glo = calloc(1, sizeof(*glo));
+ if (glo == NULL)
+ return 0;
+ glo->resolution = 160;
+ glo->alerts_initialised = 0;
+ glo->env = env;
+ glo->thiz = thiz;
+ buffer_fid = (*env)->GetFieldID(env, clazz, "fileBuffer", "[B");
+
+ /* 128 MB store for low memory devices. Tweak as necessary. */
+ glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20);
+ if (!ctx)
+ {
+ LOGE("Failed to initialise context");
+ free(glo);
+ return 0;
+ }
+
+ glo->doc = NULL;
+ fz_try(ctx)
+ {
+ stream = fz_new_stream(ctx, glo, bufferStreamRead, bufferStreamClose);
+ stream->seek = bufferStreamSeek;
+
+ glo->colorspace = fz_device_rgb;
+
+ LOGE("Opening document...");
+ fz_try(ctx)
+ {
+ glo->current_path = NULL;
+ glo->doc = fz_open_document_with_stream(ctx, "", stream);
+ alerts_init(glo);
+ }
+ fz_catch(ctx)
+ {
+ fz_throw(ctx, "Cannot open memory document");
+ }
+ LOGE("Done!");
+ }
+ fz_catch(ctx)
+ {
+ LOGE("Failed: %s", ctx->error->message);
+ fz_close_document(glo->doc);
+ glo->doc = NULL;
+ fz_free_context(ctx);
+ glo->ctx = NULL;
+ free(glo);
+ glo = NULL;
+ }
+
+ return (jlong)(void *)glo;
+}
+
JNIEXPORT int JNICALL
JNI_FN(MuPDFCore_countPagesInternal)(JNIEnv *env, jobject thiz)
{
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
index 72794bcc..1468d963 100644
--- a/android/res/values/strings.xml
+++ b/android/res/values/strings.xml
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">MuPDF</string>
- <string name="version">1.1</string>
+ <string name="version">1.1 (Build 6)</string>
<string name="no_media_warning">Storage Media not present</string>
<string name="no_media_hint">Sharing the storage media with a PC can make it inaccessible</string>
<string name="open_failed">Unable to open document</string>
+ <string name="content_failure">%1$s: %2$s</string>
<string name="cancel">Cancel</string>
<string name="search_backwards">Search backwards</string>
<string name="search_forwards">Search forwards</string>
diff --git a/android/src/com/artifex/mupdfdemo/MuPDFActivity.java b/android/src/com/artifex/mupdfdemo/MuPDFActivity.java
index f41b6178..f095a041 100644
--- a/android/src/com/artifex/mupdfdemo/MuPDFActivity.java
+++ b/android/src/com/artifex/mupdfdemo/MuPDFActivity.java
@@ -1,6 +1,8 @@
package com.artifex.mupdfdemo;
import java.util.concurrent.Executor;
+import java.io.InputStream;
+import java.io.FileInputStream;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -12,6 +14,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.RectF;
@@ -260,6 +263,23 @@ public class MuPDFActivity extends Activity
return core;
}
+ private MuPDFCore openBuffer(byte buffer[])
+ {
+ System.out.println("Trying to open byte buffer");
+ try
+ {
+ core = new MuPDFCore(buffer);
+ // New file: drop the old outline data
+ OutlineActivityData.set(null);
+ }
+ catch (Exception e)
+ {
+ System.out.println(e);
+ return null;
+ }
+ return core;
+ }
+
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
@@ -292,6 +312,7 @@ public class MuPDFActivity extends Activity
}
if (core == null) {
Intent intent = getIntent();
+ byte buffer[] = null;
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
if (uri.toString().startsWith("content://")) {
@@ -300,10 +321,51 @@ public class MuPDFActivity extends Activity
// using explicit paths.
Cursor cursor = getContentResolver().query(uri, new String[]{"_data"}, null, null, null);
if (cursor.moveToFirst()) {
- uri = Uri.parse(cursor.getString(0));
+ String str = cursor.getString(0);
+ String failString = null;
+ if (str == null) {
+ try {
+ InputStream is = getContentResolver().openInputStream(uri);
+ int len = is.available();
+ buffer = new byte[len];
+ is.read(buffer, 0, len);
+ is.close();
+ }
+ catch (java.lang.OutOfMemoryError e)
+ {
+ System.out.println("Out of memory during buffer reading");
+ failString = e.toString();
+ }
+ catch (Exception e) {
+ failString = e.toString();
+ }
+ if (failString != null)
+ {
+ buffer = null;
+ Resources res = getResources();
+ AlertDialog alert = mAlertBuilder.create();
+ String contentFailure = res.getString(R.string.content_failure);
+ String openFailed = res.getString(R.string.open_failed);
+ setTitle(String.format(contentFailure, openFailed, failString));
+ alert.setButton(AlertDialog.BUTTON_POSITIVE, "Dismiss",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ });
+ alert.show();
+ return;
+ }
+ } else {
+ uri = Uri.parse(str);
+ }
}
}
- core = openFile(Uri.decode(uri.getEncodedPath()));
+ if (buffer != null) {
+ core = openBuffer(buffer);
+ } else {
+ core = openFile(Uri.decode(uri.getEncodedPath()));
+ }
SearchTaskResult.set(null);
}
if (core != null && core.needsPassword()) {
@@ -1023,17 +1085,21 @@ public class MuPDFActivity extends Activity
@Override
protected void onStart() {
if (core != null)
+ {
core.startAlerts();
+ createAlertWaiter();
+ }
- createAlertWaiter();
super.onStart();
}
@Override
protected void onStop() {
- destroyAlertWaiter();
if (core != null)
+ {
+ destroyAlertWaiter();
core.stopAlerts();
+ }
super.onStop();
}
diff --git a/android/src/com/artifex/mupdfdemo/MuPDFCore.java b/android/src/com/artifex/mupdfdemo/MuPDFCore.java
index cf35be8d..ed9ded92 100644
--- a/android/src/com/artifex/mupdfdemo/MuPDFCore.java
+++ b/android/src/com/artifex/mupdfdemo/MuPDFCore.java
@@ -18,9 +18,11 @@ public class MuPDFCore
private float pageWidth;
private float pageHeight;
private long globals;
+ private byte fileBuffer[];
/* The native functions */
private native long openFile(String filename);
+ private native long openBuffer();
private native int countPagesInternal();
private native void gotoPageInternal(int localActionPageNum);
private native float getPageWidth();
@@ -68,6 +70,16 @@ public class MuPDFCore
}
}
+ public MuPDFCore(byte buffer[]) throws Exception
+ {
+ fileBuffer = buffer;
+ globals = openBuffer();
+ if (globals == 0)
+ {
+ throw new Exception("Failed to open buffer");
+ }
+ }
+
public int countPages()
{
if (numPages < 0)