Added working EG Authenticator to the build, but haven't tied it to the app yet....
authorkenstir <kenstir@gmail.com>
Mon, 18 Nov 2013 02:18:56 +0000 (21:18 -0500)
committerkenstir <kenstir@gmail.com>
Mon, 18 Nov 2013 02:18:56 +0000 (21:18 -0500)
18 files changed:
Open-ILS/src/Android/AndroidManifest.xml
Open-ILS/src/Android/res/layout/activity_login.xml [new file with mode: 0644]
Open-ILS/src/Android/res/layout/activity_startup.xml [new file with mode: 0644]
Open-ILS/src/Android/res/menu/startup.xml [new file with mode: 0644]
Open-ILS/src/Android/res/values-large/styles.xml [new file with mode: 0644]
Open-ILS/src/Android/res/values/ou.xml [new file with mode: 0644]
Open-ILS/src/Android/res/values/strings.xml
Open-ILS/src/Android/res/values/styles.xml
Open-ILS/src/Android/res/xml/authenticator.xml [new file with mode: 0644]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivity.java
Open-ILS/src/Android/src/org/evergreen/android/views/StartupActivity.java [new file with mode: 0644]
Open-ILS/src/Android/src/org/evergreen_ils/auth/AccountAuthenticator.java [new file with mode: 0644]
Open-ILS/src/Android/src/org/evergreen_ils/auth/AuthenticationException.java [new file with mode: 0644]
Open-ILS/src/Android/src/org/evergreen_ils/auth/AuthenticatorActivity.java
Open-ILS/src/Android/src/org/evergreen_ils/auth/AuthenticatorService.java
Open-ILS/src/Android/src/org/evergreen_ils/auth/Const.java
Open-ILS/src/Android/src/org/evergreen_ils/auth/EvergreenAuthenticate.java
Open-ILS/src/Android/src/org/evergreen_ils/auth/EvergreenAuthenticator.java [new file with mode: 0644]

index ed08c6b..f03bb40 100644 (file)
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="org.evergreen.android"
+    android:installLocation="auto"
     android:versionCode="1"
-    android:versionName="1.0" 
-    android:installLocation="auto">
-
-    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-       <uses-permission android:name="android.permission.INTERNET" /> 
-       
-       <uses-permission android:name="android.permission.CAMERA" />
-       <uses-permission android:name="android.permission.VIBRATE"/>
-       <uses-permission android:name="android.permission.FLASHLIGHT"/>
-       <uses-permission android:name="android.permission.WAKE_LOCK" />
-       <uses-feature android:name="android.hardware.camera" />
-       <uses-feature android:name="android.hardware.camera.autofocus" />
-       <uses-permission android:name="android.permission.READ_CALENDAR" />
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="18" />
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.FLASHLIGHT" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
     <uses-permission android:name="android.permission.WRITE_CALENDAR" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-       
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
+    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+
     <application
-        android:icon="@drawable/evergreen_launcher_icon"
-        android:label="@string/app_name" 
-        android:theme="@style/EvergreenTheme"
         android:allowBackup="true"
-        >
-        
+        android:icon="@drawable/evergreen_launcher_icon"
+        android:label="@string/ou_app_name"
+        android:theme="@style/EvergreenTheme" >
+
         <!-- Notification receiver -->
-        <receiver  android:process=":remote" android:name=".services.NotificationReceiver"></receiver>
-               <!-- Receiver to reinit notifications on reboot -->
-               <receiver android:name=".services.RebootReceiver">
-                       <intent-filter>
-                       <action android:name="android.intent.action.BOOT_COMPLETED"/>
-                       </intent-filter>
-               </receiver>
-               <receiver android:name=".services.PeriodicServiceBroadcastReceiver">
-                       <intent-filter>
-                       <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
-                       </intent-filter>
-               </receiver>
-               <service android:name=".services.ScheduledIntentService"></service>
-        
-        
+        <receiver
+            android:name="org.evergreen.android.services.NotificationReceiver"
+            android:process=":remote" >
+        </receiver>
+        <!-- Receiver to reinit notifications on reboot -->
+        <receiver android:name="org.evergreen.android.services.RebootReceiver" >
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name="org.evergreen.android.services.PeriodicServiceBroadcastReceiver" >
+            <intent-filter>
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+            </intent-filter>
+        </receiver>
+
+        <service android:name="org.evergreen.android.services.ScheduledIntentService" >
+        </service>
+
         <activity
-            android:name=".views.splashscreen.SplashActivity"
-            android:label="@string/app_name"
-            android:theme="@android:style/Theme.Light.NoTitleBar">
-               <intent-filter>
+            android:name="org.evergreen.android.views.splashscreen.SplashActivity"
+            android:label="@string/ou_app_name"
+            android:theme="@android:style/Theme.Light.NoTitleBar" >
+            <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
+
                 <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>          
+            </intent-filter>
         </activity>
-        
         <activity
-            android:name=".views.MainScreenDashboard"
-            android:label="@string/app_name"
-            android:theme="@android:style/Theme.Light.NoTitleBar">
-        </activity>
-        
-        <activity android:name=".views.AccountScreenDashboard"
-                       android:theme="@android:style/Theme.Light.NoTitleBar"></activity>
-        
-        <activity android:name="org.evergreen.android.views.ApplicationPreferences"></activity>
-        
+            android:name="org.evergreen.android.views.MainScreenDashboard"
+            android:label="@string/ou_app_name"
+            android:theme="@android:style/Theme.Light.NoTitleBar" >
+        </activity>
+        <activity
+            android:name="org.evergreen.android.views.AccountScreenDashboard"
+            android:theme="@android:style/Theme.Light.NoTitleBar" >
+        </activity>
+        <activity android:name="org.evergreen.android.views.ApplicationPreferences" >
+        </activity>
+
         <!-- First launch configuration activity -->
