--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+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
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+# 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 *;
+#}
--- /dev/null
+# 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
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<?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>
--- /dev/null
+<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>
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}