]> hydra-www.ietfng.org Git - android-vcpass/commitdiff
Initial checkin
authorNathaniel Wesley Filardo <nwf@hydra.priv.oc.ietfng.org>
Thu, 8 Jul 2010 05:21:43 +0000 (01:21 -0400)
committerNathaniel Wesley Filardo <nwf@hydra.priv.oc.ietfng.org>
Thu, 8 Jul 2010 05:21:43 +0000 (01:21 -0400)
26 files changed:
AndroidManifest.xml [new file with mode: 0644]
build.properties [new file with mode: 0644]
build.xml [new file with mode: 0644]
default.properties [new file with mode: 0644]
host/org/ietfng/ns/android/vcpass/VCSlideGen.java [new file with mode: 0644]
host/org/ietfng/ns/android/vcpass/VCTestVocGen.java [new file with mode: 0644]
res/drawable-hdpi/icon.png [new file with mode: 0644]
res/drawable-ldpi/icon.png [new file with mode: 0644]
res/drawable-mdpi/icon.png [new file with mode: 0644]
res/layout/main.xml [new file with mode: 0644]
res/layout/vcpact.xml [new file with mode: 0644]
res/layout/vcpimp.xml [new file with mode: 0644]
res/values/dimens.xml [new file with mode: 0644]
res/values/strings.xml [new file with mode: 0644]
src/org/ietfng/ns/android/vcpass/Utils.java [new file with mode: 0644]
src/org/ietfng/ns/android/vcpass/VCGenerator.java [new file with mode: 0644]
src/org/ietfng/ns/android/vcpass/VCParameters.java [new file with mode: 0644]
src/org/ietfng/ns/android/vcpass/VCPass.java [new file with mode: 0644]
src/org/ietfng/ns/android/vcpass/VCPassActivity.java [new file with mode: 0644]
src/org/ietfng/ns/android/vcpass/VCPassImport.java [new file with mode: 0644]
tests/AndroidManifest.xml [new file with mode: 0644]
tests/build.properties [new file with mode: 0644]
tests/build.xml [new file with mode: 0644]
tests/default.properties [new file with mode: 0644]
tests/local.properties [new file with mode: 0644]
tests/src/org/ietfng/ns/android/vcpass/VCPassTest.java [new file with mode: 0644]

diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..c982d87
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="org.ietfng.ns.android.vcpass"
+      android:versionCode="1"
+      android:versionName="1.0">
+    <application android:label="@string/app_name">
+        <activity android:name="VCPass"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name="VCPassActivity">
+            <intent-filter>
+                <action android:name="org.ietfng.ns.android.vcpass.CHAL_PRESENT" />
+                <action android:name="org.ietfng.ns.android.vcpass.CHAL_CREATE" />
+                <category android:name="android.intent.category.DEFAULT" />
+               </intent-filter>
+        </activity>
+        <activity android:name="VCPassImport">
+            <intent-filter>
+                <action android:name="org.ietfng.ns.android.vcpass.SEED_IMPORT" />
+                <action android:name="org.ietfng.ns.android.vcpass.IMPORT_AND_CREATE" />
+                <category android:name="android.intent.category.DEFAULT" />
+               </intent-filter>
+        </activity>
+    </application>
+</manifest> 
diff --git a/build.properties b/build.properties
new file mode 100644 (file)
index 0000000..edc7f23
--- /dev/null
@@ -0,0 +1,17 @@
+# This file is used to override default values used by the Ant build system.
+# 
+# This file must be checked in Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+#  'source.dir' for the location of your java source folder and
+#  'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+#  'key.store' for the location of your keystore and
+#  'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+
diff --git a/build.xml b/build.xml
new file mode 100644 (file)
index 0000000..af37263
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="VCPass" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contain the path to the SDK. It should *NOT* be checked in in Version
+         Control Systems. -->
+    <property file="local.properties"/>
+
+    <!-- The build.properties file can be created by you and is never touched
+         by the 'android' tool. This is the place to change some of the default property values
+         used by the Ant rules.
+         Here are some properties you may want to change/update:
+
+         application-package
+             the name of your application package as defined in the manifest. Used by the
+             'uninstall' rule.
+         source-folder
+             the name of the source folder. Default is 'src'.
+         out-folder
+             the name of the output folder. Default is 'bin'.
+
+         Properties related to the SDK location or the project target should be updated
+          using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your application and
+         should be checked in in Version Control Systems.
+
+         -->
+    <property file="build.properties"/>
+
+    <!-- The default.properties file is created and updated by the 'android' tool, as well
+         as ADT. 
+         This file is an integral part of the build system for your application and
+         should be checked in in Version Control Systems. -->
+    <property file="default.properties"/>
+
+    <!-- Custom Android task to deal with the project target, and import the proper rules.
+         This requires ant 1.6.0 or above. -->
+    <path id="android.antlibs">
+        <pathelement path="${sdk-location}/tools/lib/anttasks.jar" />
+        <pathelement path="${sdk-location}/tools/lib/sdklib.jar" />
+        <pathelement path="${sdk-location}/tools/lib/androidprefs.jar" />
+        <pathelement path="${sdk-location}/tools/lib/apkbuilder.jar" />
+        <pathelement path="${sdk-location}/tools/lib/jarutils.jar" />
+    </path>
+
+    <taskdef name="setup"
+        classname="com.android.ant.SetupTask"
+        classpathref="android.antlibs"/>
+
+    <!-- Execute the Android Setup task that will setup some properties specific to the target,
+         and import the rules files.
+         To customize the rules, copy/paste them below the task, and disable import by setting
+         the import attribute to false:
+            <setup import="false" />
+         
+         This will ensure that the properties are setup correctly but that your customized
+         targets are used.
+    -->
+    <setup />
+
+</project>
diff --git a/default.properties b/default.properties
new file mode 100644 (file)
index 0000000..9d79b12
--- /dev/null
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+# 
+# This file must be checked in Version Control Systems.
+# 
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-4
diff --git a/host/org/ietfng/ns/android/vcpass/VCSlideGen.java b/host/org/ietfng/ns/android/vcpass/VCSlideGen.java
new file mode 100644 (file)
index 0000000..5329e7d
--- /dev/null
@@ -0,0 +1,201 @@
+/** Host-side slide generator.
+ *
+ * Required classpath entries:
+ *  /usr/share/java/bcprov.jar
+ *  /usr/share/java/commons-codec.jar
+ *  /usr/share/java/commons-cli.jar
+ *  $HOME/src/zxing/core/core.jar 
+ *
+ * Build with:
+ *      javac -d bin/classes -cp bin/classes:... \
+ *            host/org/ietfng/ns/android/vcpass/VCSlideGen.java
+ *
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.io.FileWriter;
+import java.security.SecureRandom;
+import java.security.Provider;
+import java.security.Security;
+import javax.crypto.SecretKey;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.ByteMatrix;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+import org.apache.commons.codec.binary.Base64;
+
+public final class VCSlideGen {
+    static final private int SEED_SIZE = 16;
+
+    private static final int QR_SIZEX = 400;
+    private static final int QR_SIZEY = 400;
+
+    static final private Options cliopts = new Options();
+    static final private String OPT_SS_HELP   = "h";
+    static final private String OPT_SS_QRFILE = "b";
+    static final private String OPT_SS_SECRET = "S";
+    static final private String OPT_SS_SLIDEF = "s";
+    static final private String OPT_SS_VOCABS = "v";
+
+    private static final int crpix = VCParameters.DISP_X
+                                   / VCParameters.GRID_X;
+    private static final int ccpix = VCParameters.DISP_Y
+                                   / VCParameters.GRID_Y;
+
+    private static final void
+    printPBMHeader(FileWriter f, int v, int x, int y) 
+    throws java.io.IOException
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("P");
+        sb.append(v);
+        sb.append(" ");
+        sb.append(x);
+        sb.append(" ");
+        sb.append(y);
+        sb.append("\n");
+        f.write(sb.toString(), 0, sb.length());
+    }
+
+    static {
+        cliopts.addOption(OPT_SS_HELP, "help", false, "Show this help");
+        cliopts.addOption(OPT_SS_QRFILE, "qrfile", true,
+                            "Barcode PNM file basename");
+        cliopts.addOption(OPT_SS_SECRET, "secretseed", true,
+                            "Secret seed (for development)");
+        cliopts.addOption(OPT_SS_SLIDEF, "slidefile", true,
+                            "Slide PNM file basename");
+        cliopts.addOption(OPT_SS_VOCABS, "vocabfile", true,
+                            "Vocabulary PNM file basename");
+    }
+
+    public static void main(String[] args) throws Exception {
+        CommandLineParser parser = new PosixParser();
+        CommandLine cmd = parser.parse(cliopts, args);
+
+        if(cmd.hasOption(OPT_SS_HELP)) {
+            HelpFormatter formatter = new HelpFormatter();
+            formatter.printHelp( "VCSlideGen" , cliopts );
+            return;
+        } 
+
+        Security.addProvider(VCParameters.CSPROV);
+
+/* XXX */
+        char[] useed = "foo".toCharArray();
+        char[] vseed = "bar".toCharArray();
+
+/* XXX
+        byte[] useed = new byte[SEED_SIZE];
+        byte[] vseed = new byte[SEED_SIZE];
+
+        {        
+            SecureRandom seedsr = SecureRandom.getInstance(
+                                        VCParameters.CSPRNG
+                                  );
+            if(cmd.hasOption(OPT_SS_SECRET)) {
+                System.err.println("WARN: Using given seed.");
+                seedsr.setSeed(cmd.getOptionValue(OPT_SS_SECRET)
+                                  .getBytes("UTF-8"));
+            } else {
+                // seedsr will initialize from the system's RNG when
+                // we first pull some data out of it.
+            }
+            seedsr.nextBytes(useed);
+            seedsr.nextBytes(vseed);
+        }
+*/
+
+        if(cmd.hasOption(OPT_SS_QRFILE)) {
+            String encodedseeds = null;
+            {
+                StringBuilder esb = new StringBuilder();
+                Base64 b64 = new Base64(80,new byte[0]);
+                // XXX esb.append(b64.encodeToString(useed));
+                esb.append(" ");
+                // XXX esb.append(b64.encodeToString(vseed));
+                encodedseeds = esb.toString();
+               System.out.printf("QRSTR: %s\n", encodedseeds);
+            }
+            FileWriter qrout    = new FileWriter(
+                                     cmd.getOptionValue(OPT_SS_QRFILE)
+                                    +".pbm"
+                                  );
+            printPBMHeader(qrout, 1, QR_SIZEX, QR_SIZEY);
+            ByteMatrix qrbm = new MultiFormatWriter().encode(
+                encodedseeds,
+                BarcodeFormat.QR_CODE,
+                QR_SIZEX, QR_SIZEY);
+            byte[][] qr = qrbm.getArray();
+            for(int x = 0; x < qr.length; x++) {
+                for(int y = 0; y < qr[x].length; y++) {
+                    qrout.write('1' - (qr[x][y] & 0x1));
+                }
+                qrout.write('\n');
+            }
+            qrout.flush();
+            qrout.close();
+        }
+
+        if(cmd.hasOption(OPT_SS_SLIDEF)) {
+            FileWriter sout    = new FileWriter(
+                                    cmd.getOptionValue(OPT_SS_SLIDEF)
+                                   +".pbm"
+                                 );
+            printPBMHeader(sout, 1, VCParameters.DISP_X,
+                                    VCParameters.DISP_Y);
+
+            Integer[][] slide = VCGenerator.generateSlide(useed, null);
+
+            int[][] pixels = VCGenerator.vcArrayToPixels(slide);
+            for(int r = 0; r < pixels.length; r++) {
+                for(int c = 0; c < pixels[r].length; c++) {
+                    sout.write(pixels[r][c] == VCParameters.white ?
+                                '0' : '1');
+                }
+                sout.write('\n');
+            }
+            sout.flush();
+            sout.close();
+        }
+
+        if(cmd.hasOption(OPT_SS_VOCABS)) {
+            int[] plain = new int[VCParameters.GRID_X
+                                 *VCParameters.GRID_Y];
+            for(int i = 0; i < VCParameters.VCVOC_SIZE; i++) {
+                FileWriter vout    = new FileWriter(
+                                     cmd.getOptionValue(OPT_SS_VOCABS)
+                                     +"-"
+                                     +Integer.toString(i)
+                                     +".pbm"
+                                   );
+                printPBMHeader(vout, 1, VCParameters.DISP_X,
+                                        VCParameters.DISP_Y);
+
+                for(int j = 0; j < plain.length; j++) {
+                    plain[j] = i;
+                }
+
+                Integer[][] vocab = VCGenerator.generateChallenge(
+                                        vseed, useed, plain, null
+                                    );
+                int[][] pixels = VCGenerator.vcArrayToPixels(vocab);
+                for(int r = 0; r < pixels.length; r++) {
+                    for(int c = 0; c < pixels[r].length; c++) {
+                        vout.write(pixels[r][c] == VCParameters.white ?
+                                    '1' : '0');
+                    }
+                    vout.write('\n');
+                }
+                vout.flush();
+                vout.close();
+            }
+        }
+    }
+}
diff --git a/host/org/ietfng/ns/android/vcpass/VCTestVocGen.java b/host/org/ietfng/ns/android/vcpass/VCTestVocGen.java
new file mode 100644 (file)
index 0000000..bc871e6
--- /dev/null
@@ -0,0 +1,207 @@
+/** Host-side slide generator.
+ *
+ * Required classpath entries:
+ *  /usr/share/java/bcprov.jar
+ *  /usr/share/java/commons-codec.jar
+ *  /usr/share/java/commons-cli.jar
+ *  $HOME/src/zxing/core/core.jar 
+ *
+ * Build with:
+ *      javac -d bin/classes -cp bin/classes:... \
+ *            host/org/ietfng/ns/android/vcpass/VCTestVocGen.java
+ *
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.io.FileWriter;
+import java.security.SecureRandom;
+import java.security.Provider;
+import java.security.Security;
+import javax.crypto.SecretKey;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.ByteMatrix;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+import org.apache.commons.codec.binary.Base64;
+
+public final class VCTestVocGen {
+    static final private int SEED_SIZE = 16;
+
+    private static final int QR_SIZEX = 400;
+    private static final int QR_SIZEY = 400;
+
+    static final private Options cliopts = new Options();
+    static final private String OPT_SS_HELP   = "h";
+    static final private String OPT_SS_SECRET = "S";
+    static final private String OPT_SS_VOCSEC = "V";
+    static final private String OPT_SS_GENEXC = "c";
+    static final private String OPT_SS_GENEXV = "v";
+
+    private static final int crpix = VCParameters.DISP_X
+                                   / VCParameters.GRID_X;
+    private static final int ccpix = VCParameters.DISP_Y
+                                   / VCParameters.GRID_Y;
+
+    private static final void
+    printPBMHeader(FileWriter f, int v, int x, int y) 
+    throws java.io.IOException
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append("P");
+        sb.append(v);
+        sb.append(" ");
+        sb.append(x);
+        sb.append(" ");
+        sb.append(y);
+        sb.append("\n");
+        f.write(sb.toString(), 0, sb.length());
+    }
+
+    static {
+        cliopts.addOption(OPT_SS_HELP, "help", false, "Show this help");
+        cliopts.addOption(OPT_SS_SECRET, "secretseed", true,
+                            "Secret seed (for development)");
+        cliopts.addOption(OPT_SS_SECRET, "slideseed", true,
+                            "Slide seed (for development)");
+        cliopts.addOption(OPT_SS_VOCSEC, "vocseed", true,
+                            "Vocabulary seed (for development)");
+        cliopts.addOption(OPT_SS_GENEXC, "chalfile", true,
+                            "Example challenge file name");
+        cliopts.addOption(OPT_SS_GENEXV, "vocfile", true,
+                            "Example vocabulary file name");
+    }
+
+    public static void main(String[] args) throws Exception {
+        CommandLineParser parser = new PosixParser();
+        CommandLine cmd = parser.parse(cliopts, args);
+
+        if(cmd.hasOption(OPT_SS_HELP)) {
+            HelpFormatter formatter = new HelpFormatter();
+            formatter.printHelp( "VCSlideGen" , cliopts );
+            return;
+        } 
+
+        Security.addProvider(VCParameters.CSPROV);
+
+        char[] useed;
+        if(cmd.hasOption(OPT_SS_SECRET)) {
+            useed = cmd.getOptionValue(OPT_SS_SECRET).toCharArray();
+        } else {
+            // XXX
+            useed = "foo".toCharArray();
+        }
+
+        char[] vseed;
+        if(cmd.hasOption(OPT_SS_VOCSEC)) {
+            vseed = cmd.getOptionValue(OPT_SS_VOCSEC).toCharArray();
+        } else {
+            // XXX
+            vseed = "bar".toCharArray();
+        }
+
+        if(cmd.hasOption(OPT_SS_GENEXV)) {
+            Integer[][] vocab = new Integer[VCParameters.VCVOC_SIZE][];
+            Integer[][] vslide = new Integer[VCParameters.VCVOC_SIZE][];
+
+            {
+                Integer[][] slide = VCGenerator.generateSlide( useed, null );
+                for(int i = 0; i < vslide.length; i++) {
+                    vslide[i] = slide[0];
+                }
+            }
+
+            for(int i = 0; i < vocab.length; i++) {
+                int[] plain = new int[VCParameters.GRID_X
+                                     *VCParameters.GRID_Y];
+                plain[0] = i;
+
+                Integer[][] vocabi = VCGenerator.generateChallenge(
+                                        vseed, useed, plain, null
+                                    );
+           
+                vocab[i] = vocabi[0];
+            }
+
+            FileWriter vout    = new FileWriter(
+                                 cmd.getOptionValue(OPT_SS_GENEXV)
+                               );
+            printPBMHeader(vout, 1, VCParameters.DISP_X,
+                                    VCParameters.DISP_Y);
+
+            /* XXX */ 
+            int[][] spixels = VCGenerator.vcArrayToPixels(vslide);
+            int[][] vpixels = VCGenerator.vcArrayToPixels(vocab);
+  
+            for(int i = crpix-1; i < VCParameters.DISP_X-1; i += crpix){
+            for(int r = 0; r < vpixels.length; r++) {
+                vpixels[i][r] = VCParameters.black;
+                vpixels[i+1][r] = VCParameters.black;
+                vpixels[r][i] = VCParameters.black;
+                vpixels[r][i+1] = VCParameters.black;
+            }}
+            for(int r = 0; r < vpixels.length; r++) {
+                for(int c = 0; c < vpixels[r].length; c++) {
+                    if(spixels[r][c] == VCParameters.black) {
+                         vout.write('1');
+                    } else {
+                         vout.write(vpixels[r][c] == VCParameters.white ?
+                                '0' : '1');
+                    }
+                }
+                vout.write('\n');
+            }
+            vout.flush();
+            vout.close();
+        }
+
+        if(cmd.hasOption(OPT_SS_GENEXC)) {
+            int[] plain = new int[VCParameters.GRID_X
+                                 *VCParameters.GRID_Y];
+            for(int i = 0; i < plain.length; i++ ){
+                plain[i] = (i*7+8) % VCParameters.VCVOC_SIZE;
+            }
+            FileWriter vout    = new FileWriter(
+                                 cmd.getOptionValue(OPT_SS_GENEXC)
+                               );
+            printPBMHeader(vout, 1, VCParameters.DISP_X,
+                                    VCParameters.DISP_Y);
+  
+            Integer[][] vocab = VCGenerator.generateChallenge(
+                                    vseed, useed, plain, null
+                                );
+            int[][] vpixels = VCGenerator.vcArrayToPixels(vocab);
+  
+            Integer[][] slide = VCGenerator.generateSlide( useed, null );
+            int[][] spixels = VCGenerator.vcArrayToPixels(slide);
+  
+            for(int i = crpix-1; i < VCParameters.DISP_X-1; i += crpix){
+            for(int r = 0; r < vpixels.length; r++) {
+                vpixels[i][r] = VCParameters.black;
+                vpixels[i+1][r] = VCParameters.black;
+                vpixels[r][i] = VCParameters.black;
+                vpixels[r][i+1] = VCParameters.black;
+            }}
+            for(int r = 0; r < vpixels.length; r++) {
+                for(int c = 0; c < vpixels[r].length; c++) {
+                    if(spixels[r][c] == VCParameters.black) {
+                         vout.write('1');
+                    } else {
+                         vout.write(vpixels[r][c] == VCParameters.white ?
+                                '0' : '1');
+                    }
+                }
+                vout.write('\n');
+            }
+            vout.flush();
+            vout.close();
+        }
+    }
+}
diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png
new file mode 100644 (file)
index 0000000..8074c4c
Binary files /dev/null and b/res/drawable-hdpi/icon.png differ
diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png
new file mode 100644 (file)
index 0000000..1095584
Binary files /dev/null and b/res/drawable-ldpi/icon.png differ
diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png
new file mode 100644 (file)
index 0000000..a07c69f
Binary files /dev/null and b/res/drawable-mdpi/icon.png differ
diff --git a/res/layout/main.xml b/res/layout/main.xml
new file mode 100644 (file)
index 0000000..28fb834
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+<TextView  
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content" 
+    android:text="Hello World, VCPass"
+    />
+</LinearLayout>
+
diff --git a/res/layout/vcpact.xml b/res/layout/vcpact.xml
new file mode 100644 (file)
index 0000000..14ab14e
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent" 
+              android:layout_height="fill_parent" 
+              android:orientation="vertical"
+              android:paddingTop="4px">
+    <ImageView android:id="@+id/image"
+              android:layout_width="@dimen/chalX"
+              android:layout_height="@dimen/chalY" /> 
+    <Button android:id="@+id/done"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/image"
+            android:layout_alignParentLeft="true"
+            android:paddingTop="4px"
+            android:text="Done" />
+    <Button android:id="@+id/reset"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/image"
+            android:layout_alignParentRight="true"
+            android:paddingTop="4px"
+            android:text="Reset" />
+    <TextView android:id="@+id/prompttext"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingTop="4px"
+            android:layout_below="@id/done"
+    />
+</RelativeLayout>
diff --git a/res/layout/vcpimp.xml b/res/layout/vcpimp.xml
new file mode 100644 (file)
index 0000000..ad9383f
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:scrollbars="vertical">
+<LinearLayout
+       android:orientation="vertical" android:layout_width="fill_parent"
+       android:layout_height="fill_parent">
+               <TextView android:id="@+id/entry_header"
+                       android:paddingBottom="10px"
+                       android:paddingTop="10px"
+                       android:textSize="24px" 
+                       android:layout_width="wrap_content"
+                       android:layout_height="wrap_content"
+                       android:text="Enter VC seed secret:"
+               />
+       <EditText android:id="@+id/password" 
+               android:inputType="textPassword"
+               android:layout_width="fill_parent"
+               android:layout_height="wrap_content" />
+    <Button android:id="@+id/impdone"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/password"
+            android:layout_alignParentLeft="true"
+            android:paddingTop="4px"
+            android:text="Done" />
+</LinearLayout>
+</ScrollView>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644 (file)
index 0000000..b9daef1
--- /dev/null
@@ -0,0 +1,4 @@
+<resources>
+    <dimen name="chalX">320px</dimen>
+    <dimen name="chalY">320px</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644 (file)
index 0000000..a93ed37
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">VCPass</string>
+    <string name="import_test">
+        To ensure that you have correctly imported the seeds,
+        please answer the above challenge.  The back button
+        will take you back to importing.
+    </string>
+</resources>
diff --git a/src/org/ietfng/ns/android/vcpass/Utils.java b/src/org/ietfng/ns/android/vcpass/Utils.java
new file mode 100644 (file)
index 0000000..4f8497d
--- /dev/null
@@ -0,0 +1,46 @@
+package org.ietfng.ns.android.vcpass;
+
+import java.util.List;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+public class Utils {
+    /** From http://developer.android.com/resources/articles/can-i-use-this-intent.html */
+    public final static boolean isIntentAvailable(Context context, Intent intent) {
+        final PackageManager packageManager = context.getPackageManager();
+        List<ResolveInfo> list =
+            packageManager.queryIntentActivities(intent,
+                    PackageManager.MATCH_DEFAULT_ONLY);
+        return list.size() > 0;
+    }
+
+        /*
+         * DJB Netstrings-esque encoding functions for storing
+         * our two seeds in one string.
+         */
+        public static String encode_seeds(char[] useed, char[] vseed) {
+                StringBuilder seed = new StringBuilder();
+                seed.append(Integer.toString(useed.length));
+                seed.append(':');
+                seed.append(useed);
+                seed.append(Integer.toString(vseed.length));
+                seed.append(':');
+                seed.append(vseed);
+                return seed.toString();
+        }
+
+        public static char[][] decode_seeds(String s) {
+                StringBuilder sb = new StringBuilder(s);
+                int ulenend = sb.indexOf(":");
+                int ulen = Integer.parseInt(sb.substring(0,ulenend));
+                int vlenend = sb.indexOf(":",ulenend+ulen+1);
+                int vlen = Integer.parseInt(sb.substring(ulenend+ulen+1,vlenend));
+                char[][] ret = new char[2][];
+                ret[0] = sb.substring(ulenend+1,ulenend+ulen+1).toCharArray();
+                ret[1] = sb.substring(vlenend+1).toCharArray();
+                return ret;
+        }
+
+}
diff --git a/src/org/ietfng/ns/android/vcpass/VCGenerator.java b/src/org/ietfng/ns/android/vcpass/VCGenerator.java
new file mode 100644 (file)
index 0000000..bb1fd9b
--- /dev/null
@@ -0,0 +1,335 @@
+/** Code for generating visual cryptography slides and challenges.
+ * @author Nathaniel Filardo
+ * @license AGPLv3.
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.ProviderException;
+
+import java.util.Stack;
+import java.util.Vector;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+public final class VCGenerator {
+
+    public interface ProgCallback {
+        public void progress(int x);
+    }
+
+    private static final int cells = VCParameters.GRID_X
+                                   * VCParameters.GRID_Y;
+    private static final int cpix = VCParameters.DISP_X
+                                  * VCParameters.DISP_Y
+                                  / VCParameters.GRID_X
+                                  / VCParameters.GRID_Y;
+    private static final int crvpix = VCParameters.DISP_X
+                                    / VCParameters.GRID_X
+                                    / VCParameters.PR_X;
+    private static final int ccvpix = VCParameters.DISP_Y
+                                    / VCParameters.GRID_Y
+                                    / VCParameters.PR_Y;
+    private static final int crpix = VCParameters.DISP_X
+                                   / VCParameters.GRID_X;
+    private static final int ccpix = VCParameters.DISP_Y
+                                   / VCParameters.GRID_Y;
+
+    private static final void
+    _bitsToVCPixels(Stack<Integer> r,
+                    Integer c1,
+                    Integer c2,
+                    byte[] bits) {
+
+        for(int i = 0; i < crvpix/8; i++) {
+            byte by = bits[i];
+            for (int b = 0; b < 8; b++) {
+                boolean bi = !((by & 0x80) == 0);
+                by <<= 1;
+
+                for(int px = 0; px < VCParameters.PR_X; px++) {
+                    if(bi) { r.push(c1); }
+                    else   { r.push(c2); }
+                    Integer ct; ct = c1; c1 = c2; c2 = ct;
+                }
+            }
+        }
+
+        byte by = bits[(crvpix+7)/8 - 1];
+        for (int b = 0; b < crvpix%8; b++) {
+            boolean bi = !((by & 0x80) == 0);
+            by <<= 1;
+
+            for(int px = 0; px < VCParameters.PR_X; px++) {
+                if(bi) { r.push(c1); }
+                else   { r.push(c2); }
+                Integer ct; ct = c1; c1 = c2; c2 = ct;
+            }
+        }
+    }
+
+    private static final Vector<Integer>
+    bitsToVCPixels(byte[] bits) {
+        Stack<Integer> r = new Stack<Integer>();
+        int py;
+        for(py = 0; py < VCParameters.PR_Y/2; py++) {
+            _bitsToVCPixels(r, VCParameters.black, VCParameters.white, bits);
+        }
+        for(; py < VCParameters.PR_Y; py++) {
+            _bitsToVCPixels(r, VCParameters.white, VCParameters.black, bits);
+        }
+
+        return r;
+    }
+
+/*
+    private static final void
+    mapXor(byte[] a, byte[] b) {
+        assert(a.length == b.length);
+
+        for (int i = 0; i < a.length; i++) {
+            a[i] ^= b[i];
+        }
+    }
+*/
+
+    /*
+     * The CSPRNGs are clocked as follows:
+     *      For each cell,
+     *          For each row,
+     *              For each vocabulary entry,
+     *                  generate (CPIXX+7)/8 octets
+     *
+     * The byte array is consulted in ascending index order and
+     * bits within bytes are mapped to pixels LSB to MSB as left
+     * to right.
+     *
+     * Note that CSPRNG output bits may be lost if there are
+     * not an integer multiple of 8 vcpixels per row.
+     *
+     * Note that the user's slide does not have the "for each
+     * vocabulary entry" iteration.
+     *
+     * Even the distinguished vocabulary elements' owned pixels
+     * are clocked out of the CSPRNGs just for simplicity.
+     *
+     * The total number of bits read out for the user's slide is
+     *      GRID_X*GRID_Y*CPIXY*CPIXX
+     * or
+     *      4*4*160*160 bits = 409600 bits = 51200 bytes
+     * and for the challenge is
+     *      VCVOC_SIZE*...
+     * or
+     *      16*4*4*160*160 bits = 6553600 bits = 819200 bytes
+     */ 
+
+    public final static Integer[][]
+    generateSlide(
+        final char[] useed,
+        final ProgCallback pcb
+    ) throws
+        ProviderException, GeneralSecurityException
+    {
+        Cipher slidec = seedToCipher(useed);
+
+        Integer[][] cella = new Integer[cells][];
+
+        for(int i = 0; i < cells; i++) {
+            Vector<Integer> cell = new Vector<Integer>();
+            for(int j = 0; j < ccvpix; j++) {
+                byte[] srow = new byte[(crvpix+7)/8];
+                srow = slidec.update(srow);
+                assert(srow.length == (crvpix+7)/8);
+                Vector<Integer> srowvc = bitsToVCPixels(srow);
+                cell.addAll(srowvc);
+            } /* Row */
+            cella[i] = new Integer[cell.size()];
+            cell.toArray(cella[i]);
+        } /* Cell */
+
+        return cella;
+    }
+
+    public final static Integer[][] generateChallenge(
+        final char[] vseed,
+        final char[] useed,
+        final int[] plain,
+        final ProgCallback pcb
+    ) throws
+        ProviderException, GeneralSecurityException
+    {
+        Cipher slidec = seedToCipher(useed);
+        Cipher cellc = seedToCipher(vseed);
+
+        Integer[][] cella = new Integer[cells][];
+
+        for(int i = 0; i < cells; i++) {
+            if(Thread.interrupted()) {
+                return null;
+            }
+                       assert(plain[i] < VCParameters.VCVOC_SIZE);
+
+            Vector<Integer> cell = new Vector<Integer>();
+            for(int j = 0; j < ccvpix; j++) {
+                byte[] srow = new byte[(crvpix+7)/8];
+                srow = slidec.update(srow);
+                assert(srow.length == (crvpix+7)/8);
+
+                byte[] vrow = null;
+                /* Get the right bits into vrow */
+                for(int k = 0; k <= plain[i]; k++) {
+                    vrow = new byte[(crvpix+7)/8];
+                    vrow = cellc.update(vrow);
+                    assert(vrow.length == (crvpix+7)/8);
+                }
+
+                if(plain[i] <= VCParameters.VCVOC_DISTINGUISHED) {
+                    /* Set the owned pixels in this row to
+                     * match those of srow; mutates vrow in place.
+                     */
+
+                    /* This design works for square cells */
+                    assert(crvpix == ccvpix);
+
+                    final int lix = j/8;
+                    final int lbm
+                        = (j%8 == 0)
+                        ? 0
+                        : ((byte)0x80) >> ((j%8)-1);
+
+                    final int rix = (ccvpix+7-j)/8-1;
+                    final int rbm
+                        = ((ccvpix-j)%8 == 0)
+                        ? 0
+                        : ~(((byte)0x80) >> ((ccvpix+7-j)%8));
+
+                    switch(plain[i]) {
+                        case VCParameters.VCVOC_DISTING_DOWN:
+                            if (j < ccvpix/2) {
+                                if(lix == rix) {
+                                    vrow[lix] &= lbm | rbm;
+                                    vrow[lix] |= srow[lix] & ~(lbm | rbm);
+                                } else {
+                                    vrow[lix] &= lbm;
+                                    vrow[lix] |= srow[lix] & ~lbm;
+                                    vrow[rix] &= rbm;
+                                    vrow[rix] |= srow[rix] & ~rbm;
+                                }
+                            }
+                            for(int k = lix+1; k < rix; k++)
+                                vrow[k] = srow[k];
+                            break;
+                        case VCParameters.VCVOC_DISTING_UP:
+                            if (j > ccvpix/2) {
+                                if(lix == rix) {
+                                    vrow[lix] &= ~lbm | ~rbm;
+                                    vrow[lix] |= srow[lix] & (lbm & rbm);
+                                } else {
+                                    vrow[lix] &= ~lbm;
+                                    vrow[lix] |= srow[lix] & lbm;
+                                    vrow[rix] &= ~rbm;
+                                    vrow[rix] |= srow[rix] & rbm;
+                                }
+                            }
+                            for(int k = rix+1; k < lix; k++)
+                                vrow[k] = srow[k];
+                            break;
+                        case VCParameters.VCVOC_DISTING_RIGHT:
+                            if (j < ccvpix/2) {
+                                vrow[lix] &= ~lbm;
+                                vrow[lix] |= srow[lix] & lbm;
+                                for(int k = 0; k < lix; k++)
+                                    vrow[k] = srow[k];
+                            } else {
+                                vrow[rix] &= rbm;
+                                vrow[rix] |= srow[rix] & ~rbm;
+                                for(int k = 0; k < rix; k++)
+                                    vrow[k] = srow[k];
+                            }
+                            break;
+                        case VCParameters.VCVOC_DISTING_LEFT:
+                            if (j < ccvpix/2) {
+                                vrow[rix] &= ~rbm;
+                                vrow[rix] |= srow[rix] & rbm;
+                                for(int k = rix+1; k < (ccvpix+7)/8; k++)
+                                    vrow[k] = srow[k];
+                            } else {
+                                vrow[lix] &= lbm;
+                                vrow[lix] |= srow[lix] & ~lbm;
+                                for(int k = lix+1; k < (ccvpix+7)/8; k++)
+                                    vrow[k] = srow[k];
+                            }
+                            break;
+                    }
+                }
+
+                /* Set pixels in resulting image */
+                Vector<Integer> vrowvc = bitsToVCPixels(vrow);
+                cell.addAll(vrowvc);
+                
+                /* Drain the remaining bits from the CSPRNG */
+                for(int k = plain[i] + 1;
+                        k < VCParameters.VCVOC_SIZE;
+                        k++) {
+                    vrow = new byte[(crvpix+7)/8];
+                    vrow = cellc.update(vrow);
+                    assert(vrow.length == (crvpix+7)/8);
+                }
+            } /* Row */
+            cella[i] = new Integer[cell.size()];
+            cell.toArray(cella[i]);
+
+            if (pcb != null) { pcb.progress(i); }
+        } /* Cell */
+
+        return cella;
+    }
+
+    public final static int[][] vcArrayToPixels(Integer[][] p) {
+        assert(p.length == cells);
+        for(int i = 0; i < cells; i++) {
+            assert(p[i].length == crpix*ccpix);
+        }
+
+        int[][] res = new int[VCParameters.DISP_Y][];
+        for(int r = 0; r < VCParameters.DISP_Y; r++) {
+            res[r] = new int[VCParameters.DISP_X];
+
+            for(int c = 0; c < VCParameters.DISP_X; c++) {
+                final int cell =
+                    r/crpix*VCParameters.GRID_X + (c/ccpix);
+                final int pixoff =
+                    (r%crpix)*ccpix + c%ccpix;
+                res[r][c] = p[cell][pixoff];
+            }
+        }
+
+        return res;
+    }
+
+    private static final Cipher
+    seedToCipher(final char[] seed)
+    throws ProviderException, GeneralSecurityException
+    {
+        SecretKeyFactory skf =
+            SecretKeyFactory.getInstance(VCParameters.CSKEYFACT,
+                                         VCParameters.CSPROV);
+
+        byte[] salt = { 0x22, 0x24 };
+        PBEKeySpec pks = new PBEKeySpec(seed, salt, 1024);
+        SecretKey sk = skf.generateSecret(pks);
+
+        Cipher c = Cipher.getInstance(VCParameters.CSPRNG,
+                                      VCParameters.CSPROV);  
+        c.init(Cipher.ENCRYPT_MODE, sk);
+
+        return c;
+    }
+}
+
diff --git a/src/org/ietfng/ns/android/vcpass/VCParameters.java b/src/org/ietfng/ns/android/vcpass/VCParameters.java
new file mode 100644 (file)
index 0000000..b9f5d43
--- /dev/null
@@ -0,0 +1,41 @@
+package org.ietfng.ns.android.vcpass;
+
+import java.security.Provider;
+import javax.crypto.spec.PBEKeySpec;
+
+public class VCParameters {
+        /* CSPRNG Class name */
+    static final Provider CSPROV =
+        new org.bouncycastle.jce.provider.BouncyCastleProvider();
+    static final String CSPRNG = "AES/CFB8/NoPadding";
+    static final String CSKEYFACT = "PBEWithSHAAnd128BitAES-CBC-BC";
+
+        /* Challenge grid size */
+    static final int GRID_X = 4;
+    static final int GRID_Y = 4;
+
+        /* Real pixels per VC Pixel */
+    static final int PR_X = 2;
+    static final int PR_Y = 2;
+
+        /* Display size, in real pixels */
+    static final int DISP_X = 320;
+    static final int DISP_Y = 320;
+
+        /** Number of entries in the per-cell vocabulary */
+    static final int VCVOC_SIZE = 6;
+        /** Last index of distinguished entries in the per-cell vocabulary.
+         * @see VCPassActivity.VCPassTouchHandler.onTouch
+         * @see VCGenerator.createChallenge
+         */
+    static final int VCVOC_DISTINGUISHED = 3;
+
+    static final int VCVOC_DISTING_UP    = 0;
+    static final int VCVOC_DISTING_DOWN  = 1;
+    static final int VCVOC_DISTING_LEFT  = 2;
+    static final int VCVOC_DISTING_RIGHT = 3;
+
+        /** Pixel values */
+    static final Integer black = 0xFF000000;
+    static final Integer white = 0xFFFFFFFF;
+};
diff --git a/src/org/ietfng/ns/android/vcpass/VCPass.java b/src/org/ietfng/ns/android/vcpass/VCPass.java
new file mode 100644 (file)
index 0000000..6de8978
--- /dev/null
@@ -0,0 +1,223 @@
+/* Visual Cryptography Passcodes
+ * (C) 2009 Nathaniel Filardo
+ *
+ * This code is available under the GPLv3 license.
+ *
+ * Much credit is owed to the excellent documentation and example code at
+ * http://developer.android.com.
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.lang.reflect.Field; 
+import java.lang.reflect.Method; 
+import java.io.Serializable;
+import java.security.Provider;
+import java.util.Formatter;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.util.Log;
+
+import dalvik.system.PathClassLoader;
+
+
+// drawing
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+// import com.google.zxing.client.android.Intents;
+
+public class VCPass extends Activity
+{
+    private boolean canceled = false;
+    private boolean inited = false;
+    private char[] useed;
+    private char[] vseed;
+    private String secret;
+    private Bitmap chal;
+
+    private static final int REQ_IMPORT = 0;
+    private static final int REQ_CREATE = 1;
+    private static final int REQ_PRESENT = 2;
+
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+
+        Log.i("VCPass", "CREATE");
+
+
+               if(0) {
+                       PackageManager pm = getPackageManager();
+               Intent intent = new Intent("org.ietfng.ns.android.vcpass.CHAL_PRESENT");
+                       ResolveInfo ri = pm.resolveActivity(intent, 0);
+                       PathClassLoader pcl = new PathClassLoader
+                               (ri.activityInfo.applicationInfo.publicSourceDir
+                               ,getClassLoader()
+                       );
+
+                       Class c;
+                       try{    
+                               c = pcl.loadClass(ri.activityInfo.name);
+                               Log.i("VCPass:CREATE:c", c.toString());
+                       } catch (ClassNotFoundException cnfe) {
+                               Log.i("VCPass:CREATE:c", "CNFE" + cnfe.toString());
+                               return;
+                       }
+
+                       /* This loop is dumb because Java clearly knows better than we do
+                        * but specifying the parameter types is a pain.  If something goes
+                        * wrong, it'll get caught by reflection later.
+                        */
+                       Method m = null;
+                       for(Method mc : c.getMethods()) {
+                               if(mc.getName().equals("do_createChallenge")) { m = mc; break; }
+                       }
+                       if(m == null) {
+                               Log.i("VCPass:CREATE:m", "NOT FOUND");
+                               return;
+                       } else {
+                               Log.i("VCPass:CREATE:m", m.toString());
+                       }
+
+                       Object r;
+                       try {
+                               r = m.invoke(null, "foo".toCharArray(),
+                                                                       "bar".toCharArray(),
+                                                                       new Integer(0),
+                                                                       null);
+                               Log.i("VCPass:CREATE:r", r.toString());
+                       } catch (IllegalAccessException iae) {
+                               Log.i("VCPass:CREATE:r", "IAE" +  iae.toString());
+                               return;
+                       } catch (java.lang.reflect.InvocationTargetException ite) {
+                               Log.i("VCPass:CREATE:r", "ITE" +  ite.toString());
+                               return;
+                       }
+
+                       try {
+                               Field fe = r.getClass().getField("error");
+                               String e = (String) fe.get(r);
+                               Field fp = r.getClass().getField("plain");
+                               String p = (String) fp.get(r);
+                               Field fb = r.getClass().getField("bm");
+                               Bitmap b = (Bitmap) fb.get(r);
+
+                               if(e != null)
+                                       Log.i("VCPass:CREATE:e", e);
+                               else {
+                                       Log.i("VCPass:CREATE:b", b.toString());
+                                       Log.i("VCPass:CREATE:p", p);
+                               }
+                       } catch (IllegalAccessException iae) {
+                               Log.i("VCPass:CREATE:_", "IAE" +  iae.toString());
+                               return;
+                       } catch (NoSuchFieldException nsfe) {
+                               Log.i("VCPass:CREATE:_", "NSFE" +  nsfe.toString());
+                               return;
+                       }
+               }
+
+/*
+        for (String s : Security.getAlgorithms("cipher")) {
+            Log.d ("VCPassCrypto", s);
+        }
+        try {
+            Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
+        } catch (NoSuchAlgorithmException e) {
+            Log.d("VCPassCrypto", "Argh NSA");
+        } catch (NoSuchPaddingException e) {
+            Log.d("VCPassCrypto", "Argh NSP");
+        }
+*/
+
+    }
+
+
+    private void launchImporter() {
+        Intent intent = new Intent(this, VCPassImport.class);
+        intent.setAction(VCPassImport.ACTION_IMPORT_SEED);
+        startActivityForResult(intent, REQ_IMPORT);
+    }
+
+    private void launchGenerator() {
+        Intent intent = new Intent(this, VCPassActivity.class);
+        intent.setAction(VCPassActivity.ACTION_CREATE_CHALLENGE);
+        intent.putExtra(VCPassActivity.EXTRA_USER_SLIDE_SEED,useed);
+        intent.putExtra(VCPassActivity.EXTRA_VOCABULARY_SEED,vseed);
+        // intent.putExtra(VCPassActivity.EXTRA_QUIET_OPERATION,true);
+        Log.d("VCPass", intent.toString());
+        startActivityForResult(intent, REQ_CREATE);
+    }
+
+    private void launchChallenger() {
+        Intent intent = new Intent(this, VCPassActivity.class);
+        intent.setAction(VCPassActivity.ACTION_PRESENT_CHALLENGE);
+        intent.putExtra(VCPassActivity.EXTRA_CHALLENGE,chal);
+        intent.putExtra(VCPassActivity.EXTRA_PROMPT_TEXT,"Try this one...");
+        Log.d("VCPass", intent.toString());
+        startActivityForResult(intent, REQ_PRESENT);
+    }
+
+    public void onResume()
+    {
+        super.onResume();
+
+        Log.i("VCPass", "RESUME");
+
+        if(!inited) {
+            inited = true;
+           launchImporter();
+        }
+    }
+
+    public void onActivityResult(int req, int res, Intent data)
+    {
+        Log.i("VCPass", "OAR");
+        Log.i("VCPass", Integer.toString(req));
+
+        if(data != null)
+            Log.i("VCPass", data.getAction());
+
+        if(res == RESULT_CANCELED) {
+            Log.d("VCPass", "CANCELED");
+            canceled = true;
+            String err = null;
+            if(data != null)
+               err = data.getStringExtra(VCPassActivity.EXTRA_ERROR);
+            if(err != null)
+                Log.i("VCPass", err);
+           finish();
+        } else {
+            if (req == REQ_IMPORT) {
+               useed = data.getCharArrayExtra(VCPassActivity.EXTRA_USER_SLIDE_SEED);
+               vseed = data.getCharArrayExtra(VCPassActivity.EXTRA_VOCABULARY_SEED);
+
+               launchGenerator();
+            } else if(req == REQ_CREATE) {
+                chal = (Bitmap) data.getParcelableExtra(VCPassActivity.EXTRA_CHALLENGE);
+                secret = data.getStringExtra(VCPassActivity.EXTRA_SECRET);
+                Log.i("VCPass", "CREATECHAL: " + secret);
+
+               launchChallenger();
+            } else if (req == REQ_PRESENT) {
+                String s = data.getStringExtra(VCPassActivity.EXTRA_SECRET);
+                if(s.equals(secret)) {
+                    Log.i("VCPass","correct");
+                   launchGenerator();
+                } else {
+                   launchChallenger();
+               }
+            } else {
+                throw new RuntimeException("Invalid request!");
+            }
+        }
+    }
+}
diff --git a/src/org/ietfng/ns/android/vcpass/VCPassActivity.java b/src/org/ietfng/ns/android/vcpass/VCPassActivity.java
new file mode 100644 (file)
index 0000000..8d46be4
--- /dev/null
@@ -0,0 +1,665 @@
+/* Visual Cryptography Passcodes
+ * (C) 2009 Nathaniel Filardo
+ *
+ * This code is available under the GPLv3 license.
+ *
+ * Much credit is owed to the excellent documentation and example code at
+ * http://developer.android.com.
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.io.Serializable;
+import java.security.ProviderException;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Formatter;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Parcelable;
+import android.util.Log;
+// import android.view.Menu;
+// import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public final class
+VCPassActivity
+extends Activity
+{
+    /* * * * * * Protocol parameters * * * * * */
+
+    private static final String DBGN = "VCPassAct";
+
+    private static final String SAVED_STATE_KEY_RESPONSE
+        = "SEQ";
+
+        /** Intent ACTION name for challenging the user */
+    public static final String ACTION_PRESENT_CHALLENGE
+        = "org.ietfng.ns.android.vcpass.CHAL_PRESENT";
+        /** Intent ACTION name for creating a challenge */
+    public static final String ACTION_CREATE_CHALLENGE
+        = "org.ietfng.ns.android.vcpass.CHAL_CREATE";
+
+        /** Intent EXTRA name for error messages.
+         *
+         * Type: java.lang.String
+         *
+         * Used VCPA to caller for ACTION_*
+         */
+    public static final String EXTRA_ERROR
+        = "ERR";
+        /** Intent EXTRA name for prompt text.
+         *
+         * Type: java.lang.String
+         *
+         * Not used for ACTION_CREATE_CHALLENGE
+         * Used caller to VCPA for ACTION_PRESENT_CHALLENGE
+         */
+    public static final String EXTRA_PROMPT_TEXT
+        = "PT";
+        /** Intent EXTRA name for plain-text response.
+         *
+         * Type: java.lang.String
+         *
+         * Used VCPA to caller for ACTION_CREATE_CHALLENGE
+         * Used VCPA to caller for ACTION_PRESENT_CHALLENGE
+         */
+    public static final String EXTRA_SECRET
+        = "SEQ";
+        /** Intent EXTRA name for Bitmap challenge.
+         *
+         * Type: android.graphics.Bitmap
+         *
+         * Used VCPA to caller for ACTION_CREATE_CHALLENGE.
+         * Used caller to VCPA for ACTION_PRESENT_CHALLENGE.
+         */
+    public static final String EXTRA_CHALLENGE
+        = "CHAL";
+        /** Intent EXTRA name for user slide seed.
+         *
+         * Type: [char
+         *
+         * Used caller to VCPA for ACTION_CREATE_CHALLENGE.
+         * Not used for ACTION_PRESENT_CHALLENGE; 
+         *      ideally, the contents would be unknown to the caller.
+         * Used VCPA to caller for ACTION_IMPORT_SEED
+         */
+    public static final String EXTRA_USER_SLIDE_SEED
+        = "USEED";
+        /** Intent EXTRA name for vocabulary seed.
+         *
+         * Type: [char
+         *
+         * Used caller to VCPA for ACTION_CREATE_CHALLENGE.
+         * Not used for ACTION_PRESENT_CHALLENGE; 
+         *      ideally, the contents would be unknown to the caller.
+         * Used VCPA to caller for ACTION_IMPORT_SEED
+         */
+    public static final String EXTRA_VOCABULARY_SEED
+        = "VSEED";
+
+        /** Intent EXTRA name for quiet operation.
+         *
+         * Type: void
+         *
+         * Used caller to VCPA for ACTION_CREATE_CHALLENGE
+         * Not used for ACTION_PRESENT_CHALLENGE
+         */
+    public static final String EXTRA_QUIET_OPERATION = "QUIET";
+
+        /** Intent EXTRA name for user slide seed.
+         *
+         * Type: int
+         *
+         * Used caller to VCPA for ACTION_CREATE_CHALLENGE.
+         * Not used for ACTION_PRESENT_CHALLENGE
+         */
+    public static final String EXTRA_MINIMUM_EVENTS = "MINEVT";
+
+    /* * * * * * Private constants * * * * * */
+
+    private static final int cells = VCParameters.GRID_X
+                                   * VCParameters.GRID_Y;
+    private static final int crpix = VCParameters.DISP_X
+                                   / VCParameters.GRID_X;
+    private static final int ccpix = VCParameters.DISP_Y
+                                   / VCParameters.GRID_Y;
+
+    private static final int REQ_PRESENT_TEST   = 2;
+
+    /* * * * * * Private state * * * * * */
+
+    private Resources res;                     // Android
+    private int[] response;                    // For state saving
+    private Thread calcThread;
+
+    /* * * * * * Private utility functions * * * * * */
+
+    private final void
+    yieldError(Intent s, String e) {
+        s.putExtra(EXTRA_ERROR, e);
+        setResult(RESULT_CANCELED, s);
+        finish();
+    }
+
+    private final void
+    postYieldError(final Intent s, final String e) {
+        runOnUiThread(new Runnable(){
+            public final void run() {
+                yieldError(s,e);
+            }
+        });
+    }
+
+    /** Prepare an int array for use as a response store */
+    private static final void
+    resetResponse(int[] chal) {
+        assert(chal.length == VCParameters.GRID_X*VCParameters.GRID_Y);
+
+        for(int i = 0;
+                i < VCParameters.GRID_X*VCParameters.GRID_Y;
+                i++) {
+            chal[i] = -1;
+        }
+    }
+
+    /**
+     * Render a challenge grid as plain text.
+     *
+     * Each cell of the array contains an index into the
+     * vocabulary; the values [0,VCVOC_DISTINGUISHED] are
+     * rendered into the resulting string.  The remainder
+     * are ignored.
+     */
+    private static final StringBuilder
+    encodeResponse(int[] chal) {
+        assert(chal.length == VCParameters.GRID_X*VCParameters.GRID_Y);
+
+        StringBuilder enc = new StringBuilder();
+
+        for(int i = 0;
+                i < VCParameters.GRID_X*VCParameters.GRID_Y;
+                i++) {
+            assert(chal[i] >= -1);
+            assert(chal[i] <= VCParameters.VCVOC_SIZE);
+            if(chal[i] >= 0
+             && chal[i] <= VCParameters.VCVOC_DISTINGUISHED) {
+                // enc.append(Integer.toString(i));
+                enc.append(Integer.toString(chal[i]));
+            } else {
+                enc.append("_");
+            }
+        }
+
+        return enc;
+    }
+
+    /* * * * * * Presenting a challenge to the user * * * * * */
+
+    private final class
+    VCPassPreDrawListener
+    implements ViewTreeObserver.OnPreDrawListener { 
+        private Intent i;
+        private ImageView v;
+        private Bitmap o;
+
+        public VCPassPreDrawListener(Intent i, ImageView v, Bitmap o) {
+            this.i = i;
+            this.v = v;
+            this.o = o;
+        }
+
+        public boolean onPreDraw() {
+            // If the challenge will not fit, bail out.
+            if (v.getWidth()  < o.getWidth()
+             || v.getHeight() < o.getHeight()) {
+                Log.d("AAAA", Integer.toString(v.getWidth()));
+                Log.d("AAAA", Integer.toString(o.getWidth()));
+                Log.d("AAAA", Integer.toString(v.getHeight()));
+                Log.d("AAAA", Integer.toString(o.getHeight()));
+                yieldError(i, "Challenge of incorrect size");
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    private final class
+    VCPassTouchHandler
+    implements View.OnTouchListener {
+        private int[] r;
+        private Canvas c;
+        private Paint p;
+
+        public VCPassTouchHandler(Canvas c, int[] r) {
+            this.r = r;
+            this.c = c;
+            p = new Paint();
+            p.setARGB(127,0,255,0);
+        }
+
+        private int expected_motion = MotionEvent.ACTION_DOWN;
+        private float last_down_xc = -1;
+        private float last_down_yc = -1;
+        private int last_down_x = -1;
+        private int last_down_y = -1;
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            int action = event.getAction();
+            float w = v.getWidth();
+            float h = v.getHeight();
+            float xc = event.getX();
+            float yc = event.getY();
+
+            if (action != expected_motion)
+                return true;
+
+            int x = (int)(xc / w * VCParameters.GRID_X);
+            int y = (int)(yc / h * VCParameters.GRID_Y);
+
+            if( x < 0 || y < 0 )
+                return true;
+
+
+            if (action == MotionEvent.ACTION_DOWN) {
+                last_down_xc = xc;
+                last_down_yc = yc;
+                last_down_x = x;
+                last_down_y = y;
+                expected_motion = MotionEvent.ACTION_UP;
+            } else if (action == MotionEvent.ACTION_UP) {
+                float deltaX = xc - last_down_xc;
+                float deltaY = yc - last_down_yc;
+
+                int label;
+                assert(VCParameters.VCVOC_DISTINGUISHED == 3);
+                if ( Math.abs(deltaX) > Math.abs(deltaY) ) {
+                    if ( deltaX > 0 ) {
+                        label = VCParameters.VCVOC_DISTING_RIGHT;
+                    } else {
+                        label = VCParameters.VCVOC_DISTING_LEFT;
+                    }
+                } else {
+                    if ( deltaY > 0 ) {
+                        label = VCParameters.VCVOC_DISTING_DOWN;
+                    } else {
+                        label = VCParameters.VCVOC_DISTING_UP;
+                    }
+                }
+                r[VCParameters.GRID_X*last_down_y
+                 + last_down_x] = label;
+
+                expected_motion = MotionEvent.ACTION_DOWN;
+
+                Rect sq = new Rect((int)(last_down_x*w
+                                        /VCParameters.GRID_X
+                                        ),
+                                    (int)(last_down_y*h
+                                         /VCParameters.GRID_Y
+                                         ),
+                                    (int)((last_down_x+1)*w
+                                         /VCParameters.GRID_X
+                                         ),
+                                    (int)((last_down_y+1)*h
+                                         /VCParameters.GRID_Y
+                                         ));
+                Log.d("VCPTH", encodeResponse(r).toString());
+                c.drawRect(sq, p);
+                v.invalidate();
+            } else {
+                throw new RuntimeException("Unexpected action:" + action + "\n");
+            }
+
+            return true;
+        }
+    }
+
+    private static final class
+    VCPassResetHandler
+    implements View.OnClickListener {
+        private ImageView v;
+        private Canvas c;
+        private Bitmap o;
+        private int[] r;
+
+        VCPassResetHandler(Canvas c, ImageView v, Bitmap o, int[] r)
+        {
+            this.c = c;
+            this.v = v;
+            this.o = o;
+            this.r = r;
+        }
+
+        @Override
+        public void onClick(View bv) {
+            Bitmap nb = o.copy(o.getConfig(), true);
+            // s.delete(0,s.length());
+            resetResponse(r);
+            c.setBitmap(nb);
+            v.setImageBitmap(nb);
+        }
+    }
+
+    private final class
+    VCPassCompletionHandler
+    implements View.OnClickListener {
+        private Intent i;
+        private int[] r;
+
+        VCPassCompletionHandler(Intent i, int[] r)
+        {
+            this.i = i;
+            this.r = r;
+        }
+
+        @Override
+        public void onClick(View bv) {
+            i.putExtra(EXTRA_SECRET, encodeResponse(r).toString());
+            setResult(RESULT_OK, i);
+            finish();
+        }
+    }
+
+
+    private final void
+    presentChallenge(Intent spawner, Bundle sis) {
+        response = null;
+        if (sis != null) {
+            response = sis.getIntArray(SAVED_STATE_KEY_RESPONSE);
+        }
+        if(response == null) {
+            response = new int[VCParameters.GRID_X
+                              *VCParameters.GRID_Y];
+            resetResponse(response);
+        }
+
+
+        Bitmap origchal = (Bitmap)spawner.getParcelableExtra(EXTRA_CHALLENGE);
+        if (null == origchal) {
+            yieldError(spawner, "No Challenge Given");
+            return;
+        }
+
+        /*
+         * XXX Do we have to draw blanks here?  Does our graphic state
+         * get saved for us?
+         */
+
+        Bitmap chal = origchal.copy(origchal.getConfig(), true);
+        Canvas c = new Canvas(chal);
+
+        // Switch off the titlebar
+        // requestWindowFeature(Window.FEATURE_NO_TITLE);
+        // Lock orientation
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        // Load layout
+        setContentView(R.layout.vcpact);
+
+        ImageView imgview = (ImageView) findViewById(R.id.image);
+        imgview.setScaleType(ImageView.ScaleType.FIT_CENTER);
+        imgview.setImageBitmap(chal);
+        imgview.setOnTouchListener(new VCPassTouchHandler(c, response));
+        imgview.getViewTreeObserver()
+               .addOnPreDrawListener(
+                   new VCPassPreDrawListener(spawner, imgview, origchal));
+
+        Button resetbtn = (Button) findViewById(R.id.reset);
+        resetbtn.setOnClickListener(
+            new VCPassResetHandler(c, imgview, origchal, response));
+        Button donebtn = (Button) findViewById(R.id.done);
+        donebtn.setOnClickListener(
+            new VCPassCompletionHandler(spawner, response));
+
+        String pt = spawner.getStringExtra(EXTRA_PROMPT_TEXT);
+        if(pt != null) {
+            TextView pttv = (TextView) findViewById(R.id.prompttext);
+            pttv.setText(pt);
+        }
+    }
+
+    /* * * * * * Creating a challenge * * * * * */
+
+       public static class CreatedChallenge {
+               /* Have I mentioned recently how much I hate Java?
+                * This is as close to error+plain*bm as is trivial
+                * to make here.
+                */
+       public String plain;
+               public Bitmap bm;
+               public String error;
+       }
+
+       public static CreatedChallenge
+       do_createChallenge(
+               char[] useed,
+               char[] vseed,
+               int minevt,
+               VCGenerator.ProgCallback pcb
+       ) {
+               CreatedChallenge res = new CreatedChallenge();
+               int[] plain = new int[cells];
+
+        try {
+            // Let this one initialize from the system's random source
+            SecureRandom chalsr = new SecureRandom();
+            int evtc = 0;
+            do {
+                evtc = 0;
+                for(int i = 0; i < cells; i++) {
+                    plain[i] = chalsr.nextInt(VCParameters.VCVOC_SIZE+1);
+                    if(plain[i] <= VCParameters.VCVOC_DISTINGUISHED) {
+                        evtc++;
+                    }
+                }
+            } while (evtc < minevt);
+
+            res.plain = encodeResponse(plain).toString();
+            
+            Integer[][] cella = VCGenerator.generateChallenge(
+                                    vseed, useed,
+                                    plain, pcb
+                                );
+            if(cella == null) {
+                res.error = "Null return from generator";
+                return res;
+            }
+
+            assert(cella.length == cells);
+            assert(cella[0].length == crpix*ccpix);
+
+            res.bm = Bitmap.createBitmap(
+                        VCParameters.DISP_X,
+                        VCParameters.DISP_Y,
+                        Bitmap.Config.RGB_565);
+
+            for(int i = 0; i < cells; i++) {
+                int[] cellai = new int[cella[i].length];
+                for(int j = 0; j < cella[i].length; j++) {
+                    cellai[j] = cella[i][j];
+                }
+
+                res.bm.setPixels(cellai, 0,
+                                crpix,
+                                (i%VCParameters.GRID_X)*crpix,
+                                (i/VCParameters.GRID_Y)*ccpix,
+                                crpix, ccpix
+                               );
+            }
+
+/* Alternative, row-by-row formulation:
+        int[][] pixels = VCGenerator.vcArrayToPixels(cella);
+        for(int i = 0; i < pixels.length; i++) {
+            res.bm.setPixels(pixels[i], 0, pixels[i].length, 0, i, pixels[i].length, 1);
+        }
+*/
+
+            Paint p = new Paint();
+            p.setColor(0xC0FFFF00);
+
+            Canvas c = new Canvas(res.bm);
+            for(int i = crpix-1; i < VCParameters.DISP_X-1; i += crpix){
+                c.drawLine(i,0,i,VCParameters.DISP_Y,p);
+                c.drawLine(i+1,0,i+1,VCParameters.DISP_Y,p);
+            }
+            for(int i = ccpix-1; i < VCParameters.DISP_Y-1; i += ccpix){
+                c.drawLine(0,i,VCParameters.DISP_X,i,p);
+                c.drawLine(0,i+1,VCParameters.DISP_X,i+1,p);
+            }
+
+        } catch (ProviderException pe) {
+                       res.error = pe.toString();
+            return res;
+        } catch (GeneralSecurityException gse) {
+                       res.error = gse.toString();
+            return res;
+        }
+
+/*
+            Paint p = new Paint();
+            Canvas c = new Canvas(bm);
+            p.setTextSize(20);
+
+            StringBuilder sb = new StringBuilder();
+            Formatter f = new Formatter(sb);
+            f.format("%f", p.getFontMetrics().top);
+            Log.d("VCPassPDH", sb.toString());
+
+            c.drawText("1", 1.0f, 1.0f, p);
+            c.drawText("30", 30.0f, 30.0f, p);
+            c.drawText("70", 70.0f, 70.0f, p);
+*/
+
+               return res;
+
+       }
+
+    private final void
+    _intent_createChallenge(final Intent spawner,
+                        boolean quiet) {
+        char[] useed = spawner.getCharArrayExtra(EXTRA_USER_SLIDE_SEED   );
+        char[] vseed = spawner.getCharArrayExtra(EXTRA_VOCABULARY_SEED   );
+        int minevt   = spawner.getIntExtra      (EXTRA_MINIMUM_EVENTS, -1);
+
+        if(useed == null || vseed == null) {
+            postYieldError(spawner, "Null seed");
+            return;
+        }
+
+        final VCGenerator.ProgCallback pcb
+        = quiet ? null : new VCGenerator.ProgCallback() {
+            public void progress(final int x) {
+                    runOnUiThread(new Runnable() {
+                        public final void run() {
+                            getWindow().setFeatureInt(
+                                            Window.FEATURE_PROGRESS,
+                                            x*9000
+                                            /VCParameters.GRID_X
+                                            /VCParameters.GRID_Y);
+                        }
+                   });
+            }
+        };
+
+               CreatedChallenge cc = do_createChallenge(useed, vseed, minevt, pcb);
+               if(cc.error != null) {
+               postYieldError(spawner, cc.error);
+                       return;
+               }
+
+        spawner.putExtra(EXTRA_CHALLENGE, (Parcelable)cc.bm);
+        spawner.putExtra(EXTRA_SECRET, cc.plain);
+
+        calcThread = null;
+
+        runOnUiThread(new Runnable() {
+            final public void run() {
+                setResult(RESULT_OK, spawner);
+                finish();
+            }
+        });
+    }
+
+    private final void
+    createChallenge(final Intent spawner, Bundle sis) {
+        final boolean quiet = spawner.hasExtra(EXTRA_QUIET_OPERATION);
+
+        if(!quiet) {
+            requestWindowFeature(Window.FEATURE_PROGRESS);
+            getWindow().setFeatureInt(Window.FEATURE_PROGRESS, 0);
+
+            TextView tv = new TextView(this);
+            tv.setText("Please wait while I do some math...");
+            setContentView(tv);
+        }
+
+        calcThread = new Thread(new Runnable() {
+            public final void run() {
+                _intent_createChallenge(spawner, quiet);
+            }
+        });
+
+        calcThread.start();
+    }
+
+    /* * * * * * Android interface core * * * * * */
+
+    @Override
+    public void onCreate(Bundle sis)
+    {
+        super.onCreate(sis);
+
+        Log.i(DBGN, "CREATE");
+
+        res = getResources();
+
+        Intent spawner = getIntent();
+        String act = spawner.getAction();
+        Log.d(DBGN, act);
+
+        if(act.equals(ACTION_PRESENT_CHALLENGE)) {
+            presentChallenge(spawner, sis);
+        } else if (act.equals(ACTION_CREATE_CHALLENGE)) {
+            createChallenge(spawner, sis);
+        } else {
+            setResult(RESULT_CANCELED, null);
+            finish();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        if(isFinishing()) {
+            if(calcThread != null) { calcThread.interrupt(); }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        /* If we were presenting a challenge this time around,
+         * store our secret so that we can come back to it
+         */
+        if(getIntent().getAction().equals(ACTION_PRESENT_CHALLENGE)) {
+            outState.putIntArray(SAVED_STATE_KEY_RESPONSE, response);
+        }
+    }
+}
diff --git a/src/org/ietfng/ns/android/vcpass/VCPassImport.java b/src/org/ietfng/ns/android/vcpass/VCPassImport.java
new file mode 100644 (file)
index 0000000..7aebd63
--- /dev/null
@@ -0,0 +1,255 @@
+package org.ietfng.ns.android.vcpass;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+public final class
+VCPassImport
+extends Activity
+{
+    private static final boolean TRY_QRCODE = false;
+
+    /* * * * * * Protocol parameters * * * * * */
+
+    private static final String DBGN = "VCPassImp";
+
+    private static final String SAVED_SPAWNER = "SPAWN";
+
+    public static final String ACTION_IMPORT_SEED
+        = "org.ietfng.ns.android.vcpass.SEED_IMPORT";
+    public static final String ACTION_IMPORT_AND_CREATE
+        = "org.ietfng.ns.android.vcpass.IMPORT_AND_CREATE";
+
+    private static final int REQ_IMPORT = 0;
+    private static final int REQ_CREATE_FOR_PRESENT = 1;
+    private static final int REQ_PRESENT = 2;
+    private static final int REQ_CREATE = 3;
+
+    /* EXTRAs are as in VCPassActivity */
+
+    /* * * * * * Private state * * * * * */
+
+    char[] useed;
+    char[] vseed;
+    Bitmap cfpc;
+    String cfps;
+
+    final String SAVED_STATE_USEED = "USEED";
+    final String SAVED_STATE_VSEED = "VSEED";
+    final String SAVED_STATE_CFPC  = "CFPC";
+    final String SAVED_STATE_CFPS  = "CFPS";
+
+    /* * * * * * Importing Seeds * * * * * */
+
+    private void
+    launchGenerator(int res, int min) {
+        Log.d(DBGN, "Launching generator...");
+        Intent intent = new Intent(this, VCPassActivity.class);
+        intent.setAction(VCPassActivity.ACTION_CREATE_CHALLENGE);
+        intent.putExtra(VCPassActivity.EXTRA_USER_SLIDE_SEED,useed);
+        intent.putExtra(VCPassActivity.EXTRA_VOCABULARY_SEED,vseed);
+        if(min > 0) {
+            intent.putExtra(VCPassActivity.EXTRA_MINIMUM_EVENTS,min);
+        }
+        startActivityForResult(intent, res);
+    }
+
+    private void launchChallenger() {
+        Log.d(DBGN, "Launching challenger...");
+        Intent intent = new Intent(this, VCPassActivity.class);
+        intent.setAction(VCPassActivity.ACTION_PRESENT_CHALLENGE);
+        intent.putExtra(VCPassActivity.EXTRA_CHALLENGE,cfpc);
+        intent.putExtra(VCPassActivity.EXTRA_PROMPT_TEXT,
+                    getString(R.string.import_test));
+        Log.d("VCPass", intent.toString());
+        startActivityForResult(intent, REQ_PRESENT);
+    }
+
+    private static final class
+    VCPassImpDoneHandler
+    implements View.OnClickListener {
+        private EditText et;
+        private VCPassImport self;
+        VCPassImpDoneHandler(EditText et, VCPassImport self) {
+            this.et = et;
+            this.self = self;
+        }
+
+        @Override
+        public void onClick(View bv) {
+            self.haveImportedSeed(et.getText().toString());
+        }
+    }
+
+        // XXX
+    private String zxingscan = "com.google.zxing.client.android.SCAN";
+    private final void
+    importSeed() {
+        Log.d(DBGN, "Import seed...");
+
+        Intent intent = new Intent(zxingscan);
+        boolean isAvail = Utils.isIntentAvailable(this, intent);
+        if(TRY_QRCODE && isAvail) {
+            startActivityForResult(intent, REQ_IMPORT);
+        } else {
+            setContentView(R.layout.vcpimp);
+            EditText set = (EditText) findViewById(R.id.password);
+            Button donebtn = (Button) findViewById(R.id.impdone);
+            donebtn.setOnClickListener(
+                new VCPassImpDoneHandler(set, this));
+        }
+    }
+
+    private final boolean
+    haveImportedSeed(String seed) {
+        Log.d(DBGN, "Have imported seed...");
+
+        char[][] ds;
+        try {
+        ds = Utils.decode_seeds(seed);
+        if(ds[0] == null
+            || ds[1] == null
+            || ds[0].length == 0
+            || ds[1].length == 0)
+            return false;
+        } catch (Exception e) {
+            return false;
+        }
+
+        this.useed = ds[0];
+        this.vseed = ds[1];
+
+        if(getIntent().hasExtra(VCPassActivity.EXTRA_QUIET_OPERATION)) {
+            finishImportedSeed();
+        } else {
+            launchGenerator(REQ_CREATE_FOR_PRESENT, 1);
+        }
+
+        return true;
+    }
+
+    private final void
+    finishImportedSeed() {
+        Log.d(DBGN, "Finish imported seed...");
+        if( getIntent().getAction().equals(ACTION_IMPORT_AND_CREATE) ) {
+            launchGenerator(REQ_CREATE, 0);
+        } else {
+            finalResult(null,null);
+        }
+    }
+
+    private final void
+    finalResult(Parcelable chal, String secret) {
+        Log.d(DBGN, "Final result...");
+
+        Intent result = getIntent();
+
+        result.putExtra(VCPassActivity.EXTRA_USER_SLIDE_SEED, useed  );
+        result.putExtra(VCPassActivity.EXTRA_VOCABULARY_SEED, vseed  );
+        if(chal != null)
+            result.putExtra(VCPassActivity.EXTRA_CHALLENGE  , chal   );
+        if(secret != null)
+            result.putExtra(VCPassActivity.EXTRA_SECRET     , secret );
+        setResult(RESULT_OK, result);
+        finish();
+    }
+
+    public void onActivityResult(int req, int res, Intent data) {
+        if(res == RESULT_CANCELED && req != REQ_PRESENT) {
+           setResult(RESULT_CANCELED, getIntent());
+        }
+        if(data == null) {
+            // being canceled; report failure upstream
+            setResult(RESULT_CANCELED, null);
+            finish();
+            return;
+        }
+
+        switch(req) {
+            case REQ_IMPORT:
+                Log.d(DBGN, "Result IMPORT ...");
+                if(!haveImportedSeed(data.getStringExtra("SCAN_RESULT"))){
+                    // Something didn't work out; try again
+                    importSeed();
+                }
+                break;
+            case REQ_CREATE_FOR_PRESENT:
+                Log.d(DBGN, "Result CFP ...");
+                cfpc = (Bitmap) data.getParcelableExtra(
+                        VCPassActivity.EXTRA_CHALLENGE);
+                cfps = data.getStringExtra(VCPassActivity.EXTRA_SECRET);
+                launchChallenger();
+                break;
+            case REQ_PRESENT:
+                Log.d(DBGN, "Result P ...");
+                // go back and redo import; the user can cancel that
+                // if they really want to cancel.
+                if ( res == RESULT_CANCELED ) {
+                    importSeed();
+                } else {
+                    String sec = data.getStringExtra(
+                        VCPassActivity.EXTRA_SECRET);
+                    if(cfps.equals(sec)) {
+                        finishImportedSeed();
+                    } else {
+                        launchChallenger();
+                    }
+                }
+                break;
+            case REQ_CREATE:
+                Log.d(DBGN, "Result C ...");
+                finalResult(data.getParcelableExtra(
+                                VCPassActivity.EXTRA_CHALLENGE),
+                            data.getStringExtra(
+                                VCPassActivity.EXTRA_SECRET));
+                break;
+            default:
+                throw new RuntimeException("Impossible req!");
+        }
+    }
+
+    /* * * * * * Android interface core * * * * * */
+
+    @Override
+    public void onCreate(Bundle sis)
+    {
+        super.onCreate(sis);
+
+        Log.i(DBGN, "CREATE");
+
+        if(sis != null) {
+            useed =          sis.getCharArray (SAVED_STATE_USEED);
+            vseed =          sis.getCharArray (SAVED_STATE_VSEED);
+            cfpc  = (Bitmap) sis.getParcelable(SAVED_STATE_CFPC );
+            cfps  =          sis.getString    (SAVED_STATE_CFPS );
+        }
+
+        String act = getIntent().getAction();
+        Log.d(DBGN, act);
+
+        if (act.equals(ACTION_IMPORT_SEED)
+                || act.equals(ACTION_IMPORT_AND_CREATE)) {
+            importSeed();
+        } else {
+            setResult(RESULT_CANCELED, null);
+            finish();
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        outState.putCharArray (SAVED_STATE_USEED, useed);
+        outState.putCharArray (SAVED_STATE_VSEED, vseed);
+
+        outState.putParcelable(SAVED_STATE_CFPC , cfpc );
+        outState.putString    (SAVED_STATE_CFPS , cfps );
+    }
+
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..c491436
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- package name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="org.ietfng.ns.android.vcpass.tests"
+          android:versionCode="1"
+          android:versionName="1.0">
+    <!-- We add an application tag here just so that we can indicate that
+         this package needs to link against the android.test library,
+         which is needed when building test cases. -->
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <!--
+    This declares that this application uses the instrumentation test runner targeting
+    the package of org.ietfng.ns.android.vcpass.  To run the tests use the command:
+    "adb shell am instrument -w org.ietfng.ns.android.vcpass.tests/android.test.InstrumentationTestRunner"
+    -->
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="org.ietfng.ns.android.vcpass"
+                     android:label="Tests for VCPass"/>
+</manifest>
diff --git a/tests/build.properties b/tests/build.properties
new file mode 100644 (file)
index 0000000..a650485
--- /dev/null
@@ -0,0 +1,15 @@
+# This file is used to override default values used by the Ant build system.
+# 
+# This file must be checked in Version Control Systems, as it is
+# integral to the build system of your project.
+
+# The name of your application package as defined in the manifest.
+# Used by the 'uninstall' rule.
+#application-package=com.example.myproject
+
+# The name of the source folder.
+#source-folder=src
+
+# The name of the output folder.
+#out-folder=bin
+
diff --git a/tests/build.xml b/tests/build.xml
new file mode 100644 (file)
index 0000000..d813c2b
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="VCPass" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contain the path to the SDK. It should *NOT* be checked in in Version
+         Control Systems. -->
+    <property file="local.properties"/>
+
+    <!-- The build.properties file can be created by you and is never touched
+         by the 'android' tool. This is the place to change some of the default property values
+         used by the Ant rules.
+         Here are some properties you may want to change/update:
+
+         application-package
+             the name of your application package as defined in the manifest. Used by the
+             'uninstall' rule.
+         source-folder
+             the name of the source folder. Default is 'src'.
+         out-folder
+             the name of the output folder. Default is 'bin'.
+
+         Properties related to the SDK location or the project target should be updated
+          using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your application and
+         should be checked in in Version Control Systems.
+
+         -->
+    <property file="build.properties"/>
+
+    <!-- The default.properties file is created and updated by the 'android' tool, as well
+         as ADT. 
+         This file is an integral part of the build system for your application and
+         should be checked in in Version Control Systems. -->
+    <property file="default.properties"/>
+
+    <!-- Custom Android task to deal with the project target, and import the proper rules.
+         This requires ant 1.6.0 or above. -->
+    <path id="android.antlibs">
+        <pathelement path="${sdk-location}/tools/lib/anttasks.jar" />
+        <pathelement path="${sdk-location}/tools/lib/sdklib.jar" />
+        <pathelement path="${sdk-location}/tools/lib/androidprefs.jar" />
+        <pathelement path="${sdk-location}/tools/lib/apkbuilder.jar" />
+        <pathelement path="${sdk-location}/tools/lib/jarutils.jar" />
+    </path>
+
+    <taskdef name="setup"
+        classname="com.android.ant.SetupTask"
+        classpathref="android.antlibs"/>
+
+    <!-- Execute the Android Setup task that will setup some properties specific to the target,
+         and import the rules files.
+         To customize the rules, copy/paste them below the task, and disable import by setting
+         the import attribute to false:
+            <setup import="false" />
+         
+         This will ensure that the properties are setup correctly but that your customized
+         targets are used.
+    -->
+    <setup />
+</project>
diff --git a/tests/default.properties b/tests/default.properties
new file mode 100644 (file)
index 0000000..4513a1e
--- /dev/null
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+# 
+# This file must be checked in Version Control Systems.
+# 
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-3
diff --git a/tests/local.properties b/tests/local.properties
new file mode 100644 (file)
index 0000000..43c4169
--- /dev/null
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+# 
+# This file must *NOT* be checked in Version Control Systems,
+# as it contains information specific to your local configuration.
+
+sdk-location=/home/nwf/src/android/android-sdk-linux_x86-1.5_r3
+# location of the SDK. This is only used by Ant
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=/home/nwf/src/android/android-sdk-linux_86
diff --git a/tests/src/org/ietfng/ns/android/vcpass/VCPassTest.java b/tests/src/org/ietfng/ns/android/vcpass/VCPassTest.java
new file mode 100644 (file)
index 0000000..4b94b18
--- /dev/null
@@ -0,0 +1,21 @@
+package org.ietfng.ns.android.vcpass;
+
+import android.test.ActivityInstrumentationTestCase;
+
+/**
+ * This is a simple framework for a test of an Application.  See
+ * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
+ * how to write and extend Application tests.
+ * <p/>
+ * To run this test, you can type:
+ * adb shell am instrument -w \
+ * -e class org.ietfng.ns.android.vcpass.VCPassTest \
+ * org.ietfng.ns.android.vcpass.tests/android.test.InstrumentationTestRunner
+ */
+public class VCPassTest extends ActivityInstrumentationTestCase<VCPass> {
+
+    public VCPassTest() {
+        super("org.ietfng.ns.android.vcpass", VCPass.class);
+    }
+
+}