+        <activity android:name="org.evergreen.android.views.ConfigureApplicationActivity" >
+        </activity>
+
+        <!-- Search -->
+
+        <activity
+            android:name="org.evergreen.android.searchCatalog.SampleUnderlinesNoFade"
+            android:label="Underlines/No Fade" >
+        </activity>
+        <activity android:name="org.evergreen.android.searchCatalog.MoreCopyInformation" >
+        </activity>
         <activity
-            android:name="org.evergreen.android.views.ConfigureApplicationActivity"></activity>
-            
-        <!-- Search -->    
-            
-        <activity android:name=".searchCatalog.SampleUnderlinesNoFade"
-            android:label="Underlines/No Fade">
-        </activity>
-               <activity android:name=".searchCatalog.MoreCopyInformation">
-               </activity>
-               <activity
-            android:name=".searchCatalog.SearchCatalogListView"
-            android:label="@string/app_name">
-        </activity>
-        <activity 
-            android:name=".searchCatalog.AdvancedSearchActivity">
-        </activity>
-        <activity android:name=".barcodescan.CaptureActivity"
-                  android:label="@string/app_name"
-                  android:screenOrientation="landscape"
-                  android:theme="@android:style/Theme.NoTitleBar"
-                  android:windowSoftInputMode="stateAlwaysHidden">
-        </activity>
-        
-        
+            android:name="org.evergreen.android.searchCatalog.SearchCatalogListView"
+            android:label="@string/ou_app_name" >
+        </activity>
+        <activity android:name="org.evergreen.android.searchCatalog.AdvancedSearchActivity" >
+        </activity>
+        <activity
+            android:name="org.evergreen.android.barcodescan.CaptureActivity"
+            android:label="@string/ou_app_name"
+            android:screenOrientation="landscape"
+            android:theme="@android:style/Theme.NoTitleBar"
+            android:windowSoftInputMode="stateAlwaysHidden" >
+        </activity>
+
         <!-- Checkout Activities -->
-        <activity android:name=".accountAccess.checkout.ItemsCheckOutListView"></activity>
-        
+        <activity android:name="org.evergreen.android.accountAccess.checkout.ItemsCheckOutListView" >
+        </activity>
+
         <!-- Holds Activities -->
-        <activity android:name=".accountAccess.holds.HoldsListView"></activity>
-        <activity android:name=".accountAccess.holds.PlaceHold"></activity>
-        <activity android:name=".accountAccess.holds.HoldDetails"></activity>
-        
+        <activity android:name="org.evergreen.android.accountAccess.holds.HoldsListView" >
+        </activity>
+        <activity android:name="org.evergreen.android.accountAccess.holds.PlaceHold" >
+        </activity>
+        <activity android:name="org.evergreen.android.accountAccess.holds.HoldDetails" >
+        </activity>
+
         <!-- Fines Activities -->
-        <activity android:name=".accountAccess.fines.FinesActivity"></activity>
-        
-        
+        <activity android:name="org.evergreen.android.accountAccess.fines.FinesActivity" >
+        </activity>
+
         <!-- Bookbags -->
-        <activity android:name=".accountAccess.bookbags.BookbagsListView"></activity>
-        <activity android:name=".accountAccess.bookbags.BookBagDetails"></activity>
-        
+        <activity android:name="org.evergreen.android.accountAccess.bookbags.BookbagsListView" >
+        </activity>
+        <activity android:name="org.evergreen.android.accountAccess.bookbags.BookBagDetails" >
+        </activity>
+        <activity
+            android:name="org.evergreen.android.views.StartupActivity"
+            android:label="@string/title_activity_startup"
+            android:windowSoftInputMode="adjustResize|stateVisible" >
+        </activity>
+        <activity
+            android:name="org.evergreen_ils.auth.AuthenticatorActivity"
+            android:label="@string/ou_app_label">
+        </activity>
+        <service android:name="org.evergreen_ils.auth.AuthenticatorService">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator" />
+            </intent-filter>
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                       android:resource="@xml/authenticator" />
+        </service>
+
     </application>
