<?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>
--- /dev/null
+<?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>
+
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<?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>
<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…</string>
+ <string name="action_forgot_password">Forgot password</string>
<string name="cancel_button">Cancel</string>
<string name="connect_button">Connect</string>
<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>
<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>
--- /dev/null
+<?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"/>
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));
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+ }
+
+}
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 {
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.
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);
* 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);
}
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);
@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);
}
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;
public class AuthenticatorService extends Service {
@Override
public IBinder onBind(Intent arg0) {
- return new Authenticator(this).getIBinder();
+ return new AccountAuthenticator(this).getIBinder();
}
}
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";
}
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;
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 });
--- /dev/null
+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");
+ }
+}