From dca49ac644409e9bed09812e03977e649519f5d7 Mon Sep 17 00:00:00 2001 From: Paul Gardiner Date: Tue, 27 Aug 2013 14:49:15 +0100 Subject: Android: support signing --- platform/android/jni/mupdf.c | 58 ++++++++++- .../com/artifex/mupdfdemo/ChoosePDFActivity.java | 76 +++++++++----- .../src/com/artifex/mupdfdemo/FilePicker.java | 21 ++++ .../src/com/artifex/mupdfdemo/MuPDFActivity.java | 18 +++- .../src/com/artifex/mupdfdemo/MuPDFCore.java | 7 +- .../com/artifex/mupdfdemo/MuPDFPageAdapter.java | 6 +- .../src/com/artifex/mupdfdemo/MuPDFPageView.java | 109 +++++++++++++++++++-- 7 files changed, 254 insertions(+), 41 deletions(-) create mode 100644 platform/android/src/com/artifex/mupdfdemo/FilePicker.java (limited to 'platform') diff --git a/platform/android/jni/mupdf.c b/platform/android/jni/mupdf.c index a5bae5a7..3652595e 100644 --- a/platform/android/jni/mupdf.c +++ b/platform/android/jni/mupdf.c @@ -2177,7 +2177,15 @@ JNI_FN(MuPDFCore_getFocusedWidgetTypeInternal)(JNIEnv * env, jobject thiz) return NONE; } -JNIEXPORT jboolean JNICALL +/* This enum should be kept in line with SignatureState in MuPDFPageView.java */ +enum +{ + Signature_NoSupport, + Signature_Unsigned, + Signature_Signed +}; + +JNIEXPORT int JNICALL JNI_FN(MuPDFCore_getFocusedWidgetSignatureState)(JNIEnv * env, jobject thiz) { globals *glo = get_globals(env, thiz); @@ -2185,14 +2193,17 @@ JNI_FN(MuPDFCore_getFocusedWidgetSignatureState)(JNIEnv * env, jobject thiz) pdf_widget *focus; if (idoc == NULL) - return JNI_FALSE; + return Signature_NoSupport; focus = pdf_focused_widget(idoc); if (focus == NULL) - return JNI_FALSE; + return Signature_NoSupport; + + if (!pdf_signatures_supported()) + return Signature_NoSupport; - return pdf_dict_gets(((pdf_annot *)focus)->obj, "V") ? JNI_TRUE : JNI_FALSE; + return pdf_dict_gets(((pdf_annot *)focus)->obj, "V") ? Signature_Signed : Signature_Unsigned; } JNIEXPORT jstring JNICALL @@ -2220,6 +2231,45 @@ exit: return (*env)->NewStringUTF(env, ebuf); } +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_signFocusedSignatureInternal)(JNIEnv * env, jobject thiz, jstring jkeyfile, jstring jpassword) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(glo->doc); + pdf_widget *focus; + const char *keyfile; + const char *password; + jboolean res; + + if (idoc == NULL) + return JNI_FALSE; + + focus = pdf_focused_widget(idoc); + + if (focus == NULL) + return JNI_FALSE; + + keyfile = (*env)->GetStringUTFChars(env, jkeyfile, NULL); + password = (*env)->GetStringUTFChars(env, jpassword, NULL); + if (keyfile == NULL || password == NULL) + return JNI_FALSE; + + fz_var(res); + fz_try(ctx) + { + pdf_sign_signature(idoc, focus, keyfile, password); + dump_annotation_display_lists(glo); + res = JNI_TRUE; + } + fz_catch(ctx) + { + res = JNI_FALSE; + } + + return res; +} + JNIEXPORT jobject JNICALL JNI_FN(MuPDFCore_waitForAlertInternal)(JNIEnv * env, jobject thiz) { diff --git a/platform/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java b/platform/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java index c1c9142c..f9428dc8 100644 --- a/platform/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java +++ b/platform/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java @@ -21,6 +21,11 @@ import android.os.Handler; import android.view.View; import android.widget.ListView; +enum Purpose { + PickPDF, + PickKeyFile +} + public class ChoosePDFActivity extends ListActivity { static private File mDirectory; static private Map mPositions = new HashMap(); @@ -30,11 +35,15 @@ public class ChoosePDFActivity extends ListActivity { private Handler mHandler; private Runnable mUpdateFiles; private ChoosePDFAdapter adapter; + private Purpose mPurpose; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mPurpose = Intent.ACTION_MAIN.equals(getIntent().getAction()) ? Purpose.PickPDF : Purpose.PickKeyFile; + + String storageState = Environment.getExternalStorageState(); if (!Environment.MEDIA_MOUNTED.equals(storageState) @@ -88,29 +97,38 @@ public class ChoosePDFActivity extends ListActivity { if (file.isDirectory()) return false; String fname = file.getName().toLowerCase(); - if (fname.endsWith(".pdf")) - return true; - if (fname.endsWith(".xps")) - return true; - if (fname.endsWith(".cbz")) - return true; - if (fname.endsWith(".png")) - return true; - if (fname.endsWith(".jpe")) - return true; - if (fname.endsWith(".jpeg")) - return true; - if (fname.endsWith(".jpg")) - return true; - if (fname.endsWith(".jfif")) - return true; - if (fname.endsWith(".jfif-tbnl")) - return true; - if (fname.endsWith(".tif")) - return true; - if (fname.endsWith(".tiff")) - return true; - return false; + switch (mPurpose) { + case PickPDF: + if (fname.endsWith(".pdf")) + return true; + if (fname.endsWith(".xps")) + return true; + if (fname.endsWith(".cbz")) + return true; + if (fname.endsWith(".png")) + return true; + if (fname.endsWith(".jpe")) + return true; + if (fname.endsWith(".jpeg")) + return true; + if (fname.endsWith(".jpg")) + return true; + if (fname.endsWith(".jfif")) + return true; + if (fname.endsWith(".jfif-tbnl")) + return true; + if (fname.endsWith(".tif")) + return true; + if (fname.endsWith(".tiff")) + return true; + return false; + case PickKeyFile: + if (fname.endsWith(".pfx")) + return true; + return false; + default: + return false; + } } }); if (mFiles == null) @@ -184,7 +202,17 @@ public class ChoosePDFActivity extends ListActivity { Intent intent = new Intent(this,MuPDFActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.setData(uri); - startActivity(intent); + switch (mPurpose) { + case PickPDF: + // Start an activity to display the PDF file + startActivity(intent); + break; + case PickKeyFile: + // Return the uri to the caller + setResult(RESULT_OK, intent); + finish(); + break; + } } @Override diff --git a/platform/android/src/com/artifex/mupdfdemo/FilePicker.java b/platform/android/src/com/artifex/mupdfdemo/FilePicker.java new file mode 100644 index 00000000..65e2e901 --- /dev/null +++ b/platform/android/src/com/artifex/mupdfdemo/FilePicker.java @@ -0,0 +1,21 @@ +package com.artifex.mupdfdemo; + +import android.net.Uri; + +interface FilePickerSupport { + void performPickFor(FilePicker picker); +} + +public abstract class FilePicker { + private final FilePickerSupport support; + + FilePicker(FilePickerSupport _support) { + support = _support; + } + + void pick() { + support.performPickFor(this); + } + + abstract void onPick(Uri uri); +} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFActivity.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFActivity.java index 28aecd84..e38a3179 100644 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFActivity.java +++ b/platform/android/src/com/artifex/mupdfdemo/MuPDFActivity.java @@ -38,7 +38,7 @@ class ThreadPerTaskExecutor implements Executor { } } -public class MuPDFActivity extends Activity +public class MuPDFActivity extends Activity implements FilePickerSupport { /* The core rendering instance */ enum TopBarMode {Main, Search, Annot, Delete, More, Accept}; @@ -46,6 +46,7 @@ public class MuPDFActivity extends Activity private final int OUTLINE_REQUEST=0; private final int PRINT_REQUEST=1; + private final int FILEPICK_REQUEST=2; private MuPDFCore core; private String mFileName; private MuPDFReaderView mDocView; @@ -78,6 +79,7 @@ public class MuPDFActivity extends Activity private boolean mReflow = false; private AsyncTask mAlertTask; private AlertDialog mAlertDialog; + private FilePicker mFilePicker; public void createAlertWaiter() { mAlertsActive = true; @@ -418,7 +420,7 @@ public class MuPDFActivity extends Activity } } }; - mDocView.setAdapter(new MuPDFPageAdapter(this, core)); + mDocView.setAdapter(new MuPDFPageAdapter(this, this, core)); mSearchTask = new SearchTask(this, core) { @Override @@ -592,6 +594,9 @@ public class MuPDFActivity extends Activity if (resultCode == RESULT_CANCELED) showInfo(getString(R.string.print_failed)); break; + case FILEPICK_REQUEST: + if (mFilePicker != null && resultCode == RESULT_OK) + mFilePicker.onPick(data.getData()); } super.onActivityResult(requestCode, resultCode, data); } @@ -606,7 +611,7 @@ public class MuPDFActivity extends Activity private void reflowModeSet(boolean reflow) { mReflow = reflow; - mDocView.setAdapter(mReflow ? new MuPDFReflowAdapter(this, core) : new MuPDFPageAdapter(this, core)); + mDocView.setAdapter(mReflow ? new MuPDFReflowAdapter(this, core) : new MuPDFPageAdapter(this, this, core)); mReflowButton.setColorFilter(mReflow ? Color.argb(0xFF, 172, 114, 37) : Color.argb(0xFF, 255, 255, 255)); setButtonEnabled(mAnnotButton, !reflow); setButtonEnabled(mSearchButton, !reflow); @@ -1088,4 +1093,11 @@ public class MuPDFActivity extends Activity super.onBackPressed(); } } + + @Override + public void performPickFor(FilePicker picker) { + mFilePicker = picker; + Intent intent = new Intent(this, ChoosePDFActivity.class); + startActivityForResult(intent, FILEPICK_REQUEST); + } } diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFCore.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFCore.java index 7b1b8e41..c22f3fa9 100644 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFCore.java +++ b/platform/android/src/com/artifex/mupdfdemo/MuPDFCore.java @@ -49,8 +49,9 @@ public class MuPDFCore private native void setFocusedWidgetChoiceSelectedInternal(String [] selected); private native String [] getFocusedWidgetChoiceSelected(); private native String [] getFocusedWidgetChoiceOptions(); - private native boolean getFocusedWidgetSignatureState(); + private native int getFocusedWidgetSignatureState(); private native String checkFocusedSignatureInternal(); + private native boolean signFocusedSignatureInternal(String keyFile, String password); private native int setFocusedWidgetTextInternal(String text); private native String getFocusedWidgetTextInternal(); private native int getFocusedWidgetTypeInternal(); @@ -209,6 +210,10 @@ public class MuPDFCore return checkFocusedSignatureInternal(); } + public synchronized boolean signFocusedSignature(String keyFile, String password) { + return signFocusedSignatureInternal(keyFile, password); + } + public synchronized LinkInfo [] getPageLinks(int page) { return getPageLinksInternal(page); } diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java index 806d0830..83d6a3fc 100644 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java +++ b/platform/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java @@ -10,11 +10,13 @@ import android.widget.BaseAdapter; public class MuPDFPageAdapter extends BaseAdapter { private final Context mContext; + private final FilePickerSupport mFilePickerSupport; private final MuPDFCore mCore; private final SparseArray mPageSizes = new SparseArray(); - public MuPDFPageAdapter(Context c, MuPDFCore core) { + public MuPDFPageAdapter(Context c, FilePickerSupport filePickerSupport, MuPDFCore core) { mContext = c; + mFilePickerSupport = filePickerSupport; mCore = core; } @@ -33,7 +35,7 @@ public class MuPDFPageAdapter extends BaseAdapter { public View getView(final int position, View convertView, ViewGroup parent) { final MuPDFPageView pageView; if (convertView == null) { - pageView = new MuPDFPageView(mContext, mCore, new Point(parent.getWidth(), parent.getHeight())); + pageView = new MuPDFPageView(mContext, mFilePickerSupport, mCore, new Point(parent.getWidth(), parent.getHeight())); } else { pageView = (MuPDFPageView) convertView; } diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFPageView.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFPageView.java index 2cb3974b..774aafb7 100644 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFPageView.java +++ b/platform/android/src/com/artifex/mupdfdemo/MuPDFPageView.java @@ -10,10 +10,20 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; import android.graphics.RectF; +import android.net.Uri; +import android.text.method.PasswordTransformationMethod; import android.view.LayoutInflater; import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; import android.widget.EditText; +/* This enum should be kept in line with the cooresponding C enum in mupdf.c */ +enum SignatureState { + NoSupport, + Unsigned, + Signed +} + abstract class PassClickResultVisitor { public abstract void visitText(PassClickResultText result); public abstract void visitChoice(PassClickResultChoice result); @@ -60,11 +70,11 @@ class PassClickResultChoice extends PassClickResult { } class PassClickResultSignature extends PassClickResult { - public final boolean isSigned; + public final SignatureState state; - public PassClickResultSignature(boolean _changed, boolean _isSigned) { + public PassClickResultSignature(boolean _changed, int _state) { super(_changed); - isSigned = _isSigned; + state = SignatureState.values()[_state]; } public void acceptVisitor(PassClickResultVisitor visitor) { @@ -73,6 +83,7 @@ class PassClickResultSignature extends PassClickResult { } public class MuPDFPageView extends PageView implements MuPDFView { + final private FilePickerSupport mFilePickerSupport; private final MuPDFCore mCore; private AsyncTask mPassClick; private RectF mWidgetAreas[]; @@ -84,7 +95,10 @@ public class MuPDFPageView extends PageView implements MuPDFView { private AlertDialog.Builder mChoiceEntryBuilder; private AlertDialog.Builder mSigningDialogBuilder; private AlertDialog.Builder mSignatureReportBuilder; + private AlertDialog.Builder mPasswordEntryBuilder; + private EditText mPasswordText; private AlertDialog mTextEntry; + private AlertDialog mPasswordEntry; private EditText mEditText; private AsyncTask mSetWidgetText; private AsyncTask mSetWidgetChoice; @@ -92,10 +106,12 @@ public class MuPDFPageView extends PageView implements MuPDFView { private AsyncTask mAddInk; private AsyncTask mDeleteAnnotation; private AsyncTask mCheckSignature; + private AsyncTask mSign; private Runnable changeReporter; - public MuPDFPageView(Context c, MuPDFCore core, Point parentSize) { + public MuPDFPageView(Context c, FilePickerSupport filePickerSupport, MuPDFCore core, Point parentSize) { super(c, parentSize); + mFilePickerSupport = filePickerSupport; mCore = core; mTextEntryBuilder = new AlertDialog.Builder(c); mTextEntryBuilder.setTitle(getContext().getString(R.string.fill_out_text_field)); @@ -137,8 +153,21 @@ public class MuPDFPageView extends PageView implements MuPDFView { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } + }); + mSigningDialogBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + FilePicker picker = new FilePicker(mFilePickerSupport) { + @Override + void onPick(Uri uri) { + signWithKeyFile(uri); + } + }; + picker.pick(); + } }); + mSignatureReportBuilder = new AlertDialog.Builder(c); mSignatureReportBuilder.setTitle("Signature checked"); mSignatureReportBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { @@ -147,6 +176,59 @@ public class MuPDFPageView extends PageView implements MuPDFView { dialog.dismiss(); } }); + + mPasswordText = new EditText(c); + mPasswordText.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + mPasswordText.setTransformationMethod(new PasswordTransformationMethod()); + + mPasswordEntryBuilder = new AlertDialog.Builder(c); + mPasswordEntryBuilder.setTitle(R.string.enter_password); + mPasswordEntryBuilder.setView(mPasswordText); + mPasswordEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + mPasswordEntry = mPasswordEntryBuilder.create(); + } + + private void signWithKeyFile(final Uri uri) { + mPasswordEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + mPasswordEntry.setButton(AlertDialog.BUTTON_POSITIVE, "Sign", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + signWithKeyFileAndPassword(uri, mPasswordText.getText().toString()); + } + }); + + mPasswordEntry.show(); + } + + private void signWithKeyFileAndPassword(final Uri uri, final String password) { + mSign = new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + return mCore.signFocusedSignature(Uri.decode(uri.getEncodedPath()), password); + } + @Override + protected void onPostExecute(Boolean result) { + if (result) + { + changeReporter.run(); + } + else + { + mPasswordText.setText(""); + signWithKeyFile(uri); + } + } + + }; + + mSign.execute(); } public LinkInfo hitLink(float x, float y) { @@ -217,6 +299,12 @@ public class MuPDFPageView extends PageView implements MuPDFView { dialog.show(); } + private void warnNoSignatureSupport() { + AlertDialog dialog = mSignatureReportBuilder.create(); + dialog.setTitle("App built with no signature support"); + dialog.show(); + } + public void setChangeReporter(Runnable reporter) { changeReporter = reporter; } @@ -287,10 +375,17 @@ public class MuPDFPageView extends PageView implements MuPDFView { @Override public void visitSignature(PassClickResultSignature result) { - if (result.isSigned) - invokeSignatureCheckingDialog(); - else + switch (result.state) { + case NoSupport: + warnNoSignatureSupport(); + break; + case Unsigned: invokeSigningDialog(); + break; + case Signed: + invokeSignatureCheckingDialog(); + break; + } } }); } -- cgit v1.2.3