-    
-</manifest>
\ No newline at end of file
+
+</manifest>
diff --git a/Open-ILS/src/Android/res/layout/activity_login.xml b/Open-ILS/src/Android/res/layout/activity_login.xml
new file mode 100644 (file)
index 0000000..7fb3f36
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:paddingLeft="17dp"
+              android:paddingRight="17dp"
+        >
+
+    <EditText android:id="@+id/accountName"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:layout_marginTop="20dp"
+              android:hint="@string/hint_username"
+              android:padding="10dp"
+            />
+
+    <EditText android:id="@+id/accountPassword"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:layout_marginTop="10dp"
+              android:inputType="textPassword"
+              android:hint="@string/hint_password"
+              android:padding="10dp"
+            />
+
+    <Button android:id="@+id/submit"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_gravity="right"
+              android:layout_marginTop="16dp"
+              android:text="@string/action_sign_in"
+              android:paddingLeft="32dp"
+              android:paddingRight="32dp"/>
+
+</LinearLayout>
+
diff --git a/Open-ILS/src/Android/res/layout/activity_startup.xml b/Open-ILS/src/Android/res/layout/activity_startup.xml
new file mode 100644 (file)
index 0000000..342efb5
--- /dev/null
@@ -0,0 +1,30 @@
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".StartupActivity" >
+
+    <LinearLayout
+        android:id="@+id/login_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center_horizontal"
+        android:orientation="vertical"
+    >
+
+        <ProgressBar
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="8dp" />
+
+        <TextView
+            android:id="@+id/login_status_message"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="16dp"
+            android:fontFamily="sans-serif-light"
+            android:text="@string/login_progress_signing_in"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+    </LinearLayout>
+
+</merge>
diff --git a/Open-ILS/src/Android/res/menu/startup.xml b/Open-ILS/src/Android/res/menu/startup.xml
new file mode 100644 (file)
index 0000000..265192f
--- /dev/null
@@ -0,0 +1,8 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/action_forgot_password"
+        android:showAsAction="never"
+        android:title="@string/action_forgot_password"/>
+
+</menu>
diff --git a/Open-ILS/src/Android/res/values-large/styles.xml b/Open-ILS/src/Android/res/values-large/styles.xml
new file mode 100644 (file)
index 0000000..7b56acd
--- /dev/null
@@ -0,0 +1,10 @@
+<resources>
+
+    <style name="LoginFormContainer">
+        <item name="android:layout_width">400dp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:padding">16dp</item>
+    </style>
+
+</resources>
diff --git a/Open-ILS/src/Android/res/values/ou.xml b/Open-ILS/src/Android/res/values/ou.xml
new file mode 100644 (file)
index 0000000..fd0d204
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- ou_cwmars.xml - C/W MARS organizational unit -->
+<resources>
+    <!--  TODO: make this HTTPS -->
+    <string name="ou_gateway_url">http://bark.cwmars.org/osrf-gateway-v1</string>
+    <string name="ou_app_name">C/W Mars Rover</string>
+    <string name="ou_app_label">C/W Mars Rover</string>
+</resources>
index 34ad835..3404bb3 100644 (file)
     <string name="first_config_message">Please enter the Evergreen server url, username and password of your account.</string>
     <string name="server_name">Library Catalog URL:</string>
     <string name="username">Username:</string>
+    <string name="hint_username">Library Card Number or Username</string>
     <string name="password">Password:</string>
-    
+    <string name="hint_password">Password or PIN</string>
+    <string name="action_sign_in">Sign in</string>
+    <string name="login_progress_signing_in">Signing in&#8230;</string>
+    <string name="action_forgot_password">Forgot password</string>
     
     <string name="cancel_button">Cancel</string>
     <string name="connect_button">Connect</string>
@@ -58,8 +62,9 @@
     
     <string name="copy_information"> Availability of the book :</string>
     <string name="hello">Hello World, EvergreenAppActivity!</string>
-    <string name="app_name">EvergreenApp</string>
+    <string name="generic_app_name">Evergreen</string>
 
+    <string name="title_activity_startup">Welcome to Evergreen</string>
     <string name="title_search">Search</string>
     <string name="title_my_account">My Account</string>
     <string name="title_application_settings">App. Settings </string>
index ddbf1c6..19b81d1 100644 (file)
         <item name="android:textSize">16sp</item>
         <item name="android:textStyle">bold</item>
         <item name="android:background">@color/background3</item>
-        <item name="android:textColor">@color/foreground1</item>     
+        <item name="android:textColor">@color/foreground1</item>
     </style>
-    
-    
+
     <style name="textSmall">
         <item name="android:textSize">14sp</item>
         <item name="android:paddingLeft">15dip</item>
         <item name="android:paddingTop">4dip</item>
-               <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item> 
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
     </style>
-    
+
     <style name="textLarge">
         <item name="android:textSize">18sp</item>
         <item name="android:paddingLeft">10dip</item>
         <item name="android:paddingTop">5dip</item>
-
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
     </style>
-    
+
     <style name="spacer">
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">14dip</item>
     </style>
-    
+
     <style name="EvergreenTheme" parent="android:Theme.Black.NoTitleBar">
         <item name="android:windowNoTitle">true</item>
-        
-       </style>
-    
-     <style name="HomeButton">
+    </style>
+
+    <style name="HomeButton">
         <item name="android:layout_gravity">center_vertical</item>
         <item name="android:layout_width">130dip</item>
         <item name="android:layout_height">130dip</item>
         <item name="android:textColor">@color/dark</item>
         <item name="android:background">@drawable/menu_background_button</item>
     </style>
-    
-     
-     <style name="TitleSearchStyleList">
+
+    <style name="TitleSearchStyleList">
         <item name="android:textSize">18sp</item>
-               <item name="android:gravity">left</item>
+        <item name="android:gravity">left</item>
         <item name="android:textStyle">bold</item>
         <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">end</item> 
-     </style>
-     
-     <style name="AuthorSearchStyleList">
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="AuthorSearchStyleList">
         <item name="android:textSize">14sp</item>
-               <item name="android:gravity">left</item>
+        <item name="android:gravity">left</item>
         <item name="android:textStyle">italic</item>
         <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">end</item> 
-     </style>
-     
-     <style name="PubSearchStyleList">
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="PubSearchStyleList">
         <item name="android:textSize">12sp</item>
-               <item name="android:gravity">right</item>
+        <item name="android:gravity">right</item>
         <item name="android:textStyle">italic</item>
         <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">end</item> 
-     </style>
-     
-     
-     
-     <!--  Tabs  -->
-     
-      <style name="StyledIndicators" parent="@android:style/Theme.Light">
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <!-- Tabs -->
+
+    <style name="StyledIndicators" parent="@android:style/Theme.Light">
         <item name="vpiCirclePageIndicatorStyle">@style/CustomCirclePageIndicator</item>
         <item name="vpiLinePageIndicatorStyle">@style/CustomLinePageIndicator</item>
         <item name="vpiTitlePageIndicatorStyle">@style/CustomTitlePageIndicator</item>
         <item name="fadeLength">1000</item>
         <item name="fadeDelay">1000</item>
     </style>
