]> Pileus Git - ~andy/freeotp/commitdiff
Initial commit
authorNathaniel McCallum <npmccallum@redhat.com>
Wed, 3 Jul 2013 23:48:33 +0000 (19:48 -0400)
committerNathaniel McCallum <npmccallum@redhat.com>
Wed, 3 Jul 2013 23:48:33 +0000 (19:48 -0400)
35 files changed:
.classpath [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.project [new file with mode: 0644]
.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
AndroidManifest.xml [new file with mode: 0644]
ic_launcher-web.png [new file with mode: 0644]
logo.svg [new file with mode: 0644]
proguard-project.txt [new file with mode: 0644]
project.properties [new file with mode: 0644]
res/drawable-hdpi/delete.png [new file with mode: 0755]
res/drawable-hdpi/generate.png [new file with mode: 0755]
res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
res/drawable-hdpi/scan.png [new file with mode: 0644]
res/drawable-mdpi/delete.png [new file with mode: 0755]
res/drawable-mdpi/generate.png [new file with mode: 0755]
res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
res/drawable-mdpi/scan.png [new file with mode: 0644]
res/drawable-xhdpi/delete.png [new file with mode: 0755]
res/drawable-xhdpi/generate.png [new file with mode: 0755]
res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
res/drawable-xhdpi/scan.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_launcher.png [new file with mode: 0644]
res/layout/common.xml [new file with mode: 0644]
res/layout/hotp.xml [new file with mode: 0644]
res/layout/totp.xml [new file with mode: 0644]
res/menu/main.xml [new file with mode: 0644]
res/values-v11/styles.xml [new file with mode: 0644]
res/values-v14/styles.xml [new file with mode: 0644]
res/values/strings.xml [new file with mode: 0644]
res/values/styles.xml [new file with mode: 0644]
src/com/google/android/apps/authenticator/Base32String.java [new file with mode: 0644]
src/org/fedorahosted/freeotp/CircleProgressBar.java [new file with mode: 0644]
src/org/fedorahosted/freeotp/MainActivity.java [new file with mode: 0644]
src/org/fedorahosted/freeotp/Token.java [new file with mode: 0644]
src/org/fedorahosted/freeotp/TokenAdapter.java [new file with mode: 0644]

diff --git a/.classpath b/.classpath
new file mode 100644 (file)
index 0000000..7bc01d9
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="src" path="gen"/>
+       <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+       <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+       <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+       <classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..e614fbb
--- /dev/null
@@ -0,0 +1,2 @@
+bin
+gen
diff --git a/.project b/.project
new file mode 100644 (file)
index 0000000..7ddaa7b
--- /dev/null
+++ b/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>FreeOTP</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..b080d2d
--- /dev/null
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..5b597b4
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+   - FreeOTP
+   -
+   - Authors: Nathaniel McCallum <npmccallum@redhat.com>
+   -
+   - Copyright (C) 2013  Nathaniel McCallum, Red Hat
+   - see file 'COPYING' for use and warranty information
+   -
+   - This program is free software you can redistribute it and/or modify
+   - it under the terms of the GNU General Public License as published by
+   - the Free Software Foundation, either version 3 of the License, or
+   - (at your option) any later version.
+   -
+   - This program is distributed in the hope that it will be useful,
+   - but WITHOUT ANY WARRANTY; without even the implied warranty of
+   - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   - GNU General Public License for more details.
+   -
+   - You should have received a copy of the GNU General Public License
+   - along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.fedorahosted.freeotp"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="17" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/ic_launcher-web.png b/ic_launcher-web.png
new file mode 100644 (file)
index 0000000..3e38779
Binary files /dev/null and b/ic_launcher-web.png differ
diff --git a/logo.svg b/logo.svg
new file mode 100644 (file)
index 0000000..7b0b97e
--- /dev/null
+++ b/logo.svg
@@ -0,0 +1,770 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="72"
+   height="72"
+   id="svg2943"
+   version="1.1"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="logo.svg">
+  <defs
+     id="defs2945">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2951" />
+    <inkscape:perspective
+       id="perspective2951-9"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6009"
+       id="linearGradient2940"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.64005389,0,0,0.64005384,-77.553153,111.79559)"
+       x1="350.72498"
+       y1="-78.39135"
+       x2="350.72498"
+       y2="-136.74741" />
+    <linearGradient
+       id="linearGradient6009">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop6011" />
+      <stop
+         style="stop-color:#454d53;stop-opacity:1;"
+         offset="1"
+         id="stop6013" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6009"
+       id="linearGradient2959"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.64005389,0,0,0.64005384,-79.654473,160.04949)"
+       x1="350.72498"
+       y1="-78.39135"
+       x2="350.72498"
+       y2="-136.74741" />
+    <linearGradient
+       id="linearGradient2961">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop2963" />
+      <stop
+         style="stop-color:#454d53;stop-opacity:1;"
+         offset="1"
+         id="stop2965" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5870"
+       id="linearGradient2984"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(100,54)"
+       x1="119.5"
+       y1="107.36218"
+       x2="141.2543"
+       y2="135.61649" />
+    <linearGradient
+       id="linearGradient5870">
+      <stop
+         id="stop5872"
+         offset="0"
+         style="stop-color:#245ec9;stop-opacity:1;" />
+      <stop
+         id="stop5874"
+         offset="1"
+         style="stop-color:#4d80df;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5870"
+       id="linearGradient2982"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(48,14)"
+       x1="119.5"
+       y1="107.36218"
+       x2="141.2543"
+       y2="135.61649" />
+    <linearGradient
+       id="linearGradient2974">
+      <stop
+         id="stop2976"
+         offset="0"
+         style="stop-color:#245ec9;stop-opacity:1;" />
+      <stop
+         id="stop2978"
+         offset="1"
+         style="stop-color:#4d80df;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient2950"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.26089371,0,0,0.26089372,16.237266,37.992996)"
+       x1="89.802315"
+       y1="-127.68279"
+       x2="86.001991"
+       y2="-26.66445" />
+    <linearGradient
+       id="linearGradient5660">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop5662" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop5664" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5726"
+       id="linearGradient2952"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.26089371,0,0,0.26089372,16.237266,37.992996)"
+       x1="51.007153"
+       y1="-18.21809"
+       x2="58.08173"
+       y2="-114.41708" />
+    <linearGradient
+       id="linearGradient5726">
+      <stop
+         style="stop-color:#789820;stop-opacity:1;"
+         offset="0"
+         id="stop5728" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="1"
+         id="stop5730" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient2992"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.26089371,0,0,0.26089372,82.590234,74.897428)"
+       x1="89.802315"
+       y1="-127.68279"
+       x2="86.001991"
+       y2="-26.66445" />
+    <linearGradient
+       id="linearGradient2994">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop2996" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop2998" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5726"
+       id="linearGradient3000"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.26089371,0,0,0.26089372,82.590234,74.897428)"
+       x1="51.007153"
+       y1="-18.21809"
+       x2="58.08173"
+       y2="-114.41708" />
+    <linearGradient
+       id="linearGradient3002">
+      <stop
+         style="stop-color:#789820;stop-opacity:1;"
+         offset="0"
+         id="stop3004" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="1"
+         id="stop3006" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3008"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.26089371,0,0,0.26089372,82.590234,74.897428)"
+       x1="287.41901"
+       y1="-119.63873"
+       x2="287.41901"
+       y2="-60.427364" />
+    <linearGradient
+       id="linearGradient3010">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3012" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3014" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3016"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.26089371,0,0,0.26089372,82.590234,74.897428)"
+       x1="287.41901"
+       y1="-119.63873"
+       x2="287.41901"
+       y2="-60.427364" />
+    <linearGradient
+       id="linearGradient3018">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3020" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3022" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient2965"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,18.774357,102.24507)"
+       x1="547.77307"
+       y1="-117.08842"
+       x2="547.77307"
+       y2="-57.556679" />
+    <linearGradient
+       id="linearGradient3025">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3027" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3029" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3031"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,83.042016,222.9555)"
+       x1="547.77307"
+       y1="-117.08842"
+       x2="547.77307"
+       y2="-57.556679" />
+    <linearGradient
+       id="linearGradient3033">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3035" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3037" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient2971"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,18.774357,102.24507)"
+       x1="188.02406"
+       y1="-102.96148"
+       x2="192.05518"
+       y2="-53.5322" />
+    <linearGradient
+       id="linearGradient3040">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3042" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3044" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5726"
+       id="linearGradient2973"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,18.774357,102.24507)"
+       x1="206.34718"
+       y1="-71.272896"
+       x2="146.60516"
+       y2="-76.515343" />
+    <linearGradient
+       id="linearGradient3047">
+      <stop
+         style="stop-color:#789820;stop-opacity:1;"
+         offset="0"
+         id="stop3049" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="1"
+         id="stop3051" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3053"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,83.042016,222.9555)"
+       x1="188.02406"
+       y1="-102.96148"
+       x2="192.05518"
+       y2="-53.5322" />
+    <linearGradient
+       id="linearGradient3055">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3057" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3059" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5726"
+       id="linearGradient3061"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,83.042016,222.9555)"
+       x1="206.34718"
+       y1="-71.272896"
+       x2="146.60516"
+       y2="-76.515343" />
+    <linearGradient
+       id="linearGradient3063">
+      <stop
+         style="stop-color:#789820;stop-opacity:1;"
+         offset="0"
+         id="stop3065" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="1"
+         id="stop3067" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient2979"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.25774657,0,0,0.25774657,-19.407445,94.666979)"
+       x1="129.79414"
+       y1="-232.97458"
+       x2="115.98241"
+       y2="-147.2603" />
+    <linearGradient
+       id="linearGradient3072">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3074" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3076" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3078"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.25774657,0,0,0.25774657,-54.297423,211.30463)"
+       x1="129.79414"
+       y1="-232.97458"
+       x2="115.98241"
+       y2="-147.2603" />
+    <linearGradient
+       id="linearGradient3080">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3082" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3084" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient2966"
+       gradientUnits="userSpaceOnUse"
+       x1="287.41901"
+       y1="-119.63873"
+       x2="287.41901"
+       y2="-60.427364" />
+    <linearGradient
+       id="linearGradient3087">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3089" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3091" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3095"
+       gradientUnits="userSpaceOnUse"
+       x1="287.41901"
+       y1="-119.63873"
+       x2="287.41901"
+       y2="-60.427364" />
+    <linearGradient
+       id="linearGradient3097">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3099" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3101" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient2985"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.25774657,0,0,0.25774657,-19.407445,94.666979)"
+       x1="117.72675"
+       y1="-220.81551"
+       x2="117.50276"
+       y2="-155.6909" />
+    <linearGradient
+       id="linearGradient3104">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3106" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3108" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5726"
+       id="linearGradient2987"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.25774657,0,0,0.25774657,-19.407445,94.666979)"
+       x1="176.11484"
+       y1="-120.64497"
+       x2="175.11197"
+       y2="-236.80659" />
+    <linearGradient
+       id="linearGradient3111">
+      <stop
+         style="stop-color:#789820;stop-opacity:1;"
+         offset="0"
+         id="stop3113" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="1"
+         id="stop3115" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3117"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.25774657,0,0,0.25774657,-54.297423,211.30463)"
+       x1="117.72675"
+       y1="-220.81551"
+       x2="117.50276"
+       y2="-155.6909" />
+    <linearGradient
+       id="linearGradient3119">
+      <stop
+         style="stop-color:#dbeda9;stop-opacity:1;"
+         offset="0"
+         id="stop3121" />
+      <stop
+         style="stop-color:#afd642;stop-opacity:1;"
+         offset="1"
+         id="stop3123" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5726"
+       id="linearGradient3125"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.25774657,0,0,0.25774657,-54.297423,211.30463)"
+       x1="176.11484"
+       y1="-120.64497"
+       x2="175.11197"
+       y2="-236.80659" />
+    <linearGradient
+       id="linearGradient3127">
+      <stop
+         style="stop-color:#789820;stop-opacity:1;"
+         offset="0"
+         id="stop3129" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="1"
+         id="stop3131" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5708"
+       id="linearGradient2960"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.64153166,0,0,0.63857951,-9.9499733,-49.720458)"
+       x1="111.01576"
+       y1="118.27412"
+       x2="74.637543"
+       y2="97.271141" />
+    <linearGradient
+       id="linearGradient5708">
+      <stop
+         style="stop-color:#588ddb;stop-opacity:1;"
+         offset="0"
+         id="stop5710" />
+      <stop
+         style="stop-color:#97b9e9;stop-opacity:1;"
+         offset="1"
+         id="stop5712" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5700"
+       id="linearGradient2958"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.64153166,0,0,0.63857951,-9.9499733,-49.720458)"
+       x1="82.731491"
+       y1="157.25667"
+       x2="82.731491"
+       y2="122.979" />
+    <linearGradient
+       id="linearGradient5700">
+      <stop
+         style="stop-color:#1f54b4;stop-opacity:1;"
+         offset="0"
+         id="stop5702" />
+      <stop
+         style="stop-color:#153a7e;stop-opacity:1;"
+         offset="1"
+         id="stop5704" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5716"
+       id="linearGradient2956"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.64153166,0,0,0.63857951,54.45002,35.241715)"
+       x1="113.5061"
+       y1="120.99213"
+       x2="125.23831"
+       y2="164.77731" />
+    <linearGradient
+       id="linearGradient5716">
+      <stop
+         style="stop-color:#245ec9;stop-opacity:1;"
+         offset="0"
+         id="stop5718" />
+      <stop
+         style="stop-color:#4d80df;stop-opacity:1;"
+         offset="1"
+         id="stop5720" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3774"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,261.71674,518.72103)"
+       x1="188.02406"
+       y1="-102.96148"
+       x2="192.05518"
+       y2="-53.5322" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5726"
+       id="linearGradient3776"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,261.71674,518.72103)"
+       x1="206.34718"
+       y1="-71.272896"
+       x2="146.60516"
+       y2="-76.515343" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3778"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.27793206,0,0,0.27793205,280.58161,503.92051)"
+       x1="547.77307"
+       y1="-117.08842"
+       x2="547.77307"
+       y2="-57.556679" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient6009"
+       id="linearGradient3780"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.64005389,0,0,0.64005384,170.88578,260.40021)"
+       x1="350.72498"
+       y1="-78.39135"
+       x2="350.72498"
+       y2="-136.74741" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3782"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.26089371,0,0,0.26089372,266.86858,138.53854)"
+       x1="89.802315"
+       y1="-127.68279"
+       x2="86.001991"
+       y2="-26.66445" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5726"
+       id="linearGradient3784"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.26089371,0,0,0.26089372,266.86858,138.53854)"
+       x1="51.007153"
+       y1="-18.21809"
+       x2="58.08173"
+       y2="-114.41708" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5660"
+       id="linearGradient3788"
+       gradientUnits="userSpaceOnUse"
+       x1="287.41901"
+       y1="-119.63873"
+       x2="287.41901"
+       y2="-60.427364" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="4"
+     inkscape:cx="96.7096"
+     inkscape:cy="12.631905"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1623"
+     inkscape:window-height="1006"
+     inkscape:window-x="57"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <metadata
+     id="metadata2948">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-243.16607,-292.62799)">
+    <g
+       id="g3154"
+       transform="translate(-1,-1)">
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc"
+         id="rect3205"
+         d="m 280.07076,329.50612 31.43505,-16.60306 0,35.12187 -31.43505,16.60306 0,-35.12187 z"
+         style="fill:#4d80bf;fill-opacity:1;stroke:none" />
+      <path
+         inkscape:connector-curvature="0"
+         style="fill:#1f54b3;fill-opacity:1;stroke:none"
+         d="m 280.03256,329.59451 -30.67075,-19.15738 0,35.21026 30.67075,18.9806 0,-35.03348 z"
+         id="path3208"
+         sodipodi:nodetypes="ccccc" />
+      <path
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc"
+         id="path3210"
+         d="m 311.32903,313.01131 -30.41041,-18.45027 -31.75255,15.96448 31.1175,19.15738 31.04546,-16.67159 z"
+         style="fill:#7da7e2;fill-opacity:1;stroke:none" />
+      <text
+         transform="matrix(0.94398009,-0.56576173,0.8345113,0.55919129,0,0)"
+         id="use5724"
+         y="464.11057"
+         x="-110.0864"
+         style="font-size:33.31528473px;fill:#b9db58;fill-opacity:1;stroke:#d8e1be;stroke-width:0.63999999;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         xml:space="preserve"><tspan
+           style="font-weight:bold;letter-spacing:-1.60451317;fill:#b9db58;fill-opacity:1;stroke:#d8e1be;stroke-width:0.63999999;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Liberation Sans"
+           y="464.11057"
+           x="-110.0864"
+           id="tspan2929"
+           sodipodi:role="line">t</tspan></text>
+      <text
+         transform="matrix(1.0020591,-0.55238064,0,0.99794513,0,0)"
+         xml:space="preserve"
+         style="font-size:35.92438889px;fill:#b3d84c;fill-opacity:1;stroke:#89a53f;stroke-width:0.63999999;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none"
+         x="284.6636"
+         y="509.64584"
+         id="use5784"><tspan
+           sodipodi:role="line"
+           id="tspan2947"
+           x="284.6636"
+           y="509.64584"
+           style="font-weight:bold;letter-spacing:-1.73017108;fill:#b3d84c;fill-opacity:1;stroke:#89a53f;stroke-width:0.63999999;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none;font-family:Liberation Sans">p</tspan></text>
+      <text
+         transform="matrix(0.97056688,0.70740751,0,1.0303257,0,0)"
+         xml:space="preserve"
+         style="font-size:33.7220726px;fill:#badc5b;fill-opacity:1;stroke:#3e664e;stroke-width:0.64005393;stroke-opacity:1"
+         x="261.45175"
+         y="149.85794"
+         id="use5762"><tspan
+           sodipodi:role="line"
+           id="tspan2973"
+           x="261.45175"
+           y="149.85794"
+           style="font-weight:bold;letter-spacing:-1.62410462;fill:#badc5b;fill-opacity:1;stroke:#3e664e;stroke-width:0.64005393;stroke-opacity:1;font-family:Liberation Sans">o</tspan></text>
+    </g>
+  </g>
+</svg>
diff --git a/proguard-project.txt b/proguard-project.txt
new file mode 100644 (file)
index 0000000..f2fe155
--- /dev/null
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/project.properties b/project.properties
new file mode 100644 (file)
index 0000000..a3ee5ab
--- /dev/null
@@ -0,0 +1,14 @@
+# 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 edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
diff --git a/res/drawable-hdpi/delete.png b/res/drawable-hdpi/delete.png
new file mode 100755 (executable)
index 0000000..e9ce89e
Binary files /dev/null and b/res/drawable-hdpi/delete.png differ
diff --git a/res/drawable-hdpi/generate.png b/res/drawable-hdpi/generate.png
new file mode 100755 (executable)
index 0000000..0f3ceec
Binary files /dev/null and b/res/drawable-hdpi/generate.png differ
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..0d933fa
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ
diff --git a/res/drawable-hdpi/scan.png b/res/drawable-hdpi/scan.png
new file mode 100644 (file)
index 0000000..593b163
Binary files /dev/null and b/res/drawable-hdpi/scan.png differ
diff --git a/res/drawable-mdpi/delete.png b/res/drawable-mdpi/delete.png
new file mode 100755 (executable)
index 0000000..cedb108
Binary files /dev/null and b/res/drawable-mdpi/delete.png differ
diff --git a/res/drawable-mdpi/generate.png b/res/drawable-mdpi/generate.png
new file mode 100755 (executable)
index 0000000..ca28ba3
Binary files /dev/null and b/res/drawable-mdpi/generate.png differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..c6efecd
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ
diff --git a/res/drawable-mdpi/scan.png b/res/drawable-mdpi/scan.png
new file mode 100644 (file)
index 0000000..aae6a95
Binary files /dev/null and b/res/drawable-mdpi/scan.png differ
diff --git a/res/drawable-xhdpi/delete.png b/res/drawable-xhdpi/delete.png
new file mode 100755 (executable)
index 0000000..98c73da
Binary files /dev/null and b/res/drawable-xhdpi/delete.png differ
diff --git a/res/drawable-xhdpi/generate.png b/res/drawable-xhdpi/generate.png
new file mode 100755 (executable)
index 0000000..a80b9ab
Binary files /dev/null and b/res/drawable-xhdpi/generate.png differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..da5ba8c
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/res/drawable-xhdpi/scan.png b/res/drawable-xhdpi/scan.png
new file mode 100644 (file)
index 0000000..e61a09f
Binary files /dev/null and b/res/drawable-xhdpi/scan.png differ
diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..d01e9b6
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/res/layout/common.xml b/res/layout/common.xml
new file mode 100644 (file)
index 0000000..d586648
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+   - FreeOTP
+   -
+   - Authors: Nathaniel McCallum <npmccallum@redhat.com>
+   -
+   - Copyright (C) 2013  Nathaniel McCallum, Red Hat
+   - see file 'COPYING' for use and warranty information
+   -
+   - This program is free software you can redistribute it and/or modify
+   - it under the terms of the GNU General Public License as published by
+   - the Free Software Foundation, either version 3 of the License, or
+   - (at your option) any later version.
+   -
+   - This program is distributed in the hope that it will be useful,
+   - but WITHOUT ANY WARRANTY; without even the implied warranty of
+   - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   - GNU General Public License for more details.
+   -
+   - You should have received a copy of the GNU General Public License
+   - along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="72dp"
+    android:layout_weight="1"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="26dp"
+        android:gravity="center_vertical|left"
+        android:paddingTop="4dp"
+        android:paddingLeft="8dp"
+        android:text="Example: example@example.com"
+        android:textSize="14sp" />
+    
+    <LinearLayout
+           android:layout_width="match_parent"
+           android:layout_height="46dp"
+           android:orientation="horizontal" >
+           <ImageButton
+               android:id="@+id/button"
+            android:layout_width="46dp"
+            android:layout_height="46dp"
+            android:layout_marginLeft="8dp"
+            android:src="@drawable/delete"
+            style="?android:attr/borderlessButtonStyle"
+            android:layout_gravity="center" />
+           
+           <TextView
+               android:id="@+id/code"
+               android:layout_width="match_parent"
+               android:layout_height="46dp"
+               android:gravity="center_vertical|left"
+               android:padding="0dp"
+               android:text="00000000"
+               android:textSize="34sp"
+               android:typeface="monospace" />
+       </LinearLayout>
+    
+</LinearLayout>
diff --git a/res/layout/hotp.xml b/res/layout/hotp.xml
new file mode 100644 (file)
index 0000000..41c8745
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+   - FreeOTP
+   -
+   - Authors: Nathaniel McCallum <npmccallum@redhat.com>
+   -
+   - Copyright (C) 2013  Nathaniel McCallum, Red Hat
+   - see file 'COPYING' for use and warranty information
+   -
+   - This program is free software you can redistribute it and/or modify
+   - it under the terms of the GNU General Public License as published by
+   - the Free Software Foundation, either version 3 of the License, or
+   - (at your option) any later version.
+   -
+   - This program is distributed in the hope that it will be useful,
+   - but WITHOUT ANY WARRANTY; without even the implied warranty of
+   - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   - GNU General Public License for more details.
+   -
+   - You should have received a copy of the GNU General Public License
+   - along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="8dp"
+    android:orientation="horizontal" >
+
+    <include layout="@layout/common" />
+
+    <View
+        android:layout_width="1dp"
+        android:layout_height="72dp"
+        android:paddingTop="8dp"
+        android:paddingBottom="4dp"
+        android:background="?android:attr/dividerVertical"
+        />
+    
+    <ImageButton
+        android:id="@+id/hotpButton"
+        android:layout_width="72dp"
+        android:layout_height="72dp"
+        android:padding="8dp"
+        android:src="@drawable/generate"
+        style="?android:attr/borderlessButtonStyle"
+        />
+</LinearLayout>
diff --git a/res/layout/totp.xml b/res/layout/totp.xml
new file mode 100644 (file)
index 0000000..da8dfcf
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+   - FreeOTP
+   -
+   - Authors: Nathaniel McCallum <npmccallum@redhat.com>
+   -
+   - Copyright (C) 2013  Nathaniel McCallum, Red Hat
+   - see file 'COPYING' for use and warranty information
+   -
+   - This program is free software you can redistribute it and/or modify
+   - it under the terms of the GNU General Public License as published by
+   - the Free Software Foundation, either version 3 of the License, or
+   - (at your option) any later version.
+   -
+   - This program is distributed in the hope that it will be useful,
+   - but WITHOUT ANY WARRANTY; without even the implied warranty of
+   - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   - GNU General Public License for more details.
+   -
+   - You should have received a copy of the GNU General Public License
+   - along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+  
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="8dp"
+    android:orientation="horizontal" >
+
+    <include layout="@layout/common" />
+    
+    <View
+        android:layout_width="1dp"
+        android:layout_height="72dp"
+        android:paddingTop="8dp"
+        android:paddingBottom="4dp"
+        android:background="?android:attr/dividerVertical"
+        />
+
+    <org.fedorahosted.freeotp.CircleProgressBar
+        android:id="@+id/totpProgressBar"
+        android:layout_width="72dp"
+        android:layout_height="72dp"
+        android:padding="24dp"
+        android:max="1000"
+        style="@android:style/Widget.ProgressBar.Horizontal"
+        />
+</LinearLayout>
diff --git a/res/menu/main.xml b/res/menu/main.xml
new file mode 100644 (file)
index 0000000..cbe751c
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+   - FreeOTP
+   -
+   - Authors: Nathaniel McCallum <npmccallum@redhat.com>
+   -
+   - Copyright (C) 2013  Nathaniel McCallum, Red Hat
+   - see file 'COPYING' for use and warranty information
+   -
+   - This program is free software you can redistribute it and/or modify
+   - it under the terms of the GNU General Public License as published by
+   - the Free Software Foundation, either version 3 of the License, or
+   - (at your option) any later version.
+   -
+   - This program is distributed in the hope that it will be useful,
+   - but WITHOUT ANY WARRANTY; without even the implied warranty of
+   - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   - GNU General Public License for more details.
+   -
+   - You should have received a copy of the GNU General Public License
+   - along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/action_add"
+        android:orderInCategory="100"
+        android:showAsAction="always"
+        android:icon="@drawable/scan"
+        android:title="@string/action_add" />
+</menu>
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
new file mode 100644 (file)
index 0000000..3c02242
--- /dev/null
@@ -0,0 +1,11 @@
+<resources>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
new file mode 100644 (file)
index 0000000..a91fd03
--- /dev/null
@@ -0,0 +1,12 @@
+<resources>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644 (file)
index 0000000..886d7d9
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">FreeOTP</string>
+    <string name="action_add">Add</string>
+    <string name="token_scan_invalid">The scanned token data was invalid!</string>
+    <string name="delete_message">Are you sure you want to remove this token?\n\nNOTE: This will NOT disable two-factor authentication on the server.\n\n</string>
+    <string name="delete">Delete</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644 (file)
index 0000000..6ce89c7
--- /dev/null
@@ -0,0 +1,20 @@
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
diff --git a/src/com/google/android/apps/authenticator/Base32String.java b/src/com/google/android/apps/authenticator/Base32String.java
new file mode 100644 (file)
index 0000000..d5d5531
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2009 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.apps.authenticator;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * Encodes arbitrary byte arrays as case-insensitive base-32 strings.
+ * <p>
+ * The implementation is slightly different than in RFC 4648. During encoding,
+ * padding is not added, and during decoding the last incomplete chunk is not
+ * taken into account. The result is that multiple strings decode to the same
+ * byte array, for example, string of sixteen 7s ("7...7") and seventeen 7s both
+ * decode to the same byte array.
+ * TODO(sarvar): Revisit this encoding and whether this ambiguity needs fixing.
+ *
+ * @author sweis@google.com (Steve Weis)
+ * @author Neal Gafter
+ */
+public class Base32String {
+  // singleton
+
+  private static final Base32String INSTANCE =
+    new Base32String("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"); // RFC 4648/3548
+
+  static Base32String getInstance() {
+    return INSTANCE;
+  }
+
+  // 32 alpha-numeric characters.
+  private String ALPHABET;
+  private char[] DIGITS;
+  private int MASK;
+  private int SHIFT;
+  private HashMap<Character, Integer> CHAR_MAP;
+
+  static final String SEPARATOR = "-";
+
+  protected Base32String(String alphabet) {
+    this.ALPHABET = alphabet;
+    DIGITS = ALPHABET.toCharArray();
+    MASK = DIGITS.length - 1;
+    SHIFT = Integer.numberOfTrailingZeros(DIGITS.length);
+    CHAR_MAP = new HashMap<Character, Integer>();
+    for (int i = 0; i < DIGITS.length; i++) {
+      CHAR_MAP.put(DIGITS[i], i);
+    }
+  }
+
+  public static byte[] decode(String encoded) throws DecodingException {
+    return getInstance().decodeInternal(encoded);
+  }
+
+  protected byte[] decodeInternal(String encoded) throws DecodingException {
+    // Remove whitespace and separators
+    encoded = encoded.trim().replaceAll(SEPARATOR, "").replaceAll(" ", "");
+
+    // Remove padding. Note: the padding is used as hint to determine how many
+    // bits to decode from the last incomplete chunk (which is commented out
+    // below, so this may have been wrong to start with).
+    encoded = encoded.replaceFirst("[=]*$", "");
+
+    // Canonicalize to all upper case
+    encoded = encoded.toUpperCase(Locale.US);
+    if (encoded.length() == 0) {
+      return new byte[0];
+    }
+    int encodedLength = encoded.length();
+    int outLength = encodedLength * SHIFT / 8;
+    byte[] result = new byte[outLength];
+    int buffer = 0;
+    int next = 0;
+    int bitsLeft = 0;
+    for (char c : encoded.toCharArray()) {
+      if (!CHAR_MAP.containsKey(c)) {
+        throw new DecodingException("Illegal character: " + c);
+      }
+      buffer <<= SHIFT;
+      buffer |= CHAR_MAP.get(c) & MASK;
+      bitsLeft += SHIFT;
+      if (bitsLeft >= 8) {
+        result[next++] = (byte) (buffer >> (bitsLeft - 8));
+        bitsLeft -= 8;
+      }
+    }
+    // We'll ignore leftover bits for now.
+    //
+    // if (next != outLength || bitsLeft >= SHIFT) {
+    //  throw new DecodingException("Bits left: " + bitsLeft);
+    // }
+    return result;
+  }
+
+  public static String encode(byte[] data) {
+    return getInstance().encodeInternal(data);
+  }
+
+  protected String encodeInternal(byte[] data) {
+    if (data.length == 0) {
+      return "";
+    }
+
+    // SHIFT is the number of bits per output character, so the length of the
+    // output is the length of the input multiplied by 8/SHIFT, rounded up.
+    if (data.length >= (1 << 28)) {
+      // The computation below will fail, so don't do it.
+      throw new IllegalArgumentException();
+    }
+
+    int outputLength = (data.length * 8 + SHIFT - 1) / SHIFT;
+    StringBuilder result = new StringBuilder(outputLength);
+
+    int buffer = data[0];
+    int next = 1;
+    int bitsLeft = 8;
+    while (bitsLeft > 0 || next < data.length) {
+      if (bitsLeft < SHIFT) {
+        if (next < data.length) {
+          buffer <<= 8;
+          buffer |= (data[next++] & 0xff);
+          bitsLeft += 8;
+        } else {
+          int pad = SHIFT - bitsLeft;
+          buffer <<= pad;
+          bitsLeft += pad;
+        }
+      }
+      int index = MASK & (buffer >> (bitsLeft - SHIFT));
+      bitsLeft -= SHIFT;
+      result.append(DIGITS[index]);
+    }
+    return result.toString();
+  }
+
+  @Override
+  // enforce that this class is a singleton
+  public Object clone() throws CloneNotSupportedException {
+    throw new CloneNotSupportedException();
+  }
+
+  public static class DecodingException extends Exception {
+    public DecodingException(String message) {
+      super(message);
+    }
+  }
+}
diff --git a/src/org/fedorahosted/freeotp/CircleProgressBar.java b/src/org/fedorahosted/freeotp/CircleProgressBar.java
new file mode 100644 (file)
index 0000000..0734e40
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * FreeOTP
+ *
+ * Authors: Nathaniel McCallum <npmccallum@redhat.com>
+ *
+ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.fedorahosted.freeotp;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.widget.ProgressBar;
+
+public class CircleProgressBar extends ProgressBar {
+       private Paint paint;
+       private RectF rectf;
+       private Rect rect;
+
+       public CircleProgressBar(Context context, AttributeSet attrs, int defStyle) {
+               super(context, attrs, defStyle);
+               setup();
+       }
+
+       public CircleProgressBar(Context context, AttributeSet attrs) {
+               super(context, attrs);
+               setup();
+       }
+
+       public CircleProgressBar(Context context) {
+               super(context);
+               setup();
+       }
+       
+       private void setup() {
+               paint = new Paint();
+        rectf = new RectF();
+        rect = new Rect();
+        
+        paint.setColor(0x33333300);
+        paint.setAlpha(0x99);
+        paint.setAntiAlias(true);
+        paint.setStyle(Style.FILL_AND_STROKE);
+       }
+
+       @Override
+       protected synchronized void onDraw(Canvas canvas) {
+               getDrawingRect(rect);
+               rect.left += getPaddingLeft() + 2;
+               rect.top += getPaddingTop() + 2;
+               rect.right -= getPaddingRight() + 2;
+               rect.bottom -= getPaddingBottom() + 2;
+               
+               rectf.set(rect);
+               canvas.drawArc(rectf, -90, getProgress() * 360 / getMax(), true, paint);
+       }
+}
diff --git a/src/org/fedorahosted/freeotp/MainActivity.java b/src/org/fedorahosted/freeotp/MainActivity.java
new file mode 100644 (file)
index 0000000..cdd9a58
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * FreeOTP
+ *
+ * Authors: Nathaniel McCallum <npmccallum@redhat.com>
+ *
+ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.fedorahosted.freeotp;
+
+import java.security.NoSuchAlgorithmException;
+
+import org.fedorahosted.freeotp.Token.TokenUriInvalidException;
+
+import android.os.Bundle;
+import android.app.ListActivity;
+import android.content.Intent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.Toast;
+
+public class MainActivity extends ListActivity {
+       private TokenAdapter ta;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        ta = new TokenAdapter(this);
+        setListAdapter(ta);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.main, menu);
+        
+        menu.findItem(R.id.action_add).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                       public boolean onMenuItemClick(MenuItem item) {
+                               Intent i = new Intent("com.google.zxing.client.android.SCAN");
+                       i.putExtra("SCAN_MODE", "QR_CODE_MODE");
+                       i.putExtra("SAVE_HISTORY", false);
+                       startActivityForResult(i, 0);
+                               return false;
+                       }
+               });
+        
+        return true;
+    }
+    
+    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        if (resultCode == RESULT_OK) {
+            try {
+                               ta.add(this, intent.getStringExtra("SCAN_RESULT"));
+                       } catch (NoSuchAlgorithmException e) {
+                               Toast.makeText(this, R.string.token_scan_invalid, Toast.LENGTH_SHORT).show();
+                               e.printStackTrace();
+                       } catch (TokenUriInvalidException e) {
+                               Toast.makeText(this, R.string.token_scan_invalid, Toast.LENGTH_SHORT).show();
+                               e.printStackTrace();
+                       }
+        }
+    }
+}
diff --git a/src/org/fedorahosted/freeotp/Token.java b/src/org/fedorahosted/freeotp/Token.java
new file mode 100644 (file)
index 0000000..49de118
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * FreeOTP
+ *
+ * Authors: Nathaniel McCallum <npmccallum@redhat.com>
+ *
+ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.fedorahosted.freeotp;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import com.google.android.apps.authenticator.Base32String;
+import com.google.android.apps.authenticator.Base32String.DecodingException;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+
+public class Token {
+       public static class TokenUriInvalidException extends Exception {
+               private static final long serialVersionUID = -1108624734612362345L;
+       }
+       
+       public static enum TokenType {
+               HOTP, TOTP
+       }
+       
+       private String issuerInt;
+       private String issuerExt;
+       private String label;
+       private TokenType type;
+       private String algo;
+       private byte[] key;
+       private int digits;
+       private long counter;
+       private int period;
+       
+       public static List<Token> getTokens(Context ctx) {
+               SharedPreferences prefs = ctx.getSharedPreferences(Token.class.getName(), Context.MODE_PRIVATE);
+               
+               List<Token> tokens = new ArrayList<Token>();
+               for (String key : prefs.getAll().keySet()) {
+                       try {
+                               tokens.add(new Token(prefs.getString(key, null)));
+                       } catch (TokenUriInvalidException e) {
+                               e.printStackTrace();
+                       } catch (NoSuchAlgorithmException e) {
+                               e.printStackTrace();
+                       }
+               }
+               
+               return tokens;
+       }
+
+       private Token(Uri uri) throws TokenUriInvalidException, NoSuchAlgorithmException {
+               if (!uri.getScheme().equals("otpauth"))
+                       throw new TokenUriInvalidException();
+               
+               if (uri.getAuthority().equals("totp"))
+                       type = TokenType.TOTP;
+               else if (uri.getAuthority().equals("hotp"))
+                       type = TokenType.HOTP;
+               else
+                       throw new TokenUriInvalidException();
+               
+               String path = uri.getPath();
+               if (path == null)
+                       throw new TokenUriInvalidException();
+
+               // Strip the path of its leading '/'
+               for (int i = 0; path.charAt(i) == '/'; i++)
+                       path = path.substring(1);
+               if (path.length() == 0)
+                       throw new TokenUriInvalidException();
+               
+               int i = path.indexOf(':');
+               issuerExt = i < 0 ? "" : path.substring(0, i);
+               issuerInt = uri.getQueryParameter("issuer");
+               label = path.substring(i >= 0 ? i + 1 : 0);
+               
+               algo = uri.getQueryParameter("algorithm");
+               if (algo == null)
+                       algo = "sha1";
+               algo = algo.toUpperCase(Locale.US);
+               if (!algo.equals("SHA1") && !algo.equals("SHA256") &&
+            !algo.equals("SHA512") && !algo.equals("MD5"))
+                       throw new TokenUriInvalidException();
+               Mac.getInstance("Hmac" + algo);
+               
+               try {
+                       String d = uri.getQueryParameter("digits");
+                       if (d == null)
+                               d = "6";
+                       digits = Integer.parseInt(d);
+                       if (digits != 6 && digits != 8)
+                               throw new TokenUriInvalidException();
+               } catch (NumberFormatException e) {
+                       throw new TokenUriInvalidException();
+               }
+               
+               switch (type) {
+               case HOTP:
+                       try {
+                               String c = uri.getQueryParameter("counter");
+                               if (c == null)
+                                       c = "0";
+                               counter = Long.parseLong(c);
+                       } catch (NumberFormatException e) {
+                               throw new TokenUriInvalidException();
+                       }
+                       break;
+               case TOTP:
+                       try {
+                               String p = uri.getQueryParameter("period");
+                               if (p == null)
+                                       p = "30";
+                               period = Integer.parseInt(p);
+                       } catch (NumberFormatException e) {
+                               throw new TokenUriInvalidException();
+                       }
+                       break;
+               }
+               
+               try {
+                       String s = uri.getQueryParameter("secret");
+                       key = Base32String.decode(s);
+               } catch (DecodingException e) {
+                       throw new TokenUriInvalidException();
+               }
+       }
+       
+       private String getHOTP(long counter) {
+               // Encode counter in network byte order
+               ByteBuffer bb = ByteBuffer.allocate(8);
+               bb.putLong(counter);
+               
+               // Create digits divisor
+               int div = 1;
+               for (int i = digits; i > 0; i--)
+                       div *= 10;
+               
+               // Create the HMAC
+               try {
+                       Mac mac = Mac.getInstance("Hmac" + algo);
+                       mac.init(new SecretKeySpec(key, "Hmac" + algo));
+                       
+                       // Do the hashing
+                       byte[] digest = mac.doFinal(bb.array());
+                       
+                       // Truncate
+                       int binary;
+                       int off = digest[digest.length - 1] & 0xf;
+                       binary  = (digest[off + 0] & 0x7f) << 0x18;
+                       binary |= (digest[off + 1] & 0xff) << 0x10;
+                       binary |= (digest[off + 2] & 0xff) << 0x08;
+                       binary |= (digest[off + 3] & 0xff) << 0x00;
+                       binary  = binary % div;
+                       
+                       // Zero pad
+                       String hotp = Integer.toString(binary);
+                       while (hotp.length() != digits)
+                               hotp = "0" + hotp;
+                       
+                       return hotp;
+               } catch (InvalidKeyException e) {
+                       e.printStackTrace();
+               } catch (NoSuchAlgorithmException e) {
+                       e.printStackTrace();
+               }
+
+               return "";
+       }
+       
+       public Token(String uri) throws TokenUriInvalidException, NoSuchAlgorithmException {
+               this(Uri.parse(uri));
+       }
+       
+       private String getId() {
+               String id;
+               if (issuerInt != null && !issuerInt.equals(""))
+                       id = issuerInt + ":" + label;
+               else if (issuerExt != null && !issuerExt.equals(""))
+                       id = issuerExt + ":" + label;
+               else
+                       id = label;
+               
+               return id;
+       }
+       
+       public void remove(Context ctx) {
+               SharedPreferences prefs = ctx.getSharedPreferences(Token.class.getName(), Context.MODE_PRIVATE);
+               prefs.edit().remove(getId()).apply();
+       }
+       
+       public void save(Context ctx) {
+               SharedPreferences prefs = ctx.getSharedPreferences(Token.class.getName(), Context.MODE_PRIVATE);
+               prefs.edit().putString(getId(), toString()).apply();
+       }
+       
+       public String getTitle() {
+               String title = "";
+               if (issuerExt != null && !issuerExt.equals(""))
+                       title += issuerExt + ": ";
+               title += label;
+               return title;
+       }
+       
+       public String getCurrentTokenValue(Context ctx, boolean increment) {
+               if (type == TokenType.HOTP) {
+                       if (increment) {
+                               try {
+                                       return getHOTP(counter++);
+                               } finally {
+                                       save(ctx);
+                               }
+                       } else {
+                               String placeholder = "";
+                               for (int i = 0; i < digits; i++)
+                                       placeholder += "-";
+                               
+                               return placeholder;
+                       }
+               }
+               
+               return getHOTP(System.currentTimeMillis() / 1000 / period);
+       }
+       
+       public Uri toUri() {
+               String issuerLabel = !issuerExt.equals("") ? issuerExt + ":" + label : label;
+               
+               Uri.Builder builder = new Uri.Builder()
+                       .scheme("otpauth")
+                       .path(issuerLabel)
+                       .appendQueryParameter("secret", Base32String.encode(key))
+                       .appendQueryParameter("issuer", issuerInt == null ? issuerExt : issuerInt)
+                       .appendQueryParameter("algorithm", algo)
+                       .appendQueryParameter("digits", Integer.toString(digits));
+               
+               switch (type) {
+               case HOTP:
+                       builder.authority("hotp");
+                       builder.appendQueryParameter("counter", Long.toString(counter));
+                       break;
+               case TOTP:
+                       builder.authority("totp");
+                       builder.appendQueryParameter("period", Integer.toString(period));
+                       break;
+               }
+               
+               return builder.build();
+       }
+       
+       public TokenType getType() {
+               return type;
+       }
+       
+       // Progress is on a scale from 0 - 1000.
+       public int getProgress() {
+               int p = period * 10;
+               
+               long time = System.currentTimeMillis() / 100;
+               return (int) ((time % p) * 1000 / p);
+       }
+       
+       @Override
+       public String toString() {
+               return toUri().toString();
+       }
+}
diff --git a/src/org/fedorahosted/freeotp/TokenAdapter.java b/src/org/fedorahosted/freeotp/TokenAdapter.java
new file mode 100644 (file)
index 0000000..06a3f3b
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * FreeOTP
+ *
+ * Authors: Nathaniel McCallum <npmccallum@redhat.com>
+ *
+ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.fedorahosted.freeotp;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.fedorahosted.freeotp.Token.TokenUriInvalidException;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageButton;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+public class TokenAdapter extends BaseAdapter {
+       private static class Ticker extends Handler {
+               private static interface OnTickListener {
+                       public void tick(ProgressBar pb);
+               }
+               
+               private Map<ProgressBar, OnTickListener> map = new HashMap<ProgressBar, OnTickListener>();
+               
+               @Override
+               public void handleMessage(Message msg) {
+                       for (ProgressBar pb : map.keySet())
+                               map.get(pb).tick(pb);
+                       
+                       sendEmptyMessageDelayed(0, 200);
+               }
+               
+               public void set(ProgressBar pb, OnTickListener otl) {
+                       map.put(pb, otl);
+               }
+       }
+       
+       private List<Token> tokens = new ArrayList<Token>();
+       private Ticker ticker = new Ticker();
+       
+       private void sort() {
+               Collections.sort(tokens, new Comparator<Token>() {
+                       public int compare(Token lhs, Token rhs) {
+                               return lhs.getTitle().compareTo(rhs.getTitle());
+                       }
+               });
+       }
+       
+       public TokenAdapter(Context ctx) {
+               tokens.addAll(Token.getTokens(ctx));
+               ticker.sendEmptyMessageDelayed(0, 200);
+               sort();
+       }
+       
+       @Override
+       public int getCount() {
+               return tokens.size();
+       }
+
+       @Override
+       public Token getItem(int position) {
+               return tokens.get(position);
+       }
+
+       @Override
+       public long getItemId(int position) {
+               return position;
+       }
+
+       @Override
+       public View getView(int position, View convertView, ViewGroup parent) {
+               final Context ctx = parent.getContext();
+               
+               if (convertView == null) {
+                       switch (getItem(position).getType()) {
+                       case HOTP:
+                               convertView = View.inflate(ctx, R.layout.hotp, null);
+                               break;
+                               
+                       case TOTP:
+                               convertView = View.inflate(ctx, R.layout.totp, null);
+                               break;
+                       }
+               }
+               
+               final Token item = getItem(position);
+               final TextView code = (TextView) convertView.findViewById(R.id.code);
+               final TextView title = (TextView) convertView.findViewById(R.id.title);
+               final ImageButton ib = (ImageButton) convertView.findViewById(R.id.button);
+               
+               code.setText(item.getCurrentTokenValue(ctx, false));
+               title.setText(item.getTitle());
+               
+               ib.setOnClickListener(new OnClickListener() {
+                       public void onClick(View v) {
+                               String delmsg = ctx.getString(R.string.delete_message);
+
+                               AlertDialog ad = new AlertDialog.Builder(ctx)
+                               .setTitle("Delete")
+                               .setMessage(delmsg + item.getTitle())
+                               .setIcon(android.R.drawable.ic_delete)
+                               .setPositiveButton(R.string.delete,
+                                               new DialogInterface.OnClickListener() {
+                                                       public void onClick(DialogInterface dialog, int which) {
+                                                               tokens.remove(tokens.indexOf(item));
+                                                               item.remove(ctx);
+                                                               notifyDataSetChanged();
+                                                               dialog.dismiss();
+                                                       }
+       
+                                               })
+                               .setNegativeButton(android.R.string.cancel,
+                                               new DialogInterface.OnClickListener() {
+                                                       public void onClick(DialogInterface dialog, int which) {
+                                                               dialog.cancel();
+                                                       }
+                                               }).create();
+                               ad.show();
+                       }
+               });
+
+               switch (getItem(position).getType()) {
+               case HOTP:
+                       ImageButton hotp = (ImageButton) convertView.findViewById(R.id.hotpButton);
+                       hotp.setOnClickListener(new OnClickListener() {
+                               public void onClick(View v) {
+                                       code.setText(item.getCurrentTokenValue(ctx, true));
+                               }
+                       });
+                       break;
+                       
+               case TOTP:
+                       ProgressBar pb = (ProgressBar) convertView.findViewById(R.id.totpProgressBar);
+                       ticker.set(pb, new Ticker.OnTickListener() {
+                               public void tick(ProgressBar pb) {
+                                       int max = pb.getMax();
+                                       int pro = item.getProgress();
+                                       pb.setProgress(max - pro);
+                                       if (pro < max / 20 || pro > max / 20 * 19)
+                                               code.setText(item.getCurrentTokenValue(ctx, false));
+                               }
+                       });
+                       break;
+               }
+               
+               return convertView;
+       }
+       
+       @Override
+       public int getViewTypeCount() {
+               return 2;
+       }
+       
+       @Override
+       public int getItemViewType(int position) {
+               switch (getItem(position).getType()) {
+               case HOTP:
+                       return 0;
+               case TOTP:
+                       return 1;
+               default:
+                       return -1;
+               }
+       }
+       
+       public void add(Context ctx, String uri) throws NoSuchAlgorithmException, TokenUriInvalidException {
+               Token t = new Token(uri);
+               t.save(ctx);
+               tokens.add(t);
+               sort();
+               notifyDataSetChanged();
+       }
+}