summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/jni/mupdf.c230
-rw-r--r--android/src/com/artifex/mupdf/MuPDFActivity.java131
-rw-r--r--android/src/com/artifex/mupdf/MuPDFAlert.java21
-rw-r--r--android/src/com/artifex/mupdf/MuPDFAlertInternal.java30
-rw-r--r--android/src/com/artifex/mupdf/MuPDFCore.java21
5 files changed, 433 insertions, 0 deletions
diff --git a/android/jni/mupdf.c b/android/jni/mupdf.c
index af3b86fd..a24be98a 100644
--- a/android/jni/mupdf.c
+++ b/android/jni/mupdf.c
@@ -1,5 +1,6 @@
#include <jni.h>
#include <time.h>
+#include <pthread.h>
#include <android/log.h>
#include <android/bitmap.h>
@@ -12,6 +13,7 @@
#define LOG_TAG "libmupdf"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
+#define LOGT(...) __android_log_print(ANDROID_LOG_INFO,"alert",__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
/* Set to 1 to enable debug log traces. */
@@ -87,6 +89,117 @@ static void dump_annotation_display_lists()
}
}
+// fin_lock and fin_lock2 are used during shutdown. The two waiting tasks
+// show_alert and waitForAlertInternal respectively take these locks while
+// waiting. During shutdown, the conditions are signaled and then the fin_locks
+// are taken momentarily to ensure the blocked threads leave the controlled
+// area of code before the mutexes and condition variables are destroyed.
+static pthread_mutex_t fin_lock;
+static pthread_mutex_t fin_lock2;
+// alert_lock is the main lock guarding the variables directly below.
+static pthread_mutex_t alert_lock;
+// Flag indicating if the alert system is active. When not active, both
+// show_alert and waitForAlertInternal return immediately.
+static int alerts_active;
+// Pointer to the alert struct passed in by show_alert, and valid while
+// show_alert is blocked.
+static fz_alert_event *current_alert;
+// Flag and condition varibles to signal a request is present and a reply
+// is present, respectively. The condition variables alone are not sufficient
+// because of the pthreads permit spurious signals.
+static int alert_request;
+static int alert_reply;
+static pthread_cond_t alert_request_cond;
+static pthread_cond_t alert_reply_cond;
+
+static void show_alert(fz_alert_event *alert)
+{
+ pthread_mutex_lock(&fin_lock2);
+ pthread_mutex_lock(&alert_lock);
+
+ LOGT("Enter show_alert: %s", alert->title);
+ alert->button_pressed = 0;
+
+ if (alerts_active)
+ {
+ current_alert = alert;
+ alert_request = 1;
+ pthread_cond_signal(&alert_request_cond);
+
+ while (alerts_active && !alert_reply)
+ pthread_cond_wait(&alert_reply_cond, &alert_lock);
+ alert_reply = 0;
+ current_alert = NULL;
+ }
+
+ LOGT("Exit show_alert");
+
+ pthread_mutex_unlock(&alert_lock);
+ pthread_mutex_unlock(&fin_lock2);
+}
+
+static void event_cb(fz_doc_event *event, void *data)
+{
+ switch (event->type)
+ {
+ case FZ_DOCUMENT_EVENT_ALERT:
+ show_alert(fz_access_alert_event(event));
+ break;
+ }
+}
+
+static void alerts_init()
+{
+ fz_interactive *idoc = fz_interact(doc);
+
+ if (!idoc)
+ return;
+
+ alerts_active = 0;
+ alert_request = 0;
+ alert_reply = 0;
+ pthread_mutex_init(&fin_lock, NULL);
+ pthread_mutex_init(&fin_lock2, NULL);
+ pthread_mutex_init(&alert_lock, NULL);
+ pthread_cond_init(&alert_request_cond, NULL);
+ pthread_cond_init(&alert_reply_cond, NULL);
+
+ fz_set_doc_event_callback(idoc, event_cb, NULL);
+ LOGT("alert_init");
+}
+
+static void alerts_fin()
+{
+ fz_interactive *idoc = fz_interact(doc);
+
+ if (!idoc)
+ return;
+
+ LOGT("Enter alerts_fin");
+ fz_set_doc_event_callback(idoc, NULL, NULL);
+
+ // Set alerts_active false and wake up show_alert and waitForAlertInternal,
+ pthread_mutex_lock(&alert_lock);
+ current_alert = NULL;
+ alerts_active = 0;
+ pthread_cond_signal(&alert_request_cond);
+ pthread_cond_signal(&alert_reply_cond);
+ pthread_mutex_unlock(&alert_lock);
+
+ // Wait for the fin_locks.
+ pthread_mutex_lock(&fin_lock);
+ pthread_mutex_unlock(&fin_lock);
+ pthread_mutex_lock(&fin_lock2);
+ pthread_mutex_unlock(&fin_lock2);
+
+ pthread_cond_destroy(&alert_reply_cond);
+ pthread_cond_destroy(&alert_request_cond);
+ pthread_mutex_destroy(&alert_lock);
+ pthread_mutex_destroy(&fin_lock2);
+ pthread_mutex_destroy(&fin_lock);
+ LOGT("Exit alerts_fin");
+}
+
JNIEXPORT int JNICALL
Java_com_artifex_mupdf_MuPDFCore_openFile(JNIEnv * env, jobject thiz, jstring jfilename)
{
@@ -117,6 +230,7 @@ Java_com_artifex_mupdf_MuPDFCore_openFile(JNIEnv * env, jobject thiz, jstring jf
fz_try(ctx)
{
doc = fz_open_document(ctx, (char *)filename);
+ alerts_init();
}
fz_catch(ctx)
{
@@ -818,12 +932,15 @@ Java_com_artifex_mupdf_MuPDFCore_destroying(JNIEnv * env, jobject thiz)
{
int i;
+ LOGI("Destroying");
fz_free(ctx, hit_bbox);
hit_bbox = NULL;
for (i = 0; i < NUM_CACHE; i++)
drop_page_cache(&pages[i]);
+ alerts_fin();
+
fz_close_document(doc);
doc = NULL;
}
@@ -1125,3 +1242,116 @@ Java_com_artifex_mupdf_MuPDFCore_getFocusedWidgetTypeInternal(JNIEnv * env, jobj
return NONE;
}
+
+JNIEXPORT jobject JNICALL
+Java_com_artifex_mupdf_MuPDFCore_waitForAlertInternal(JNIEnv * env, jobject thiz)
+{
+ jclass alertClass;
+ jmethodID ctor;
+ jstring title;
+ jstring message;
+ int alert_present;
+ fz_alert_event alert;
+
+ LOGT("Enter waitForAlert");
+ pthread_mutex_lock(&fin_lock);
+ pthread_mutex_lock(&alert_lock);
+
+ while (alerts_active && !alert_request)
+ pthread_cond_wait(&alert_request_cond, &alert_lock);
+ alert_request = 0;
+
+ alert_present = (alerts_active && current_alert);
+
+ if (alert_present)
+ alert = *current_alert;
+
+ pthread_mutex_unlock(&alert_lock);
+ pthread_mutex_unlock(&fin_lock);
+ LOGT("Exit waitForAlert %d", alert_present);
+
+ if (!alert_present)
+ return NULL;
+
+ alertClass = (*env)->FindClass(env, "com/artifex/mupdf/MuPDFAlertInternal");
+ if (alertClass == NULL)
+ return NULL;
+
+ ctor = (*env)->GetMethodID(env, alertClass, "<init>", "(Ljava/lang/String;IILjava/lang/String;I)V");
+ if (ctor == NULL)
+ return NULL;
+
+ title = (*env)->NewStringUTF(env, alert.title);
+ if (title == NULL)
+ return NULL;
+
+ message = (*env)->NewStringUTF(env, alert.message);
+ if (message == NULL)
+ return NULL;
+
+ return (*env)->NewObject(env, alertClass, ctor, title, alert.icon_type, alert.button_group_type, message, alert.button_pressed);
+}
+
+JNIEXPORT void JNICALL
+Java_com_artifex_mupdf_MuPDFCore_replyToAlertInternal(JNIEnv * env, jobject thiz, jobject alert)
+{
+ jclass alertClass;
+ jfieldID field;
+ int button_pressed;
+
+ alertClass = (*env)->FindClass(env, "com/artifex/mupdf/MuPDFAlertInternal");
+ if (alertClass == NULL)
+ return;
+
+ field = (*env)->GetFieldID(env, alertClass, "buttonPressed", "I");
+ if (field == NULL)
+ return;
+
+ button_pressed = (*env)->GetIntField(env, alert, field);
+
+ LOGT("Enter replyToAlert");
+ pthread_mutex_lock(&alert_lock);
+
+ if (alerts_active && current_alert)
+ {
+ // Fill in button_pressed and signal reply received.
+ current_alert->button_pressed = button_pressed;
+ alert_reply = 1;
+ pthread_cond_signal(&alert_reply_cond);
+ }
+
+ pthread_mutex_unlock(&alert_lock);
+ LOGT("Exit replyToAlert");
+}
+
+JNIEXPORT void JNICALL
+Java_com_artifex_mupdf_MuPDFCore_startAlertsInternal(JNIEnv * env, jobject thiz)
+{
+ LOGT("Enter startAlerts");
+ pthread_mutex_lock(&alert_lock);
+
+ alert_reply = 0;
+ alert_request = 0;
+ alerts_active = 1;
+ current_alert = NULL;
+
+ pthread_mutex_unlock(&alert_lock);
+ LOGT("Exit startAlerts");
+}
+
+JNIEXPORT void JNICALL
+Java_com_artifex_mupdf_MuPDFCore_stopAlertsInternal(JNIEnv * env, jobject thiz)
+{
+ LOGT("Enter stopAlerts");
+ pthread_mutex_lock(&alert_lock);
+
+ alert_reply = 0;
+ alert_request = 0;
+ alerts_active = 0;
+ current_alert = NULL;
+ pthread_cond_signal(&alert_reply_cond);
+ pthread_cond_signal(&alert_request_cond);
+
+ pthread_mutex_unlock(&alert_lock);
+ LOGT("Exit stopAleerts");
+}
diff --git a/android/src/com/artifex/mupdf/MuPDFActivity.java b/android/src/com/artifex/mupdf/MuPDFActivity.java
index b4c10ee9..cf1d2abd 100644
--- a/android/src/com/artifex/mupdf/MuPDFActivity.java
+++ b/android/src/com/artifex/mupdf/MuPDFActivity.java
@@ -1,5 +1,7 @@
package com.artifex.mupdf;
+import java.util.concurrent.Executor;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
@@ -10,6 +12,7 @@ import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.RectF;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
@@ -31,6 +34,11 @@ import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.ViewSwitcher;
+class ThreadPerTaskExecutor implements Executor {
+ public void execute(Runnable r) {
+ new Thread(r).start();
+ }
+}
class SearchTaskResult {
public final String txt;
public final int pageNumber;
@@ -99,6 +107,106 @@ public class MuPDFActivity extends Activity
private AlertDialog.Builder mAlertBuilder;
private LinkState mLinkState = LinkState.DEFAULT;
private final Handler mHandler = new Handler();
+ private AsyncTask<Void,Void,MuPDFAlert> mAlertTask;
+
+ public void createAlertWaiter() {
+ // All mupdf library calls are performed on asynchronous tasks to avoid stalling
+ // the UI. Some calls can lead to javascript-invoked requests to display an
+ // alert dialog and collect a reply from the user. The task has to be blocked
+ // until the user's reply is received. This method creates an asynchronous task,
+ // the purpose of which is to wait of these requests and produce the dialog
+ // in response, while leaving the core blocked. When the dialog receives the
+ // user's response, it is sent to the core via replyToAlert, unblocking it.
+ // Another alert-waiting task is then created to pick up the next alert.
+ if (mAlertTask != null) {
+ mAlertTask.cancel(true);
+ mAlertTask = null;
+ }
+ mAlertTask = new SafeAsyncTask<Void,Void,MuPDFAlert>() {
+
+ @Override
+ protected MuPDFAlert doInBackground(Void... arg0) {
+ return core.waitForAlert();
+ }
+
+ @Override
+ protected void onPostExecute(final MuPDFAlert result) {
+ // core.waitForAlert may return null when shutting down
+ if (result == null)
+ return;
+ final MuPDFAlert.ButtonPressed pressed[] = new MuPDFAlert.ButtonPressed[3];
+ for(int i = 0; i < 3; i++)
+ pressed[i] = MuPDFAlert.ButtonPressed.None;
+ DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ int index = 0;
+ switch (which) {
+ case AlertDialog.BUTTON1: index=0; break;
+ case AlertDialog.BUTTON2: index=1; break;
+ case AlertDialog.BUTTON3: index=2; break;
+ }
+ result.buttonPressed = pressed[index];
+ // Send the user's response to the core, so that it can
+ // continue processing.
+ core.replyToAlert(result);
+ // Create another alert-waiter to pick up the next alert.
+ createAlertWaiter();
+ }
+ };
+ AlertDialog alert = mAlertBuilder.create();
+ alert.setTitle(result.title);
+ alert.setMessage(result.message);
+ switch (result.iconType)
+ {
+ case Error:
+ break;
+ case Warning:
+ break;
+ case Question:
+ break;
+ case Status:
+ break;
+ }
+ switch (result.buttonGroupType)
+ {
+ case OkCancel:
+ alert.setButton(AlertDialog.BUTTON2, "Cancel", listener);
+ pressed[1] = MuPDFAlert.ButtonPressed.Cancel;
+ case Ok:
+ alert.setButton(AlertDialog.BUTTON1, "Ok", listener);
+ pressed[0] = MuPDFAlert.ButtonPressed.Ok;
+ break;
+ case YesNoCancel:
+ alert.setButton(AlertDialog.BUTTON3, "Cancel", listener);
+ pressed[2] = MuPDFAlert.ButtonPressed.Cancel;
+ case YesNo:
+ alert.setButton(AlertDialog.BUTTON1, "Yes", listener);
+ pressed[0] = MuPDFAlert.ButtonPressed.Yes;
+ alert.setButton(AlertDialog.BUTTON2, "No", listener);
+ pressed[1] = MuPDFAlert.ButtonPressed.No;
+ break;
+ }
+ alert.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ result.buttonPressed = MuPDFAlert.ButtonPressed.None;
+ core.replyToAlert(result);
+ createAlertWaiter();
+ }
+ });
+
+ alert.show();
+ }
+ };
+
+ mAlertTask.executeOnExecutor(new ThreadPerTaskExecutor());
+ }
+
+ public void destroyAlertWaiter() {
+ if (mAlertTask != null) {
+ mAlertTask.cancel(true);
+ mAlertTask = null;
+ }
+ }
private MuPDFCore openFile(String path)
{
@@ -205,6 +313,7 @@ public class MuPDFActivity extends Activity
public void createUI(Bundle savedInstanceState) {
if (core == null)
return;
+
// Now create the UI.
// First create the document view making use of the ReaderView's internal
// gesture recognition
@@ -521,6 +630,10 @@ public class MuPDFActivity extends Activity
{
if (core != null)
core.onDestroy();
+ if (mAlertTask != null) {
+ mAlertTask.cancel(true);
+ mAlertTask = null;
+ }
core = null;
super.onDestroy();
}
@@ -768,4 +881,22 @@ public class MuPDFActivity extends Activity
}
return super.onPrepareOptionsMenu(menu);
}
+
+ @Override
+ protected void onStart() {
+ if (core != null)
+ core.startAlerts();
+
+ createAlertWaiter();
+ super.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ destroyAlertWaiter();
+ if (core != null)
+ core.stopAlerts();
+
+ super.onStop();
+ }
}
diff --git a/android/src/com/artifex/mupdf/MuPDFAlert.java b/android/src/com/artifex/mupdf/MuPDFAlert.java
new file mode 100644
index 00000000..c024ad44
--- /dev/null
+++ b/android/src/com/artifex/mupdf/MuPDFAlert.java
@@ -0,0 +1,21 @@
+package com.artifex.mupdf;
+
+public class MuPDFAlert {
+ public enum IconType {Error,Warning,Question,Status};
+ public enum ButtonPressed {None,Ok,Cancel,No,Yes};
+ public enum ButtonGroupType {Ok,OkCancel,YesNo,YesNoCancel};
+
+ public final String message;
+ public final IconType iconType;
+ public final ButtonGroupType buttonGroupType;
+ public final String title;
+ public ButtonPressed buttonPressed;
+
+ MuPDFAlert(String aMessage, IconType aIconType, ButtonGroupType aButtonGroupType, String aTitle, ButtonPressed aButtonPressed) {
+ message = aMessage;
+ iconType = aIconType;
+ buttonGroupType = aButtonGroupType;
+ title = aTitle;
+ buttonPressed = aButtonPressed;
+ }
+}
diff --git a/android/src/com/artifex/mupdf/MuPDFAlertInternal.java b/android/src/com/artifex/mupdf/MuPDFAlertInternal.java
new file mode 100644
index 00000000..d1e93701
--- /dev/null
+++ b/android/src/com/artifex/mupdf/MuPDFAlertInternal.java
@@ -0,0 +1,30 @@
+package com.artifex.mupdf;
+
+// Version of MuPDFAlert without enums to simplify JNI
+public class MuPDFAlertInternal {
+ public final String message;
+ public final int iconType;
+ public final int buttonGroupType;
+ public final String title;
+ public int buttonPressed;
+
+ MuPDFAlertInternal(String aMessage, int aIconType, int aButtonGroupType, String aTitle, int aButtonPressed) {
+ message = aMessage;
+ iconType = aIconType;
+ buttonGroupType = aButtonGroupType;
+ title = aTitle;
+ buttonPressed = aButtonPressed;
+ }
+
+ MuPDFAlertInternal(MuPDFAlert alert) {
+ message = alert.message;
+ iconType = alert.iconType.ordinal();
+ buttonGroupType = alert.buttonGroupType.ordinal();
+ title = alert.message;
+ buttonPressed = alert.buttonPressed.ordinal();
+ }
+
+ MuPDFAlert toAlert() {
+ return new MuPDFAlert(message, MuPDFAlert.IconType.values()[iconType], MuPDFAlert.ButtonGroupType.values()[buttonGroupType], title, MuPDFAlert.ButtonPressed.values()[buttonPressed]);
+ }
+}
diff --git a/android/src/com/artifex/mupdf/MuPDFCore.java b/android/src/com/artifex/mupdf/MuPDFCore.java
index b7ab2834..98413504 100644
--- a/android/src/com/artifex/mupdf/MuPDFCore.java
+++ b/android/src/com/artifex/mupdf/MuPDFCore.java
@@ -43,6 +43,10 @@ public class MuPDFCore
private static native boolean hasOutlineInternal();
private static native boolean needsPasswordInternal();
private static native boolean authenticatePasswordInternal(String password);
+ private static native MuPDFAlertInternal waitForAlertInternal();
+ private static native void replyToAlertInternal(MuPDFAlertInternal alert);
+ private static native void startAlertsInternal();
+ private static native void stopAlertsInternal();
private static native void destroying();
public static native boolean javascriptSupported();
@@ -84,6 +88,23 @@ public class MuPDFCore
return new PointF(pageWidth, pageHeight);
}
+ public MuPDFAlert waitForAlert() {
+ MuPDFAlertInternal alert = waitForAlertInternal();
+ return alert != null ? alert.toAlert() : null;
+ }
+
+ public void replyToAlert(MuPDFAlert alert) {
+ replyToAlertInternal(new MuPDFAlertInternal(alert));
+ }
+
+ public void stopAlerts() {
+ stopAlertsInternal();
+ }
+
+ public void startAlerts() {
+ startAlertsInternal();
+ }
+
public synchronized void onDestroy() {
destroying();
}