-    
-    
-    <!--  Search Details -->
-   <style name="SearchDetailsInfoSeparator">
-               <item name="android:background">@drawable/shadow_rect</item>
+
+    <!-- Search Details -->
+    <style name="SearchDetailsInfoSeparator">
+        <item name="android:background">@drawable/shadow_rect</item>
         <item name="android:padding">5dip</item>
         <item name="android:layout_marginTop">10dip</item>
         <item name="android:textColor">@color/dark</item>
-   </style>
-    
-   <style name="SeparatorInformation">
+    </style>
+
+    <style name="SeparatorInformation">
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
-               <item name="android:background">@drawable/shadow_rect</item>
+        <item name="android:background">@drawable/shadow_rect</item>
         <item name="android:padding">5dip</item>
         <item name="android:layout_marginTop">10dip</item>
         <item name="android:textColor">@color/dark</item>
         <item name="android:gravity">center</item>
         <item name="android:textStyle">bold</item>
-   </style>
-   
-   
-   <style name="SearchDetailsActionButton">
-       <item name="android:background">@drawable/details_button</item>
-       <item name="android:textColor">@drawable/dark_text_color</item>
-       
-   </style>
-   
-   <style name="HeaderTitle">
-       <item name="android:background">@drawable/title_header_background</item>
-       <item name="android:textSize">16dip</item>
-       <item name="android:textStyle">bold</item>
-       <item name="android:gravity">center</item>
-       <item name="android:paddingTop">2dip</item>
-       <item name="android:paddingLeft">5dip</item>
-       <item name="android:paddingBottom">2dip</item>
-       <item name="android:textColor">@color/header_title</item>
-   </style>
-   
-   <style name="Content">
-       <item name="android:layout_marginTop">5dip</item>
-   </style>
-    
-   
-   
-   <style name="HeaderButton">
-       <item name="android:textColor">@color/white</item>
-       <item name="android:background">@drawable/header_button</item>
-       <item name="android:layout_marginRight">5dip</item>
-   </style>
-   
-   <style name="ApplicationButton">
-       <item name="android:textColor">@color/white</item>
-       <item name="android:background">@drawable/application_button</item>
-       <item name="android:padding">10dip</item>
-   </style>
-   
-    <!--  Menu List Buttons -->
-   <style name="MenuListButton">
-               <item name="android:layout_margin">5dip</item>
-               <item name="android:gravity">top|center_horizontal</item>
-               <item name="android:paddingTop">5dip</item>
-               <item name="android:textColor">#eee</item>
-               <item name="android:textStyle">bold</item>
-               
-   </style>
-    
-   <style name="MenuListRedButton">
-       <item name="android:background">@drawable/menu_red_button</item>
-               <item name="android:layout_margin">5dip</item>
-   </style>
-   
-   <style name="MenuListOrangeButton">
-       <item name="android:background">@drawable/menu_orange_button</item>
-               <item name="android:layout_margin">5dip</item>
-               <item name="android:textColor">@color/white</item>
-   </style>
-   
-   <style name="MenuListBlueButton">
-       <item name="android:background">@drawable/menu_blue_button</item>
-       <item name="android:layout_margin">5dip</item>
-       <item name="android:textColor">@color/white</item>
-   </style>
-   
-   <style name="MenuListPurpleButton">
-       <item name="android:background">@drawable/menu_purple_button</item>
-       <item name="android:layout_margin">5dip</item>
-       <item name="android:textColor">@color/white</item>
-   </style>
-   
-   
-   <style name="DashboardButton">
+    </style>
+
+    <style name="SearchDetailsActionButton">
+        <item name="android:background">@drawable/details_button</item>
+        <item name="android:textColor">@drawable/dark_text_color</item>
+    </style>
+
+    <style name="HeaderTitle">
+        <item name="android:background">@drawable/title_header_background</item>
+        <item name="android:textSize">16dip</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:gravity">center</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingLeft">5dip</item>
+        <item name="android:paddingBottom">2dip</item>
+        <item name="android:textColor">@color/header_title</item>
+    </style>
+
+    <style name="Content">
+        <item name="android:layout_marginTop">5dip</item>
+    </style>
+
+    <style name="HeaderButton">
+        <item name="android:textColor">@color/white</item>
+        <item name="android:background">@drawable/header_button</item>
+        <item name="android:layout_marginRight">5dip</item>
+    </style>
+
+    <style name="ApplicationButton">
+        <item name="android:textColor">@color/white</item>
+        <item name="android:background">@drawable/application_button</item>
+        <item name="android:padding">10dip</item>
+    </style>
+
+    <!-- Menu List Buttons -->
+    <style name="MenuListButton">
+        <item name="android:layout_margin">5dip</item>
+        <item name="android:gravity">top|center_horizontal</item>
+        <item name="android:paddingTop">5dip</item>
+        <item name="android:textColor">#eee</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="MenuListRedButton">
+        <item name="android:background">@drawable/menu_red_button</item>
+        <item name="android:layout_margin">5dip</item>
+    </style>
+
+    <style name="MenuListOrangeButton">
+        <item name="android:background">@drawable/menu_orange_button</item>
+        <item name="android:layout_margin">5dip</item>
+        <item name="android:textColor">@color/white</item>
+    </style>
+
+    <style name="MenuListBlueButton">
+        <item name="android:background">@drawable/menu_blue_button</item>
+        <item name="android:layout_margin">5dip</item>
+        <item name="android:textColor">@color/white</item>
+    </style>
+
+    <style name="MenuListPurpleButton">
+        <item name="android:background">@drawable/menu_purple_button</item>
+        <item name="android:layout_margin">5dip</item>
+        <item name="android:textColor">@color/white</item>
+    </style>
+
+    <style name="DashboardButton">
         <item name="android:layout_gravity">center_vertical</item>
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:textStyle">bold</item>
         <item name="android:textColor">#ff29549f</item>
         <item name="android:background">@null</item>
