Scanning directly from an ODK form - cims-bioko/cims-prints GitHub Wiki

The following outlines how to design an ODK form to scan fingerprints using cims-prints.

Background

Open Data Kit's Collect application offers functionality to integrate with external applications on the same Android mobile device, using Intents. Essentially, cims-prints provides just enough functionality to satisfy the expected input and output using this interface. Collect knows nothing about how cims-prints works, instead cims-prints know enough about the format Collect expects back to be able to populate an appropriately designed form.

Form Design

To enable Collect to capture fingerprints, you simply have to design an appropriate form. The following example provides two prompts, one for scanning each hand, left and right. It uses the ODK Collect functionality for launching external apps to popuate multiple fields:

<?xml version="1.0" encoding="UTF-8"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa">
  <h:head>
    <h:title>Fingerprint Scanning</h:title>
    <model>
      <instance>
        <data id="fingerprint_scanning">
          <meta>
            <instanceID/>
          </meta>
          <left_thumb/>
          <left_index/>
          <right_thumb/>
          <right_index/>
        </data>
      </instance>
      <submission method="form-data-post"
                  action="https://your-host/submission-endpoint"
                  base64RsaPublicKey="..." />
      <itext>
        <translation lang="English">
          <text id="scan_prompt:label">
            <value>Scan each finger by first tapping the finger to scan</value>
          </text>
          <text id="right_hand:label">
            <value>Right Hand</value>
            <value form="long">Scan right hand using fingerprint scanner</value>
            <value form="buttonText">Launch Scanner</value>
            <value form="noAppErrorString">Fingerprint scanning app not installed</value>
          </text>
          <text id="right_index:label">
            <value>Right Index</value>
          </text>
          <text id="right_thumb:label">
            <value>Right Thumb</value>
          </text>
          <text id="left_hand:label">
            <value>Left Hand</value>
            <value form="long">Scan left hand using fingerprint scanner</value>
            <value form="buttonText">Launch Scanner</value>
            <value form="noAppErrorString">Fingerprint scanning app not installed</value>
          </text>
          <text id="left_index:label">
            <value>Left Index</value>
          </text>
          <text id="left_thumb:label">
            <value>Left Thumb</value>
          </text>
        </translation>
      </itext>
      <bind nodeset="/data/meta/instanceID" type="string" readonly="true()" calculate="concat('uuid:', uuid())"/>
      <bind nodeset="/data/left_thumb" type="string" />
      <bind nodeset="/data/left_index" type="string" />
      <bind nodeset="/data/right_thumb" type="string" />
      <bind nodeset="/data/right_index" type="string" />
    </model>
  </h:head>
  <h:body>
    <group ref="/data" appearance="field-list" intent="com.openandid.internal.SCAN(prompt=jr:itext('scan_prompt:label'),left_finger_assignment='left_thumb',right_finger_assignment='left_index',easy_skip='true')">
      <label ref="jr:itext('left_hand:label')"/>
      <input ref="left_thumb">
         <label ref="jr:itext('left_thumb:label')"/>
      </input>
      <input ref="left_index">
         <label ref="jr:itext('left_index:label')"/>
      </input>
    </group> 
    <group ref="/data" appearance="field-list" intent="com.openandid.internal.SCAN(prompt=jr:itext('scan_prompt:label'),left_finger_assignment='right_thumb',right_finger_assignment='right_index',easy_skip='true')">
      <label ref="jr:itext('right_hand:label')"/>
      <input ref="right_thumb">
         <label ref="jr:itext('right_thumb:label')"/>
      </input>
      <input ref="right_index">
         <label ref="jr:itext('right_index:label')"/>
      </input>
    </group> 
  </h:body>
</h:html>

To link the fingerprint templates with an identity, you can include an identifier in the form. To highlight the important parts of the design let's dissect the design for a single scan:

<group ref="/data" appearance="field-list" intent="com.openandid.internal.SCAN(prompt=jr:itext('scan_prompt:label'),left_finger_assignment='left_thumb',right_finger_assignment='left_index',easy_skip='true')">
  <label ref="jr:itext('left_hand:label')"/>
  <input ref="left_thumb">
     <label ref="jr:itext('left_thumb:label')"/>
  </input>
  <input ref="left_index">
     <label ref="jr:itext('left_index:label')"/>
  </input>
</group> 

This group is actually responsible for capturing two fingerprints for the left hand, elements /data/left_thumb and /data/left_index as seen in the instance portion of the model declaration:

...
    <model>
      <instance>
...
          <left_thumb/>
          <left_index/>
...

These elements are plain-old string values like you would include in any ODK form. You can see that from the relevant bind declarations for those elements:

      <bind nodeset="/data/left_thumb" type="string" />
      <bind nodeset="/data/left_index" type="string" />

However, the element names are not arbitrary. We chose names that match what is returned from the activity named in the intent spec. That intent launches the cims-prints scanning activity which returns the encoded fingerprint template using the name of the finger scanned. The finger names supported are of the format [left|right]_[thumb|index|middle|ring|pinky]. The group declaration below configures the scan:

<group ref="/data" appearance="field-list" intent="com.openandid.internal.SCAN(prompt=jr:itext('scan_prompt:label'),left_finger_assignment='left_thumb',right_finger_assignment='left_index',easy_skip='true')">
...
</group

The intent portion tells Collect what Intent to fire to launch the external application. The value com.openandid.internal.SCAN will launch CIMS Print's scanning activity. The portion in parentheses communicates additional information to the scanning activity on what to scan:

prompt=jr:itext('scan_prompt:label'),left_finger_assignment='left_thumb',right_finger_assignment='left_index',easy_skip='true'

Specifically, prompt sets the text prompt shown to the user on the scanning screen. Here we use ODK's translation support to send in a prompt from the translations in the form. left/right_finger_assignment tell the scanning activity which finger to prompt for in the left and right panes of the scanning activity. Incidentally, this also affects which values are returned by the scanning activity to populate your form questions. Finally, easy_skip is an additional option supported by the scanning activity that allows exiting the activity without scanning the fingers.

Encryption

Last, but not least, because the fingerprint templates are encoded directly in the form, you want to be careful with how you handle them. For example, you probably don't want to embed personal information along with the templates due to privacy concerns. You also don't want just anyone to read the templates themselves. Fortunately, ODK includes support for encrypted forms. The example above includes a submission element that enables encrypted form support. This ensures that ODK encrypts saved forms immediately, so that only the person with the private key can read the submission (normally with ODK Briefcase).

      <submission method="form-data-post"
                  action="https://your-host/submission-endpoint"
                  base64RsaPublicKey="..." />

This element signals to Collect that it should handle this form as an encrypted form. It encrypts it with the public key specified in base64RsaPublicKey. Once encrypted only someone with the private key of the pair can decrypt and read the contents of the form submission. Details on how to generate the necessary keys and use encrypted form support is described in the ODK user documentation.

⚠️ **GitHub.com Fallback** ⚠️