Electronic Signatures and Digital Signing Combined
This example shows how to combine a signature render created with the ElectronicSignatureDialog with digitally signing the document with a certificate.
/* * Copyright © 2018-2026 PSPDFKit GmbH. All rights reserved. * * The PSPDFKit Sample applications are licensed with a modified BSD license. * Please see License for details. This notice may not be removed from this file. */
package com.pspdfkit.catalog.examples.kotlin
import android.annotation.SuppressLintimport android.content.Contextimport android.graphics.Bitmapimport android.net.Uriimport android.os.Bundleimport android.widget.Toastimport androidx.core.graphics.createBitmapimport androidx.fragment.app.Fragmentimport androidx.fragment.app.FragmentManagerimport androidx.fragment.app.FragmentOnAttachListenerimport com.pspdfkit.catalog.Rimport com.pspdfkit.catalog.SdkExampleimport com.pspdfkit.catalog.tasks.ExtractAssetTask.extractimport com.pspdfkit.configuration.activity.PdfActivityConfigurationimport com.pspdfkit.forms.FormTypeimport com.pspdfkit.forms.SignatureFormElementimport com.pspdfkit.signatures.DigitalSignatureMetadataimport com.pspdfkit.signatures.Signatureimport com.pspdfkit.signatures.SignatureAppearanceimport com.pspdfkit.signatures.SignatureGraphicimport com.pspdfkit.signatures.SignerOptionsimport com.pspdfkit.signatures.SigningManagerimport com.pspdfkit.signatures.getPrivateKeyEntryFromP12Streamimport com.pspdfkit.signatures.getX509Certificatesimport com.pspdfkit.signatures.listeners.OnSignaturePickedListenerimport com.pspdfkit.ui.PdfActivityimport com.pspdfkit.ui.PdfActivityIntentBuilderimport com.pspdfkit.ui.PdfFragmentimport com.pspdfkit.ui.signatures.ElectronicSignatureFragmentimport io.reactivex.rxjava3.android.schedulers.AndroidSchedulersimport kotlinx.coroutines.runBlockingimport java.io.Fileimport java.io.FileOutputStreamimport java.io.IOExceptionimport java.security.GeneralSecurityExceptionimport java.security.KeyStore
/** * Showcases how to combine a created signature annotation using the [ElectronicSignatureFragment] with digitally signing * the document using a certificate. * * See guide: https://nutrient.io/guides/android/signatures/using-electronic-signatures-and-digital-signatures-together/ */class CombineElectronicSignaturesWithDigitalSigningExample(context: Context) : SdkExample( context, R.string.CombineElectronicSignaturesWithDigitalSigningExampleTitle, R.string.CombineElectronicSignaturesWithDigitalSigningExampleDescription ) {
override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) { // The form field for signing is on page with index 16. configuration.page(16)
// Extract the document from the assets. extract(WELCOME_DOC, title, context) { documentFile -> val intent = PdfActivityIntentBuilder.fromUri(context, Uri.fromFile(documentFile)) .configuration(configuration.build()) .activityClass(CombineElectronicSignaturesWithDigitalSigningActivity::class) .build()
// Start the CombineElectronicSignaturesWithDigitalSigningActivity showing the demo document. context.startActivity(intent) } }}
/** * Shows how to implement a signature picked listener and document signing listener in order to create a signature and sign with the * created signature render. */class CombineElectronicSignaturesWithDigitalSigningActivity : PdfActivity(), OnSignaturePickedListener, FragmentOnAttachListener {
/** Name of the previously clicked signature form field (if any). Used to access it after a configuration change. */ private var signatureFormFieldName: String? = null
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
supportFragmentManager.addFragmentOnAttachListener(this)
// In case this activity is being recreated (e.g. during a configuration change), reattach // the activity as listener to the existing dialog. Calling restore() is safe, and won't do // anything in case the dialog isn't currently shown. ElectronicSignatureFragment.restore(supportFragmentManager, this)
// Also restore information about any previously clicked signature for element. We'll use // this when adding an ink annotation later. if (savedInstanceState != null) { signatureFormFieldName = savedInstanceState.getString(STATE_FORM_FIELD_NAME, null) } }
/** * Intercept clicks on form elements so we can customize showing the signature picker. * * @param fragmentManager FragmentManager the fragment is now attached to. This will * be the same FragmentManager that is returned by * [Fragment.getParentFragmentManager]. * @param fragment Fragment that just received a callback to [Fragment.onAttach] */ override fun onAttachFragment( fragmentManager: FragmentManager, fragment: Fragment ) { if (fragment is PdfFragment) { fragment.addOnFormElementClickedListener { formElement -> when (formElement.type) { FormType.SIGNATURE -> { val signatureFormElement = formElement as SignatureFormElement if (!signatureFormElement.isSigned) { onSignatureFormElementClicked(formElement) // By returning true, you intercept the event and prevent Nutrient from showing the signature picker itself. true } else { false } } // This click event is not interesting for us. Return false to let Nutrient handle this event. else -> false } } } }
override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState)
// Make sure to persist information of any clicked signature form element (so it outlives configuration changes). outState.putString(STATE_FORM_FIELD_NAME, signatureFormFieldName) }
/** * This callback handles showing the signature picker whenever an unsigned signature form element is clicked by the user. * We need to keep track of the form field name so we can retrieve it when digitally signing after the signature has been created * in the Electronic Signature Dialog. The dialog is opened by Nutrient automatically when the signature form element is clicked. * This behaviour can also be overridden here if required. */ private fun onSignatureFormElementClicked(formElement: SignatureFormElement) { // Keep reference of the stored signature form element so we can later on access it. signatureFormFieldName = formElement.formField.name // We need to control the showing of the signature dialog in order to attach this as a listener. ElectronicSignatureFragment.show(supportFragmentManager, this) }
/** * We are the signature creation listener, so this method is called by the signature picker, * whenever the user selects/creates a signature. */ override fun onSignaturePicked(signature: Signature) { // You can add you custom signature handling logic here. // ... digitallySignWithSignatureAnnotation(signature) }
/** * This method is called by the signature picker, if the user dismissed the picker without selecting a signature. */ override fun onDismiss() { signatureFormFieldName = null }
/** * This is an example that shows how to use the created signature annotation as the digital signature stamp appearance. * Note we're only ignoring the return of getFormFieldWithFullyQualifiedNameAsync for the sake of the example. */ @SuppressLint("CheckResult") private fun digitallySignWithSignatureAnnotation(signature: Signature) { val document = document ?: return val signatureFormFieldName = this.signatureFormFieldName ?: return
// Retrieve the previously clicked signature form element. We do this asynchronously to not block the UI thread. document.formProvider.getFormFieldWithFullyQualifiedNameAsync(signatureFormFieldName) .observeOn(AndroidSchedulers.mainThread()) .subscribe { formField -> val clickedSignatureFormElement = formField.formElement as SignatureFormElement
// We want to place the ink annotation on top of the signature field. We retrieve the widget annotation to access its position. val formFieldAnnotation = clickedSignatureFormElement.annotation
// The signature object provides convenient conversion to ink or stamp annotation. val signatureAnnotation = signature.toAnnotation(document, formFieldAnnotation.pageIndex, formFieldAnnotation.boundingBox)
// Add the annotation to the document. This step is required so we can render the signature // in order to pass the render to the `SignatureAppearance` when digitally signing in the // next step. We remove it once we have the render. // Here, we use the synchronous `AnnotationProvider` method as the next steps need to wait for this to complete. runBlocking { document.annotationProvider.addAnnotationToPage(signatureAnnotation) }
val w = kotlin.math.abs(signatureAnnotation.boundingBox.width().toInt()) val h = kotlin.math.abs(signatureAnnotation.boundingBox.height().toInt()) val signatureBitmap = createBitmap(w, h, Bitmap.Config.ARGB_8888) signatureAnnotation.renderToBitmap(signatureBitmap)
// Now we've made the bitmap, we can remove the annotation from the document, as it will appear on the digital // signature when we digitally sign the document. runBlocking { document.annotationProvider.removeAnnotationFromPage(signatureAnnotation) }
// Now sign the document using the certificate added in the example class in `addCertificateAndSigner`. signDocumentWithSignatureBitmap(clickedSignatureFormElement, signatureBitmap) } }
/** * Takes the created signature bitmap and digitally signs the document using the bitmap and the signing certificate initialised in * [CombineElectronicSignaturesWithDigitalSigningExample]. */ private fun signDocumentWithSignatureBitmap(formElement: SignatureFormElement, signatureBitmap: Bitmap) { val outputFile = File(filesDir, "signedDocument.pdf")
val signatureAppearance = SignatureAppearance( showWatermark = false, signatureGraphic = SignatureGraphic.fromBitmap(getImageUri(signatureBitmap)) )
val key = getPrivateKeyEntry(this) val signedDocumentUri = Uri.fromFile(outputFile) val signerOptions = SignerOptions.Builder(formElement.formField, signedDocumentUri).setSignatureMetadata( DigitalSignatureMetadata(signatureAppearance = signatureAppearance) ).setPrivateKey(key.privateKey).setCertificates(key.getX509Certificates()).build()
// handles the signing process SigningManager.signDocument( context = this, signerOptions = signerOptions, onFailure = { t -> Toast.makeText(this, t.localizedMessage, Toast.LENGTH_LONG).show() } ) { setDocumentFromUri(signedDocumentUri, null) // Signature page. pageIndex = 16 } }
/** * Stores the signature image to the FS in order to obtain a URI to pass to the [SignatureAppearance]. */ private fun getImageUri(bitmap: Bitmap): Uri { val imageFile = File(cacheDir, "signature.png") val fileStream = FileOutputStream(imageFile) bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileStream) fileStream.close() return Uri.fromFile(imageFile) }
@Throws(IOException::class, GeneralSecurityException::class) private fun getPrivateKeyEntry(context: Context): KeyStore.PrivateKeyEntry { // Inside a p12 we have both the certificate (or certificate chain to the root CA) and private key used for signing. val keystoreFile = context.assets.open("digital-signatures/ExampleSigner.p12") return getPrivateKeyEntryFromP12Stream(keystoreFile, "test") }}
private const val STATE_FORM_FIELD_NAME = "Example.FORM_FIELD_NAME"This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.