-    </style> 
-   
-   
-       <!-- Header portion start -->
-       
-       <style name="LibraryLogoText">
-               <item name="android:background">@drawable/logo_button</item>
-    </style>
-       
-       <!-- Header portion end -->
+    </style>
+
+    <!-- Header portion start -->
+
+    <style name="LibraryLogoText">
+        <item name="android:background">@drawable/logo_button</item>
+    </style>
+
+    <!-- Header portion end -->
+    <style name="LoginFormContainer">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:padding">16dp</item>
+    </style>
+
 </resources>
diff --git a/Open-ILS/src/Android/res/xml/authenticator.xml b/Open-ILS/src/Android/res/xml/authenticator.xml
new file mode 100644 (file)
index 0000000..0f35665
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+                       android:accountType="org.evergreen_ils.opac"
+                       android:icon="@drawable/evergreen_launcher_icon"
+                       android:smallIcon="@drawable/evergreen_launcher_icon"
+                       android:label="@string/ou_app_label"/>
index 5f5e6d5..4d7a8da 100755 (executable)
@@ -151,7 +151,7 @@ public class CaptureActivity extends Activity implements SurfaceHolder.Callback
 
        private void displayFrameworkBugMessageAndExit(String info) {
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
-               builder.setTitle(getString(R.string.app_name));
+               builder.setTitle(getString(R.string.ou_app_name));
                builder.setMessage("[" + info + "] "
                                + getString(R.string.msg_camera_framework_bug));
                builder.setPositiveButton(R.string.button_ok, new FinishListener(this));
diff --git a/Open-ILS/src/Android/src/org/evergreen/android/views/StartupActivity.java b/Open-ILS/src/Android/src/org/evergreen/android/views/StartupActivity.java
new file mode 100644 (file)
index 0000000..e12d484
--- /dev/null
@@ -0,0 +1,94 @@
+package org.evergreen.android.views;
+
+import org.evergreen.android.R;
+import org.evergreen.android.searchCatalog.SearchCatalogListView;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.widget.TextView;
+
+/**
+ * Activity which displays a login screen to the user, offering registration as
+ * well.
+ */
+public class StartupActivity extends Activity {
+
+    private TextView mLoginStatusMessageView;
+    private StartupTask mStartupTask = null;
+    private String mAlertMessage = null;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_startup);
+        mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message);
+        
+        //TODO
+        /*
+        last_username = getPref();
+        if (last_username) getAuthToken()
+         */
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+    }
+
+    /*
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        getMenuInflater().inflate(R.menu.startup, menu);
+        return true;
+    }
+    */
+
+    private void startNextActivity() {
+        Intent intent = new Intent(this, SearchCatalogListView.class);
+        startActivity(intent);
+        finish();
+    }
+
+    public void downloadResources() {
+        if (mStartupTask != null) {
+            return;
+        }
+        
+        // blah blah blah
+        // mDownloadTask = new LoginTask();
+    }
+
+    public class StartupTask extends AsyncTask<Void, Void, Boolean> {
+        @Override
+        protected Boolean doInBackground(Void... params) {
+            try {
+                // TODO
+                Thread.sleep(2000);
+            } catch (InterruptedException e) {
+                return false;
+            }
+
+            return true;
+        }
+        
+        @Override
+        protected void onProgressUpdate(Void... params) {
+        }
+
+        @Override
+        protected void onPostExecute(final Boolean success) {
+            mStartupTask = null;
+            if (success) {
+                startNextActivity();
+            }
+        }
+
+        @Override
+        protected void onCancelled() {
+            mStartupTask = null;
+        }
+    }
+}
diff --git a/Open-ILS/src/Android/src/org/evergreen_ils/auth/AccountAuthenticator.java b/Open-ILS/src/Android/src/org/evergreen_ils/auth/AccountAuthenticator.java
new file mode 100644 (file)
index 0000000..6a93ebc
--- /dev/null
@@ -0,0 +1,129 @@
+package org.evergreen_ils.auth;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+public class AccountAuthenticator extends AbstractAccountAuthenticator {
+    
+    private final String TAG = "eg.auth";
+    private Context context;
+
+    public AccountAuthenticator(Context context) {
+        super(context);
+        this.context = context;
+    }
+
+    @Override
+    public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
+        Log.d(TAG, "addaccount "+accountType+" "+authTokenType);
+        final Intent intent = new Intent(context, AuthenticatorActivity.class);
+        // setting ARG_IS_ADDING_NEW_ACCOUNT here does not work, because this is not the
+        // same Intent as the one in AuthenticatorActivity.finishLogin
+        //intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
+        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+        
+        Bundle result = new Bundle();
+        result.putParcelable(AccountManager.KEY_INTENT, intent);
+        return result;
+    }
+
+    @Override
+    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
+        Log.d(TAG, "getAuthToken> "+account.name);
+
+        // If the caller requested an authToken type we don't support, then
+        // return an error
+        if (!authTokenType.equals(Const.AUTHTOKEN_TYPE)) {
+            final Bundle result = new Bundle();
+            result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
+            return result;
+        }
+
+        final AccountManager am = AccountManager.get(context);
+        String authToken = am.peekAuthToken(account, authTokenType);
+        Log.d(TAG, "getAuthToken> peekAuthToken returned " + authToken);
+        if (TextUtils.isEmpty(authToken)) {
+            final String password = am.getPassword(account);
+            if (password != null) {
+                try {
+                    Log.d(TAG, "getAuthToken> attempting to sign in with existing password");
+                    authToken = EvergreenAuthenticator.signIn(context, account.name, password);
+                    Log.d(TAG, "getAuthToken> signIn returned token "+authToken);
+                } catch (AuthenticationException e) {
+                    Log.d(TAG, "getAuthToken> caught exception", e);
+                    Log.d(TAG, "getAuthToken> caught exception "+e.getMessage());
+                    final Bundle result = new Bundle();
+                    result.putString(AccountManager.KEY_ERROR_MESSAGE, e.getMessage());
+                    return result;
+                } catch (Exception e2) {
+                    Log.d(TAG, "getAuthToken> caught other Exception");
+                    final Bundle result = new Bundle();
+                    result.putString(AccountManager.KEY_ERROR_MESSAGE, "Sign in failed");
+                    return result;
+                }
+            }
+        }
+
+        // If we get an authToken - we return it
+        Log.d(TAG, "getAuthToken> token "+authToken);
+        if (!TextUtils.isEmpty(authToken)) {
+            final Bundle result = new Bundle();
+            result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+            result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+            result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
+            return result;
+        }
+
+        // If we get here, then we couldn't access the user's password - so we
+        // need to re-prompt them for their credentials. We do that by creating
+        // an intent to display our AuthenticatorActivity.
+        Log.d(TAG, "getAuthToken> creating intent to display AuthenticatorActivity");
+        final Intent intent = new Intent(context, AuthenticatorActivity.class);
+        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+        intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type);
+        intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
+        intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_NAME, account.name);
+        final Bundle bundle = new Bundle();
+        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+        return bundle;
+    }
+
+    @Override
+    public String getAuthTokenLabel(String authTokenType) {
+        return Const.AUTHTOKEN_TYPE_LABEL;
+    }
+
+    @Override
+    public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
+        Log.d(TAG, "hasFeatures "+account.name+" features "+features);
+        final Bundle result = new Bundle();
+        result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+        return result;
+    }
+
+    @Override
+    public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+        Log.d(TAG, "editProperties "+accountType);
+        return null;
+    }
+
+    @Override
+    public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
+        Log.d(TAG, "confirmCredentials "+account.name);
+        return null;
+    }
+
+    @Override
+    public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
+        Log.d(TAG, "updateCredentials "+account.name);
+        return null;
+    }
+}
diff --git a/Open-ILS/src/Android/src/org/evergreen_ils/auth/AuthenticationException.java b/Open-ILS/src/Android/src/org/evergreen_ils/auth/AuthenticationException.java
new file mode 100644 (file)
index 0000000..de033a1
--- /dev/null
@@ -0,0 +1,20 @@
+package org.evergreen_ils.auth;
+
+public class AuthenticationException extends Exception {
+
+    public AuthenticationException() {
+    }
+
+    public AuthenticationException(String detailMessage) {
+        super(detailMessage);
+    }
+
+    public AuthenticationException(Throwable throwable) {
+        super(throwable);
+    }
+
+    public AuthenticationException(String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+    }
+
+}
index 2435561..f775641 100644 (file)
@@ -1,15 +1,21 @@
 package org.evergreen_ils.auth;
 
