diff options
-rw-r--r-- | android/jni/mupdf.c | 230 | ||||
-rw-r--r-- | android/src/com/artifex/mupdf/MuPDFActivity.java | 131 | ||||
-rw-r--r-- | android/src/com/artifex/mupdf/MuPDFAlert.java | 21 | ||||
-rw-r--r-- | android/src/com/artifex/mupdf/MuPDFAlertInternal.java | 30 | ||||
-rw-r--r-- | android/src/com/artifex/mupdf/MuPDFCore.java | 21 |
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(); } |