+import org.evergreen.android.R;
+
+import android.accounts.Account;
 import android.accounts.AccountAuthenticatorActivity;
 import android.accounts.AccountManager;
-import android.accounts.Account;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.widget.TextView;
-import android.widget.Toast;
 
 public class AuthenticatorActivity extends AccountAuthenticatorActivity {
 
@@ -18,16 +24,17 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity {
     public final static String ARG_ACCOUNT_TYPE = "ACCOUNT_TYPE";
     public final static String ARG_AUTH_TYPE = "AUTH_TYPE";
     public final static String ARG_ACCOUNT_NAME = "ACCOUNT_NAME";
-    public final static String ARG_IS_ADDING_NEW_ACCOUNT = "IS_ADDING_ACCOUNT";
-
+    //public final static String ARG_IS_ADDING_NEW_ACCOUNT = "IS_ADDING_ACCOUNT";
     public static final String KEY_ERROR_MESSAGE = "ERR_MSG";
-
     public final static String PARAM_USER_PASS = "USER_PASS";
-
     private final int REQ_SIGNUP = 1;
+    private static final String STATE_ALERT_MESSAGE = "state_dialog";
 
     private AccountManager accountManager;
     private String authTokenType;
+    private AsyncTask task = null;
+    private AlertDialog alertDialog = null;
+    private String alertMessage = null;
 
     /**
      * Called when the activity is first created.
@@ -39,9 +46,11 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity {
         accountManager = AccountManager.get(getBaseContext());
 
         String accountName = getIntent().getStringExtra(ARG_ACCOUNT_NAME);
+        Log.d(TAG, "onCreate> accountName="+accountName);
         authTokenType = getIntent().getStringExtra(ARG_AUTH_TYPE);
         if (authTokenType == null)
             authTokenType = Const.AUTHTOKEN_TYPE;
+        Log.d(TAG, "onCreate> authTokenType="+authTokenType);
 
         if (accountName != null) {
             ((TextView) findViewById(R.id.accountName)).setText(accountName);
@@ -65,15 +74,32 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity {
          * SignUpActivity.class); signup.putExtras(getIntent().getExtras());
          * startActivityForResult(signup, REQ_SIGNUP); } });
          */
+        if (savedInstanceState != null) {
+            Log.d(TAG, "onCreate> should I create a dialog here? alertDialog="+alertDialog+" alertMessage="+alertMessage);
+            if (savedInstanceState.getString(STATE_ALERT_MESSAGE) != null) {
+                showAlert(savedInstanceState.getString(STATE_ALERT_MESSAGE));
+            }
+        }
+    }
+    
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (alertMessage != null) {
+            outState.putString(STATE_ALERT_MESSAGE, alertMessage);
+        }
+        if (task != null) {
+            Log.d(TAG, "onSaveInstanceState> we have task, should we cancel it?");
+        }
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-
+        Log.d(TAG, "onActivityResult> requestCode="+requestCode+" resultCode="+resultCode);
         // The sign up activity returned that the user has successfully created
         // an account
         if (requestCode == REQ_SIGNUP && resultCode == RESULT_OK) {
-            finishLogin(data);
+            onAuthSuccess(data);
         } else
             super.onActivityResult(requestCode, resultCode, data);
     }
@@ -84,26 +110,33 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity {
         final String password = ((TextView) findViewById(R.id.accountPassword)).getText().toString();
         //final String account_type = getIntent().getStringExtra(ARG_ACCOUNT_TYPE);
 
-        new AsyncTask<String, Void, Intent>() {
+        task = new AsyncTask<String, Void, Intent>() {
 
             @Override
             protected Intent doInBackground(String... params) {
 
-                Log.d(TAG, "Started authenticating");
+                Log.d(TAG, "task> Start authenticating");
 
                 String authtoken = null;
+                String errorMessage = "Login failed";
                 Bundle data = new Bundle();
                 try {
-                    authtoken = EvergreenAuthenticate.signIn(AuthenticatorActivity.this, username, password);
+                    authtoken = EvergreenAuthenticator.signIn(AuthenticatorActivity.this, username, password);
+                    Log.d(TAG, "task> signIn returned "+authtoken);
 
                     data.putString(AccountManager.KEY_ACCOUNT_NAME, username);
                     data.putString(AccountManager.KEY_ACCOUNT_TYPE, Const.ACCOUNT_TYPE);
                     data.putString(AccountManager.KEY_AUTHTOKEN, authtoken);
                     data.putString(PARAM_USER_PASS, password);
-
-                } catch (Exception e) {
-                    data.putString(KEY_ERROR_MESSAGE, e.getMessage());
+                } catch (AuthenticationException e) {
+                    if (e != null) errorMessage = e.getMessage();
+                    Log.d(TAG, "task> signIn caught auth exception "+errorMessage);
+                } catch (Exception e2) {
+                    if (e2 != null) errorMessage = e2.getMessage();
+                    Log.d(TAG, "task> signIn caught other exception "+errorMessage);
                 }
+                if (authtoken == null)
+                    data.putString(KEY_ERROR_MESSAGE, errorMessage);
 
                 final Intent res = new Intent();
                 res.putExtras(data);
@@ -112,39 +145,57 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity {
 
             @Override
             protected void onPostExecute(Intent intent) {
+                task = null;
                 if (intent.hasExtra(KEY_ERROR_MESSAGE)) {
-                    Toast.makeText(getBaseContext(),
-                            intent.getStringExtra(KEY_ERROR_MESSAGE),
-                            Toast.LENGTH_SHORT).show();
+                    Log.d(TAG, "task.onPostExecute> error msg: "+intent.getStringExtra(KEY_ERROR_MESSAGE));
+                    onAuthFailure(intent.getStringExtra(KEY_ERROR_MESSAGE));
                 } else {
-                    finishLogin(intent);
+                    Log.d(TAG, "task.onPostExecute> no error msg, finishing");
+                    onAuthSuccess(intent);
                 }
             }
         }.execute();
     }
 
-    private void finishLogin(Intent intent) {
-        Log.d(TAG, "finishLogin");
+    protected void onAuthFailure(String errorMessage) {
+        showAlert(errorMessage);
+    }
+    
+    protected void showAlert(String errorMessage) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        alertMessage = errorMessage;
+        alertDialog = builder.setTitle("Login failed")
+                .setMessage(errorMessage)
+                .setPositiveButton(android.R.string.ok, new OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        alertDialog = null;
+                        alertMessage = null;
+                    }
+                })
+                .create();
+        alertDialog.show();
+    }
 
-        String accountName = intent
-                .getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
+    private void onAuthSuccess(Intent intent) {
+        String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
+        String accountType = intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
         String accountPassword = intent.getStringExtra(PARAM_USER_PASS);
-        final Account account = new Account(accountName,
-                intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE));
-
-        if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) {
-            Log.d(TAG, "finishLogin > addAccountExplicitly");
-            String authtoken = intent
-                    .getStringExtra(AccountManager.KEY_AUTHTOKEN);
-            String authtokenType = authTokenType;
-
-            // Creating the account on the device and setting the auth token we
-            // got
-            // (Not setting the auth token will cause another call to the server
-            // to authenticate the user)
-            accountManager.addAccountExplicitly(account, accountPassword, null);
+        final Account account = new Account(accountName, accountType);
+        Log.d(TAG, "finishLogin> accountName="+accountName);
+
+        //if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false))
+        Log.d(TAG, "finishLogin > addAccountExplicitly "+accountName);
+        String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN);
+        String authtokenType = authTokenType;
+
+        // Create the account on the device
+        if (accountManager.addAccountExplicitly(account, accountPassword, null)) {
+            // Not setting the auth token will cause another call to the server
+            // to authenticate the user
             accountManager.setAuthToken(account, authtokenType, authtoken);
         } else {
+            // Probably the account already existed, in which case update the password
             Log.d(TAG, "finishLogin > setPassword");
             accountManager.setPassword(account, accountPassword);
         }
index 3a54fe8..bb4f082 100644 (file)
@@ -1,6 +1,6 @@
 package org.evergreen_ils.auth;
 
-import org.evergreen_ils.auth.Authenticator;
+import org.evergreen_ils.auth.AccountAuthenticator;
 
 import android.app.Service;
 import android.content.Intent;
@@ -9,6 +9,6 @@ import android.os.IBinder;
 public class AuthenticatorService extends Service {
     @Override
     public IBinder onBind(Intent arg0) {
-        return new Authenticator(this).getIBinder();
+        return new AccountAuthenticator(this).getIBinder();
     }
 }
index 0f114a6..6df2e20 100644 (file)
@@ -1,7 +1,7 @@
 package org.evergreen_ils.auth;
 
 public class Const {
-    public static final String ACCOUNT_TYPE = "org.evergreen-ils.opac";
+    public static final String ACCOUNT_TYPE = "org.evergreen_ils.opac";
     public static final String AUTHTOKEN_TYPE = "opac";
     public static final String AUTHTOKEN_TYPE_LABEL = "Online Public Access Catalog";
 }
index 3831d57..4b00b08 100644 (file)
@@ -5,6 +5,7 @@ import java.security.NoSuchAlgorithmException;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.evergreen.android.R;
 import org.opensrf.Method;
 import org.opensrf.net.http.GatewayRequest;
 import org.opensrf.net.http.HttpConnection;
@@ -71,7 +72,7 @@ public class EvergreenAuthenticate {
     public static String signIn(Context context, String username, String password) throws Exception {
         Log.d(TAG, "signIn "+username);
 
-        HttpConnection conn = new HttpConnection(context.getString(R.string.gateway_url));
+        HttpConnection conn = new HttpConnection(context.getString(R.string.ou_gateway_url));
 
         // step 1: get seed
         Object resp = doRequest(conn, SERVICE_AUTH, METHOD_AUTH_INIT, new Object[] { username });
diff --git a/Open-ILS/src/Android/src/org/evergreen_ils/auth/EvergreenAuthenticator.java b/Open-ILS/src/Android/src/org/evergreen_ils/auth/EvergreenAuthenticator.java
new file mode 100644 (file)
index 0000000..b451dc4
--- /dev/null
@@ -0,0 +1,119 @@
+package org.evergreen_ils.auth;
+
+import java.net.MalformedURLException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.opensrf.Method;
+import org.opensrf.net.http.GatewayRequest;
+import org.opensrf.net.http.HttpConnection;
+import org.opensrf.net.http.HttpRequest;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import org.evergreen.android.R;
+
+public class EvergreenAuthenticator {
+    private final static String TAG = "eg.auth";
+    public final static String SERVICE_AUTH = "open-ils.auth";
+    public final static String METHOD_AUTH_INIT = "open-ils.auth.authenticate.init";
+    public final static String METHOD_AUTH_COMPLETE = "open-ils.auth.authenticate.complete";
+
+    private static String md5(String s) {
+        try {
+            MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
+            digest.update(s.getBytes());
+            byte messageDigest[] = digest.digest();
+
+            // Create Hex String
+            StringBuffer hexString = new StringBuffer();
+            for (int i = 0; i < messageDigest.length; i++) {
+                String hex = Integer.toHexString(0xFF & messageDigest[i]);
+                if (hex.length() == 1) {
+                    // could use a for loop, but we're only dealing with a
+                    // single byte
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+
+        return "";
+    }
+
+    public static Object doRequest(HttpConnection conn, String service, String methodName, Object[] params) {
+        Method method = new Method(methodName);
+
+        Log.d(TAG, "doRequest> Method :" + methodName + ":");
+        for (int i = 0; i < params.length; i++) {
+            method.addParam(params[i]);
+            Log.d(TAG, "doRequest> Param " + i + ": " + params[i]);
+        }
+
+        // sync request
+        HttpRequest req = new GatewayRequest(conn, service, method).send();
+        Object resp;
+
+        while ((resp = req.recv()) != null) {
+            Log.d(TAG, "doRequest> Sync Response: " + resp);
+            Object response = (Object) resp;
+            return response;
+        }
+        return null;
+    }
+    
+    @SuppressWarnings("unchecked")
+    public static String signIn(Context context, String username, String password) throws AuthenticationException {
+        Log.d(TAG, "signIn> "+username);
+
+        HttpConnection conn;
+        try {
+            conn = new HttpConnection(context.getString(R.string.ou_gateway_url));
+        } catch (MalformedURLException e) {
+            throw new AuthenticationException(e);
+        }
+
+        // step 1: get seed
+        Object resp = doRequest(conn, SERVICE_AUTH, METHOD_AUTH_INIT, new Object[] { username });
+        if (resp == null)
+            throw new AuthenticationException("Unable to contact login service");
+        String seed = resp.toString();
+
+        // step 2: complete auth with seed + password
+        HashMap<String, String> complexParam = new HashMap<String, String>();
+        complexParam.put("type", "opac");
+        complexParam.put("username", username);
+        complexParam.put("password", md5(seed + md5(password)));
+        resp = doRequest(conn, SERVICE_AUTH, METHOD_AUTH_COMPLETE, new Object[] { complexParam });
+        if (resp == null)
+            throw new AuthenticationException("Unable to complete login");
+        
+        // parse response
+        String textcode = ((Map<String, String>) resp).get("textcode");
+        System.out.println("textcode: " + textcode);
+        if (textcode.equals("SUCCESS")) {
+            Object payload = ((Map<String, String>) resp).get("payload");
+            System.out.println("payload: " + payload);
+            String authtoken = ((Map<String, String>) payload).get("authtoken");
+            System.out.println("authtoken: " + authtoken);
+            Integer authtime = ((Map<String, Integer>) payload).get("authtime");
+            System.out.println("authtime: " + authtime);
+            return authtoken;
+        } else if (textcode.equals("LOGIN_FAILED")) {
+            String desc = ((Map<String, String>) resp).get("desc");
+            System.out.println("desc: "+desc);
+            if (!TextUtils.isEmpty(desc)) {
+                throw new AuthenticationException(desc);
+            }
+        }
+        
+        throw new AuthenticationException("Login failed");
+    }
+}